0
# Nextcord UI Framework
1
2
Interactive Discord UI components including views, buttons, select menus, modals, and text inputs for rich user interactions.
3
4
## Views
5
6
Container classes for UI components with interaction handling and lifecycle management.
7
8
### View Class { .api }
9
10
```python
11
import nextcord
12
from nextcord.ui import View, Item, Button, Select, Modal
13
from typing import Optional, List, Any, Callable
14
15
class View:
16
"""Represents a UI view with interactive components.
17
18
A view is a collection of UI components that can be attached to messages
19
to create interactive interfaces.
20
21
Attributes
22
----------
23
timeout: Optional[float]
24
The timeout duration in seconds. None means no timeout.
25
children: List[Item]
26
The list of UI components in this view.
27
"""
28
29
def __init__(self, *, timeout: Optional[float] = 180.0):
30
"""Initialize a new view.
31
32
Parameters
33
----------
34
timeout: Optional[float]
35
The timeout for this view in seconds. Defaults to 180 seconds (3 minutes).
36
Set to None for no timeout.
37
"""
38
...
39
40
def add_item(self, item: Item) -> Self:
41
"""Add an item to this view.
42
43
Parameters
44
----------
45
item: Item
46
The UI component to add to this view.
47
48
Returns
49
-------
50
Self
51
The view instance for method chaining.
52
"""
53
...
54
55
def remove_item(self, item: Item) -> Self:
56
"""Remove an item from this view.
57
58
Parameters
59
----------
60
item: Item
61
The UI component to remove from this view.
62
63
Returns
64
-------
65
Self
66
The view instance for method chaining.
67
"""
68
...
69
70
def clear_items(self) -> Self:
71
"""Remove all items from this view.
72
73
Returns
74
-------
75
Self
76
The view instance for method chaining.
77
"""
78
...
79
80
def stop(self) -> None:
81
"""Stop listening for interactions on this view."""
82
...
83
84
def is_finished(self) -> bool:
85
"""bool: Whether this view has finished running."""
86
...
87
88
def is_persistent(self) -> bool:
89
"""bool: Whether this view is persistent across bot restarts."""
90
...
91
92
async def wait(self) -> bool:
93
"""Wait until the view finishes running.
94
95
Returns
96
-------
97
bool
98
True if the view finished due to timeout, False otherwise.
99
"""
100
...
101
102
# Callback methods that can be overridden
103
async def on_timeout(self) -> None:
104
"""Called when the view times out."""
105
...
106
107
async def on_error(
108
self,
109
interaction: nextcord.Interaction,
110
error: Exception,
111
item: Item
112
) -> None:
113
"""Called when an error occurs in a view interaction.
114
115
Parameters
116
----------
117
interaction: nextcord.Interaction
118
The interaction that caused the error.
119
error: Exception
120
The error that occurred.
121
item: Item
122
The UI component that caused the error.
123
"""
124
...
125
126
async def interaction_check(self, interaction: nextcord.Interaction) -> bool:
127
"""Check if an interaction should be processed by this view.
128
129
Parameters
130
----------
131
interaction: nextcord.Interaction
132
The interaction to check.
133
134
Returns
135
-------
136
bool
137
True if the interaction should be processed, False otherwise.
138
"""
139
return True
140
141
# Basic view example
142
class MyView(View):
143
def __init__(self):
144
super().__init__(timeout=60.0) # 1 minute timeout
145
146
@nextcord.ui.button(label="Click Me!", style=nextcord.ButtonStyle.primary)
147
async def click_me(self, button: Button, interaction: nextcord.Interaction):
148
await interaction.response.send_message("Button clicked!", ephemeral=True)
149
150
async def on_timeout(self):
151
# Disable all components when the view times out
152
for child in self.children:
153
child.disabled = True
154
155
# You would typically edit the message here to show disabled state
156
# This requires keeping a reference to the message
157
158
# Usage
159
@bot.slash_command(description="Show a view with a button")
160
async def show_view(interaction: nextcord.Interaction):
161
view = MyView()
162
await interaction.response.send_message("Here's a button:", view=view)
163
```
164
165
### Persistent Views { .api }
166
167
```python
168
class PersistentView(View):
169
"""A persistent view that survives bot restarts.
170
171
Persistent views must be added to the bot during startup
172
and use custom_id for their components.
173
"""
174
175
def __init__(self):
176
super().__init__(timeout=None) # Persistent views don't timeout
177
178
@nextcord.ui.button(
179
label="Persistent Button",
180
style=nextcord.ButtonStyle.secondary,
181
custom_id="persistent:button:1" # Must have custom_id
182
)
183
async def persistent_button(self, button: Button, interaction: nextcord.Interaction):
184
await interaction.response.send_message(
185
"This button works even after bot restart!",
186
ephemeral=True
187
)
188
189
# Add persistent views during bot startup
190
@bot.event
191
async def on_ready():
192
print(f'{bot.user} has connected to Discord!')
193
194
# Add persistent views
195
bot.add_view(PersistentView())
196
197
# Example: Role assignment view
198
class RoleView(View):
199
def __init__(self):
200
super().__init__(timeout=None)
201
202
@nextcord.ui.button(
203
label="Get Member Role",
204
emoji="👤",
205
style=nextcord.ButtonStyle.green,
206
custom_id="role:member"
207
)
208
async def member_role(self, button: Button, interaction: nextcord.Interaction):
209
role = nextcord.utils.get(interaction.guild.roles, name="Member")
210
if role:
211
if role in interaction.user.roles:
212
await interaction.user.remove_roles(role)
213
await interaction.response.send_message("Member role removed!", ephemeral=True)
214
else:
215
await interaction.user.add_roles(role)
216
await interaction.response.send_message("Member role added!", ephemeral=True)
217
else:
218
await interaction.response.send_message("Member role not found!", ephemeral=True)
219
220
@nextcord.ui.button(
221
label="Get Notifications",
222
emoji="🔔",
223
style=nextcord.ButtonStyle.blurple,
224
custom_id="role:notifications"
225
)
226
async def notifications_role(self, button: Button, interaction: nextcord.Interaction):
227
role = nextcord.utils.get(interaction.guild.roles, name="Notifications")
228
if role:
229
if role in interaction.user.roles:
230
await interaction.user.remove_roles(role)
231
await interaction.response.send_message("Notifications role removed!", ephemeral=True)
232
else:
233
await interaction.user.add_roles(role)
234
await interaction.response.send_message("Notifications role added!", ephemeral=True)
235
else:
236
await interaction.response.send_message("Notifications role not found!", ephemeral=True)
237
```
238
239
## Buttons
240
241
Clickable buttons with various styles and callback handling.
242
243
### Button Class { .api }
244
245
```python
246
class Button(Item):
247
"""Represents a button component.
248
249
Attributes
250
----------
251
style: ButtonStyle
252
The style of the button.
253
custom_id: Optional[str]
254
The custom ID of the button.
255
url: Optional[str]
256
The URL this button links to (for link buttons).
257
disabled: bool
258
Whether the button is disabled.
259
label: Optional[str]
260
The label text displayed on the button.
261
emoji: Optional[Union[str, Emoji, PartialEmoji]]
262
The emoji displayed on the button.
263
row: Optional[int]
264
The row this button should be placed in (0-4).
265
"""
266
267
def __init__(
268
self,
269
*,
270
style: ButtonStyle = ButtonStyle.secondary,
271
label: Optional[str] = None,
272
disabled: bool = False,
273
custom_id: Optional[str] = None,
274
url: Optional[str] = None,
275
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
276
row: Optional[int] = None,
277
):
278
"""Initialize a button.
279
280
Parameters
281
----------
282
style: ButtonStyle
283
The visual style of the button.
284
label: Optional[str]
285
The text displayed on the button.
286
disabled: bool
287
Whether the button is disabled.
288
custom_id: Optional[str]
289
A custom ID for the button (required for non-link buttons).
290
url: Optional[str]
291
URL for link-style buttons.
292
emoji: Optional[Union[str, Emoji, PartialEmoji]]
293
Emoji to display on the button.
294
row: Optional[int]
295
Which row to place the button in (0-4).
296
"""
297
...
298
299
async def callback(self, interaction: nextcord.Interaction) -> None:
300
"""The callback function called when the button is pressed.
301
302
This method should be overridden in subclasses or set via decorator.
303
304
Parameters
305
----------
306
interaction: nextcord.Interaction
307
The interaction that triggered this button press.
308
"""
309
...
310
311
# Button styles
312
class ButtonStyle(Enum):
313
primary = 1 # Blurple
314
secondary = 2 # Grey
315
success = 3 # Green
316
danger = 4 # Red
317
link = 5 # Grey with link
318
319
# Aliases
320
blurple = 1
321
grey = 2
322
gray = 2
323
green = 3
324
red = 4
325
url = 5
326
327
# Button decorator usage
328
class ButtonView(View):
329
@nextcord.ui.button(label="Primary", style=nextcord.ButtonStyle.primary)
330
async def primary_button(self, button: Button, interaction: nextcord.Interaction):
331
await interaction.response.send_message("Primary button pressed!", ephemeral=True)
332
333
@nextcord.ui.button(label="Success", style=nextcord.ButtonStyle.success, emoji="✅")
334
async def success_button(self, button: Button, interaction: nextcord.Interaction):
335
await interaction.response.send_message("Success button pressed!", ephemeral=True)
336
337
@nextcord.ui.button(label="Danger", style=nextcord.ButtonStyle.danger, emoji="❌")
338
async def danger_button(self, button: Button, interaction: nextcord.Interaction):
339
await interaction.response.send_message("Danger button pressed!", ephemeral=True)
340
341
@nextcord.ui.button(label="Visit GitHub", style=nextcord.ButtonStyle.link, url="https://github.com")
342
async def link_button(self, button: Button, interaction: nextcord.Interaction):
343
# This callback will never be called for link buttons
344
pass
345
346
# Dynamic button creation
347
class DynamicButtonView(View):
348
def __init__(self):
349
super().__init__()
350
351
# Add buttons dynamically
352
for i in range(3):
353
button = Button(
354
label=f"Button {i+1}",
355
style=nextcord.ButtonStyle.secondary,
356
custom_id=f"dynamic_button_{i}"
357
)
358
button.callback = self.dynamic_button_callback
359
self.add_item(button)
360
361
async def dynamic_button_callback(self, interaction: nextcord.Interaction):
362
# Get which button was pressed from custom_id
363
button_num = interaction.data["custom_id"].split("_")[-1]
364
await interaction.response.send_message(f"Dynamic button {button_num} pressed!", ephemeral=True)
365
366
# Button with state management
367
class CounterView(View):
368
def __init__(self):
369
super().__init__()
370
self.counter = 0
371
self.update_button_label()
372
373
def update_button_label(self):
374
# Find and update the counter button
375
for child in self.children:
376
if hasattr(child, 'custom_id') and child.custom_id == "counter":
377
child.label = f"Count: {self.counter}"
378
break
379
380
@nextcord.ui.button(label="Count: 0", style=nextcord.ButtonStyle.primary, custom_id="counter")
381
async def counter_button(self, button: Button, interaction: nextcord.Interaction):
382
self.counter += 1
383
self.update_button_label()
384
385
# Update the message with the new view
386
await interaction.response.edit_message(view=self)
387
388
@nextcord.ui.button(label="Reset", style=nextcord.ButtonStyle.secondary)
389
async def reset_button(self, button: Button, interaction: nextcord.Interaction):
390
self.counter = 0
391
self.update_button_label()
392
await interaction.response.edit_message(view=self)
393
```
394
395
## Select Menus
396
397
Dropdown select menus for choosing from predefined options.
398
399
### Select Menu Types { .api }
400
401
```python
402
from nextcord.ui import Select, UserSelect, RoleSelect, MentionableSelect, ChannelSelect
403
from nextcord import SelectOption, ChannelType
404
405
class Select(Item):
406
"""String-based select menu with custom options.
407
408
Attributes
409
----------
410
custom_id: Optional[str]
411
The custom ID of the select menu.
412
placeholder: Optional[str]
413
Placeholder text shown when nothing is selected.
414
min_values: int
415
Minimum number of options that must be selected.
416
max_values: int
417
Maximum number of options that can be selected.
418
options: List[SelectOption]
419
The list of options available in this select menu.
420
disabled: bool
421
Whether the select menu is disabled.
422
row: Optional[int]
423
The row this select menu should be placed in (0-4).
424
"""
425
426
def __init__(
427
self,
428
*,
429
custom_id: Optional[str] = None,
430
placeholder: Optional[str] = None,
431
min_values: int = 1,
432
max_values: int = 1,
433
options: List[SelectOption] = None,
434
disabled: bool = False,
435
row: Optional[int] = None,
436
):
437
"""Initialize a select menu.
438
439
Parameters
440
----------
441
custom_id: Optional[str]
442
A custom ID for the select menu.
443
placeholder: Optional[str]
444
Placeholder text to display.
445
min_values: int
446
Minimum number of selections required.
447
max_values: int
448
Maximum number of selections allowed.
449
options: List[SelectOption]
450
List of options for the select menu.
451
disabled: bool
452
Whether the select menu is disabled.
453
row: Optional[int]
454
Which row to place the select menu in (0-4).
455
"""
456
...
457
458
def add_option(
459
self,
460
*,
461
label: str,
462
value: str,
463
description: Optional[str] = None,
464
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
465
default: bool = False,
466
) -> None:
467
"""Add an option to this select menu.
468
469
Parameters
470
----------
471
label: str
472
The user-facing name of the option.
473
value: str
474
The developer-defined value of the option.
475
description: Optional[str]
476
An additional description of the option.
477
emoji: Optional[Union[str, Emoji, PartialEmoji]]
478
An emoji for the option.
479
default: bool
480
Whether this option is selected by default.
481
"""
482
...
483
484
async def callback(self, interaction: nextcord.Interaction) -> None:
485
"""Called when an option is selected.
486
487
Parameters
488
----------
489
interaction: nextcord.Interaction
490
The interaction containing the selected values.
491
"""
492
...
493
494
class SelectOption:
495
"""Represents an option in a select menu.
496
497
Attributes
498
----------
499
label: str
500
The user-facing name of the option.
501
value: str
502
The developer-defined value of the option.
503
description: Optional[str]
504
An additional description of the option.
505
emoji: Optional[Union[str, Emoji, PartialEmoji]]
506
An emoji that appears on the option.
507
default: bool
508
Whether this option is selected by default.
509
"""
510
511
def __init__(
512
self,
513
*,
514
label: str,
515
value: str,
516
description: Optional[str] = None,
517
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
518
default: bool = False,
519
):
520
"""Create a select option.
521
522
Parameters
523
----------
524
label: str
525
The display name for this option.
526
value: str
527
The value returned when this option is selected.
528
description: Optional[str]
529
Additional description text for this option.
530
emoji: Optional[Union[str, Emoji, PartialEmoji]]
531
Emoji to display with this option.
532
default: bool
533
Whether this option is selected by default.
534
"""
535
...
536
537
# Basic select menu example
538
class ColorSelectView(View):
539
@nextcord.ui.select(
540
placeholder="Choose your favorite color...",
541
min_values=1,
542
max_values=1,
543
options=[
544
nextcord.SelectOption(
545
label="Red",
546
description="The color of fire",
547
emoji="🔴",
548
value="red"
549
),
550
nextcord.SelectOption(
551
label="Blue",
552
description="The color of water",
553
emoji="🔵",
554
value="blue"
555
),
556
nextcord.SelectOption(
557
label="Green",
558
description="The color of nature",
559
emoji="🟢",
560
value="green"
561
),
562
]
563
)
564
async def select_callback(self, select: Select, interaction: nextcord.Interaction):
565
selected_color = select.values[0]
566
await interaction.response.send_message(
567
f"You selected {selected_color}!",
568
ephemeral=True
569
)
570
571
# Multi-select example
572
class HobbiesSelectView(View):
573
@nextcord.ui.select(
574
placeholder="Select your hobbies (up to 3)...",
575
min_values=0,
576
max_values=3,
577
options=[
578
nextcord.SelectOption(label="Gaming", value="gaming", emoji="🎮"),
579
nextcord.SelectOption(label="Reading", value="reading", emoji="📚"),
580
nextcord.SelectOption(label="Music", value="music", emoji="🎵"),
581
nextcord.SelectOption(label="Sports", value="sports", emoji="⚽"),
582
nextcord.SelectOption(label="Art", value="art", emoji="🎨"),
583
nextcord.SelectOption(label="Cooking", value="cooking", emoji="👨🍳"),
584
]
585
)
586
async def hobbies_callback(self, select: Select, interaction: nextcord.Interaction):
587
if select.values:
588
hobbies = ", ".join(select.values)
589
await interaction.response.send_message(
590
f"Your selected hobbies: {hobbies}",
591
ephemeral=True
592
)
593
else:
594
await interaction.response.send_message(
595
"You didn't select any hobbies.",
596
ephemeral=True
597
)
598
```
599
600
### Discord Entity Select Menus { .api }
601
602
```python
603
# User select menu
604
class UserSelect(Item):
605
"""A select menu for choosing users/members.
606
607
Attributes
608
----------
609
custom_id: Optional[str]
610
The custom ID of the select menu.
611
placeholder: Optional[str]
612
Placeholder text shown when nothing is selected.
613
min_values: int
614
Minimum number of users that must be selected.
615
max_values: int
616
Maximum number of users that can be selected.
617
disabled: bool
618
Whether the select menu is disabled.
619
"""
620
pass
621
622
class RoleSelect(Item):
623
"""A select menu for choosing roles.
624
625
Attributes
626
----------
627
custom_id: Optional[str]
628
The custom ID of the select menu.
629
placeholder: Optional[str]
630
Placeholder text shown when nothing is selected.
631
min_values: int
632
Minimum number of roles that must be selected.
633
max_values: int
634
Maximum number of roles that can be selected.
635
disabled: bool
636
Whether the select menu is disabled.
637
"""
638
pass
639
640
class MentionableSelect(Item):
641
"""A select menu for choosing mentionable entities (users and roles).
642
643
Attributes
644
----------
645
custom_id: Optional[str]
646
The custom ID of the select menu.
647
placeholder: Optional[str]
648
Placeholder text shown when nothing is selected.
649
min_values: int
650
Minimum number of entities that must be selected.
651
max_values: int
652
Maximum number of entities that can be selected.
653
disabled: bool
654
Whether the select menu is disabled.
655
"""
656
pass
657
658
class ChannelSelect(Item):
659
"""A select menu for choosing channels.
660
661
Attributes
662
----------
663
custom_id: Optional[str]
664
The custom ID of the select menu.
665
placeholder: Optional[str]
666
Placeholder text shown when nothing is selected.
667
min_values: int
668
Minimum number of channels that must be selected.
669
max_values: int
670
Maximum number of channels that can be selected.
671
channel_types: Optional[List[ChannelType]]
672
The types of channels that can be selected.
673
disabled: bool
674
Whether the select menu is disabled.
675
"""
676
pass
677
678
# Entity select examples
679
class EntitySelectView(View):
680
@nextcord.ui.user_select(placeholder="Select users...", min_values=1, max_values=3)
681
async def user_select_callback(self, select: UserSelect, interaction: nextcord.Interaction):
682
users = [user.mention for user in select.values]
683
await interaction.response.send_message(
684
f"Selected users: {', '.join(users)}",
685
ephemeral=True
686
)
687
688
@nextcord.ui.role_select(placeholder="Select roles...", min_values=1, max_values=2)
689
async def role_select_callback(self, select: RoleSelect, interaction: nextcord.Interaction):
690
roles = [role.mention for role in select.values]
691
await interaction.response.send_message(
692
f"Selected roles: {', '.join(roles)}",
693
ephemeral=True
694
)
695
696
@nextcord.ui.channel_select(
697
placeholder="Select channels...",
698
channel_types=[nextcord.ChannelType.text, nextcord.ChannelType.voice]
699
)
700
async def channel_select_callback(self, select: ChannelSelect, interaction: nextcord.Interaction):
701
channels = [channel.mention for channel in select.values]
702
await interaction.response.send_message(
703
f"Selected channels: {', '.join(channels)}",
704
ephemeral=True
705
)
706
707
# Moderation panel example
708
class ModerationView(View):
709
def __init__(self):
710
super().__init__(timeout=300.0) # 5 minute timeout
711
712
@nextcord.ui.user_select(
713
placeholder="Select user to moderate...",
714
min_values=1,
715
max_values=1
716
)
717
async def select_user(self, select: UserSelect, interaction: nextcord.Interaction):
718
user = select.values[0]
719
720
# Create a follow-up view with moderation actions
721
mod_view = UserModerationView(user)
722
723
embed = nextcord.Embed(
724
title="User Moderation",
725
description=f"Selected user: {user.mention}",
726
color=nextcord.Color.orange()
727
)
728
729
await interaction.response.send_message(
730
embed=embed,
731
view=mod_view,
732
ephemeral=True
733
)
734
735
class UserModerationView(View):
736
def __init__(self, user: nextcord.Member):
737
super().__init__(timeout=180.0)
738
self.user = user
739
740
@nextcord.ui.button(label="Timeout", style=nextcord.ButtonStyle.secondary, emoji="⏰")
741
async def timeout_user(self, button: Button, interaction: nextcord.Interaction):
742
# Open modal for timeout duration
743
modal = TimeoutModal(self.user)
744
await interaction.response.send_modal(modal)
745
746
@nextcord.ui.button(label="Kick", style=nextcord.ButtonStyle.danger, emoji="👢")
747
async def kick_user(self, button: Button, interaction: nextcord.Interaction):
748
try:
749
await self.user.kick(reason=f"Kicked by {interaction.user}")
750
await interaction.response.send_message(
751
f"✅ {self.user.mention} has been kicked.",
752
ephemeral=True
753
)
754
except nextcord.Forbidden:
755
await interaction.response.send_message(
756
"❌ I don't have permission to kick this user.",
757
ephemeral=True
758
)
759
760
@nextcord.ui.button(label="Ban", style=nextcord.ButtonStyle.danger, emoji="🔨")
761
async def ban_user(self, button: Button, interaction: nextcord.Interaction):
762
try:
763
await self.user.ban(reason=f"Banned by {interaction.user}")
764
await interaction.response.send_message(
765
f"✅ {self.user.mention} has been banned.",
766
ephemeral=True
767
)
768
except nextcord.Forbidden:
769
await interaction.response.send_message(
770
"❌ I don't have permission to ban this user.",
771
ephemeral=True
772
)
773
```
774
775
## Modals
776
777
Dialog forms for collecting text input from users.
778
779
### Modal Class { .api }
780
781
```python
782
from nextcord.ui import Modal, TextInput
783
from nextcord import TextInputStyle
784
785
class Modal:
786
"""Represents a modal dialog form.
787
788
Modals are popup forms that can collect text input from users.
789
They can contain up to 5 TextInput components.
790
791
Attributes
792
----------
793
title: str
794
The title of the modal.
795
timeout: Optional[float]
796
The timeout for this modal in seconds.
797
children: List[TextInput]
798
The text inputs in this modal.
799
"""
800
801
def __init__(self, *, title: str, timeout: Optional[float] = None):
802
"""Initialize a modal.
803
804
Parameters
805
----------
806
title: str
807
The title displayed at the top of the modal.
808
timeout: Optional[float]
809
The timeout for this modal in seconds.
810
"""
811
...
812
813
def add_item(self, item: TextInput) -> None:
814
"""Add a text input to this modal.
815
816
Parameters
817
----------
818
item: TextInput
819
The text input to add.
820
"""
821
...
822
823
def remove_item(self, item: TextInput) -> None:
824
"""Remove a text input from this modal.
825
826
Parameters
827
----------
828
item: TextInput
829
The text input to remove.
830
"""
831
...
832
833
def clear_items(self) -> None:
834
"""Remove all text inputs from this modal."""
835
...
836
837
async def on_submit(self, interaction: nextcord.Interaction) -> None:
838
"""Called when the modal is submitted.
839
840
This method should be overridden to handle modal submissions.
841
842
Parameters
843
----------
844
interaction: nextcord.Interaction
845
The interaction containing the submitted data.
846
"""
847
...
848
849
async def on_error(
850
self,
851
interaction: nextcord.Interaction,
852
error: Exception
853
) -> None:
854
"""Called when an error occurs in modal handling.
855
856
Parameters
857
----------
858
interaction: nextcord.Interaction
859
The interaction that caused the error.
860
error: Exception
861
The error that occurred.
862
"""
863
...
864
865
async def on_timeout(self) -> None:
866
"""Called when the modal times out."""
867
...
868
869
class TextInput(Item):
870
"""A text input field for modals.
871
872
Attributes
873
----------
874
label: str
875
The label for this text input.
876
style: TextInputStyle
877
The style of the text input (short or paragraph).
878
custom_id: Optional[str]
879
The custom ID of this text input.
880
placeholder: Optional[str]
881
Placeholder text shown when the input is empty.
882
default: Optional[str]
883
Default text to pre-fill the input with.
884
required: bool
885
Whether this input is required.
886
min_length: Optional[int]
887
Minimum length of the input.
888
max_length: Optional[int]
889
Maximum length of the input.
890
value: Optional[str]
891
The current value of the text input (available after submission).
892
"""
893
894
def __init__(
895
self,
896
*,
897
label: str,
898
style: TextInputStyle = TextInputStyle.short,
899
custom_id: Optional[str] = None,
900
placeholder: Optional[str] = None,
901
default: Optional[str] = None,
902
required: bool = True,
903
min_length: Optional[int] = None,
904
max_length: Optional[int] = None,
905
row: Optional[int] = None,
906
):
907
"""Initialize a text input.
908
909
Parameters
910
----------
911
label: str
912
The label displayed above the text input.
913
style: TextInputStyle
914
Whether this is a short (single-line) or paragraph (multi-line) input.
915
custom_id: Optional[str]
916
A custom ID for this text input.
917
placeholder: Optional[str]
918
Placeholder text to show when empty.
919
default: Optional[str]
920
Default text to pre-fill the input.
921
required: bool
922
Whether this input must be filled out.
923
min_length: Optional[int]
924
Minimum character length.
925
max_length: Optional[int]
926
Maximum character length.
927
row: Optional[int]
928
Which row to place this input in.
929
"""
930
...
931
932
class TextInputStyle(Enum):
933
"""Text input styles for modals."""
934
short = 1 # Single line input
935
paragraph = 2 # Multi-line input
936
937
# Basic modal example
938
class FeedbackModal(Modal):
939
def __init__(self):
940
super().__init__(title="Feedback Form", timeout=300.0)
941
942
name = nextcord.ui.TextInput(
943
label="Name",
944
placeholder="Enter your name here...",
945
required=True,
946
max_length=50
947
)
948
949
feedback = nextcord.ui.TextInput(
950
label="Feedback",
951
style=nextcord.TextInputStyle.paragraph,
952
placeholder="Tell us what you think...",
953
required=True,
954
max_length=1000
955
)
956
957
rating = nextcord.ui.TextInput(
958
label="Rating (1-10)",
959
placeholder="Rate from 1 to 10",
960
required=False,
961
max_length=2
962
)
963
964
async def on_submit(self, interaction: nextcord.Interaction):
965
embed = nextcord.Embed(
966
title="Feedback Received",
967
color=nextcord.Color.green()
968
)
969
embed.add_field(name="Name", value=self.name.value, inline=True)
970
embed.add_field(name="Rating", value=self.rating.value or "Not provided", inline=True)
971
embed.add_field(name="Feedback", value=self.feedback.value, inline=False)
972
973
await interaction.response.send_message(
974
"Thank you for your feedback!",
975
embed=embed,
976
ephemeral=True
977
)
978
979
# Button that opens a modal
980
class FeedbackView(View):
981
@nextcord.ui.button(label="Give Feedback", style=nextcord.ButtonStyle.primary, emoji="📝")
982
async def feedback_button(self, button: Button, interaction: nextcord.Interaction):
983
modal = FeedbackModal()
984
await interaction.response.send_modal(modal)
985
986
# Advanced modal with validation
987
class UserRegistrationModal(Modal):
988
def __init__(self):
989
super().__init__(title="User Registration", timeout=600.0)
990
991
username = nextcord.ui.TextInput(
992
label="Username",
993
placeholder="Choose a username...",
994
required=True,
995
min_length=3,
996
max_length=20
997
)
998
999
email = nextcord.ui.TextInput(
1000
label="Email",
1001
placeholder="your.email@example.com",
1002
required=True,
1003
max_length=100
1004
)
1005
1006
age = nextcord.ui.TextInput(
1007
label="Age",
1008
placeholder="Enter your age",
1009
required=True,
1010
max_length=3
1011
)
1012
1013
bio = nextcord.ui.TextInput(
1014
label="Bio (Optional)",
1015
style=nextcord.TextInputStyle.paragraph,
1016
placeholder="Tell us about yourself...",
1017
required=False,
1018
max_length=500
1019
)
1020
1021
async def on_submit(self, interaction: nextcord.Interaction):
1022
# Validate age
1023
try:
1024
age = int(self.age.value)
1025
if age < 13 or age > 120:
1026
await interaction.response.send_message(
1027
"❌ Age must be between 13 and 120.",
1028
ephemeral=True
1029
)
1030
return
1031
except ValueError:
1032
await interaction.response.send_message(
1033
"❌ Please enter a valid age (numbers only).",
1034
ephemeral=True
1035
)
1036
return
1037
1038
# Validate email (basic check)
1039
import re
1040
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
1041
if not re.match(email_pattern, self.email.value):
1042
await interaction.response.send_message(
1043
"❌ Please enter a valid email address.",
1044
ephemeral=True
1045
)
1046
return
1047
1048
# Registration successful
1049
embed = nextcord.Embed(
1050
title="Registration Successful!",
1051
description="Welcome to our community!",
1052
color=nextcord.Color.green()
1053
)
1054
embed.add_field(name="Username", value=self.username.value, inline=True)
1055
embed.add_field(name="Age", value=self.age.value, inline=True)
1056
embed.add_field(name="Email", value=self.email.value, inline=False)
1057
1058
if self.bio.value:
1059
embed.add_field(name="Bio", value=self.bio.value, inline=False)
1060
1061
await interaction.response.send_message(embed=embed, ephemeral=True)
1062
1063
async def on_error(self, interaction: nextcord.Interaction, error: Exception):
1064
await interaction.response.send_message(
1065
"❌ An error occurred during registration. Please try again.",
1066
ephemeral=True
1067
)
1068
print(f"Modal error: {error}")
1069
1070
# Timeout modal example
1071
class TimeoutModal(Modal):
1072
def __init__(self, user: nextcord.Member):
1073
super().__init__(title=f"Timeout {user.display_name}")
1074
self.user = user
1075
1076
duration = nextcord.ui.TextInput(
1077
label="Duration (minutes)",
1078
placeholder="Enter timeout duration in minutes (1-40320)",
1079
required=True,
1080
max_length=5
1081
)
1082
1083
reason = nextcord.ui.TextInput(
1084
label="Reason",
1085
style=nextcord.TextInputStyle.paragraph,
1086
placeholder="Reason for timeout (optional)",
1087
required=False,
1088
max_length=200
1089
)
1090
1091
async def on_submit(self, interaction: nextcord.Interaction):
1092
try:
1093
duration_minutes = int(self.duration.value)
1094
1095
if duration_minutes < 1 or duration_minutes > 40320: # Max 4 weeks
1096
await interaction.response.send_message(
1097
"❌ Duration must be between 1 and 40320 minutes (4 weeks).",
1098
ephemeral=True
1099
)
1100
return
1101
1102
from datetime import datetime, timedelta
1103
until = datetime.utcnow() + timedelta(minutes=duration_minutes)
1104
reason = self.reason.value or f"Timed out by {interaction.user}"
1105
1106
await self.user.timeout(until=until, reason=reason)
1107
1108
embed = nextcord.Embed(
1109
title="User Timed Out",
1110
description=f"{self.user.mention} has been timed out for {duration_minutes} minutes.",
1111
color=nextcord.Color.orange()
1112
)
1113
embed.add_field(name="Reason", value=reason, inline=False)
1114
embed.add_field(
1115
name="Until",
1116
value=until.strftime("%Y-%m-%d %H:%M UTC"),
1117
inline=True
1118
)
1119
1120
await interaction.response.send_message(embed=embed, ephemeral=True)
1121
1122
except ValueError:
1123
await interaction.response.send_message(
1124
"❌ Please enter a valid number for duration.",
1125
ephemeral=True
1126
)
1127
except nextcord.Forbidden:
1128
await interaction.response.send_message(
1129
"❌ I don't have permission to timeout this user.",
1130
ephemeral=True
1131
)
1132
except Exception as e:
1133
await interaction.response.send_message(
1134
"❌ An error occurred while timing out the user.",
1135
ephemeral=True
1136
)
1137
```
1138
1139
## Advanced UI Examples
1140
1141
Complex UI implementations combining multiple components.
1142
1143
### Multi-Page Interface { .api }
1144
1145
```python
1146
class PaginatorView(View):
1147
"""A paginated interface for displaying multiple pages of content."""
1148
1149
def __init__(self, pages: List[nextcord.Embed]):
1150
super().__init__(timeout=300.0)
1151
self.pages = pages
1152
self.current_page = 0
1153
self.update_buttons()
1154
1155
def update_buttons(self):
1156
"""Update button states based on current page."""
1157
self.first_page.disabled = self.current_page == 0
1158
self.previous_page.disabled = self.current_page == 0
1159
self.next_page.disabled = self.current_page == len(self.pages) - 1
1160
self.last_page.disabled = self.current_page == len(self.pages) - 1
1161
1162
@nextcord.ui.button(label="<<", style=nextcord.ButtonStyle.secondary)
1163
async def first_page(self, button: Button, interaction: nextcord.Interaction):
1164
self.current_page = 0
1165
self.update_buttons()
1166
1167
embed = self.pages[self.current_page]
1168
embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
1169
1170
await interaction.response.edit_message(embed=embed, view=self)
1171
1172
@nextcord.ui.button(label="<", style=nextcord.ButtonStyle.primary)
1173
async def previous_page(self, button: Button, interaction: nextcord.Interaction):
1174
self.current_page -= 1
1175
self.update_buttons()
1176
1177
embed = self.pages[self.current_page]
1178
embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
1179
1180
await interaction.response.edit_message(embed=embed, view=self)
1181
1182
@nextcord.ui.button(label=">", style=nextcord.ButtonStyle.primary)
1183
async def next_page(self, button: Button, interaction: nextcord.Interaction):
1184
self.current_page += 1
1185
self.update_buttons()
1186
1187
embed = self.pages[self.current_page]
1188
embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
1189
1190
await interaction.response.edit_message(embed=embed, view=self)
1191
1192
@nextcord.ui.button(label=">>", style=nextcord.ButtonStyle.secondary)
1193
async def last_page(self, button: Button, interaction: nextcord.Interaction):
1194
self.current_page = len(self.pages) - 1
1195
self.update_buttons()
1196
1197
embed = self.pages[self.current_page]
1198
embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
1199
1200
await interaction.response.edit_message(embed=embed, view=self)
1201
1202
@nextcord.ui.button(label="Jump to Page", style=nextcord.ButtonStyle.secondary, emoji="🔢")
1203
async def jump_to_page(self, button: Button, interaction: nextcord.Interaction):
1204
modal = JumpToPageModal(self)
1205
await interaction.response.send_modal(modal)
1206
1207
async def on_timeout(self):
1208
# Disable all buttons when view times out
1209
for child in self.children:
1210
child.disabled = True
1211
1212
class JumpToPageModal(Modal):
1213
def __init__(self, paginator: PaginatorView):
1214
super().__init__(title="Jump to Page")
1215
self.paginator = paginator
1216
1217
page_number = nextcord.ui.TextInput(
1218
label=f"Page Number (1-{len(paginator.pages)})",
1219
placeholder="Enter page number...",
1220
required=True,
1221
max_length=3
1222
)
1223
1224
async def on_submit(self, interaction: nextcord.Interaction):
1225
try:
1226
page_num = int(self.page_number.value)
1227
if 1 <= page_num <= len(self.paginator.pages):
1228
self.paginator.current_page = page_num - 1
1229
self.paginator.update_buttons()
1230
1231
embed = self.paginator.pages[self.paginator.current_page]
1232
embed.set_footer(text=f"Page {self.paginator.current_page + 1} of {len(self.paginator.pages)}")
1233
1234
await interaction.response.edit_message(embed=embed, view=self.paginator)
1235
else:
1236
await interaction.response.send_message(
1237
f"❌ Page number must be between 1 and {len(self.paginator.pages)}.",
1238
ephemeral=True
1239
)
1240
except ValueError:
1241
await interaction.response.send_message(
1242
"❌ Please enter a valid page number.",
1243
ephemeral=True
1244
)
1245
1246
# Usage example
1247
@bot.slash_command(description="Show paginated help")
1248
async def help_pages(interaction: nextcord.Interaction):
1249
# Create multiple embed pages
1250
pages = []
1251
1252
# Page 1 - Commands
1253
embed1 = nextcord.Embed(title="Bot Help - Commands", color=nextcord.Color.blue())
1254
embed1.add_field(name="/help", value="Show this help message", inline=False)
1255
embed1.add_field(name="/info", value="Get bot information", inline=False)
1256
embed1.add_field(name="/ping", value="Check bot latency", inline=False)
1257
pages.append(embed1)
1258
1259
# Page 2 - Features
1260
embed2 = nextcord.Embed(title="Bot Help - Features", color=nextcord.Color.green())
1261
embed2.add_field(name="Moderation", value="Timeout, kick, ban users", inline=False)
1262
embed2.add_field(name="Utility", value="Server info, user info", inline=False)
1263
embed2.add_field(name="Fun", value="Games and entertainment", inline=False)
1264
pages.append(embed2)
1265
1266
# Page 3 - Support
1267
embed3 = nextcord.Embed(title="Bot Help - Support", color=nextcord.Color.red())
1268
embed3.add_field(name="Support Server", value="[Join Here](https://discord.gg/example)", inline=False)
1269
embed3.add_field(name="Bug Reports", value="Use `/report` command", inline=False)
1270
embed3.add_field(name="Feature Requests", value="Use `/suggest` command", inline=False)
1271
pages.append(embed3)
1272
1273
# Set initial page footer
1274
pages[0].set_footer(text=f"Page 1 of {len(pages)}")
1275
1276
view = PaginatorView(pages)
1277
await interaction.response.send_message(embed=pages[0], view=view)
1278
```
1279
1280
### Complex Configuration Panel { .api }
1281
1282
```python
1283
class ServerConfigView(View):
1284
"""A comprehensive server configuration panel."""
1285
1286
def __init__(self, guild: nextcord.Guild):
1287
super().__init__(timeout=600.0) # 10 minute timeout
1288
self.guild = guild
1289
self.config = self.load_config(guild.id) # Load from database/file
1290
1291
def load_config(self, guild_id: int) -> dict:
1292
"""Load server configuration (implement your storage here)."""
1293
return {
1294
"welcome_channel": None,
1295
"log_channel": None,
1296
"auto_role": None,
1297
"prefix": "!",
1298
"moderation": True,
1299
"auto_mod": False
1300
}
1301
1302
def save_config(self) -> None:
1303
"""Save server configuration (implement your storage here)."""
1304
# Save self.config to database/file
1305
pass
1306
1307
@nextcord.ui.select(
1308
placeholder="Configure server settings...",
1309
options=[
1310
nextcord.SelectOption(
1311
label="Welcome System",
1312
description="Set up welcome messages and channel",
1313
emoji="👋",
1314
value="welcome"
1315
),
1316
nextcord.SelectOption(
1317
label="Logging",
1318
description="Configure audit log channel",
1319
emoji="📝",
1320
value="logging"
1321
),
1322
nextcord.SelectOption(
1323
label="Auto Role",
1324
description="Set role to assign to new members",
1325
emoji="👤",
1326
value="auto_role"
1327
),
1328
nextcord.SelectOption(
1329
label="Moderation",
1330
description="Toggle moderation features",
1331
emoji="🔨",
1332
value="moderation"
1333
),
1334
]
1335
)
1336
async def config_select(self, select: Select, interaction: nextcord.Interaction):
1337
selection = select.values[0]
1338
1339
if selection == "welcome":
1340
view = WelcomeConfigView(self.guild, self.config)
1341
elif selection == "logging":
1342
view = LoggingConfigView(self.guild, self.config)
1343
elif selection == "auto_role":
1344
view = AutoRoleConfigView(self.guild, self.config)
1345
elif selection == "moderation":
1346
view = ModerationConfigView(self.guild, self.config)
1347
1348
embed = nextcord.Embed(
1349
title=f"{selection.replace('_', ' ').title()} Configuration",
1350
description="Use the buttons below to configure this feature.",
1351
color=nextcord.Color.blue()
1352
)
1353
1354
await interaction.response.edit_message(embed=embed, view=view)
1355
1356
@nextcord.ui.button(label="Save & Exit", style=nextcord.ButtonStyle.success, emoji="💾")
1357
async def save_and_exit(self, button: Button, interaction: nextcord.Interaction):
1358
self.save_config()
1359
1360
embed = nextcord.Embed(
1361
title="Configuration Saved",
1362
description="All changes have been saved successfully!",
1363
color=nextcord.Color.green()
1364
)
1365
1366
await interaction.response.edit_message(embed=embed, view=None)
1367
self.stop()
1368
1369
class WelcomeConfigView(View):
1370
def __init__(self, guild: nextcord.Guild, config: dict):
1371
super().__init__(timeout=300.0)
1372
self.guild = guild
1373
self.config = config
1374
1375
@nextcord.ui.channel_select(
1376
placeholder="Select welcome channel...",
1377
channel_types=[nextcord.ChannelType.text]
1378
)
1379
async def welcome_channel_select(self, select: ChannelSelect, interaction: nextcord.Interaction):
1380
channel = select.values[0]
1381
self.config["welcome_channel"] = channel.id
1382
1383
await interaction.response.send_message(
1384
f"✅ Welcome channel set to {channel.mention}",
1385
ephemeral=True
1386
)
1387
1388
@nextcord.ui.button(label="Set Welcome Message", style=nextcord.ButtonStyle.primary)
1389
async def set_welcome_message(self, button: Button, interaction: nextcord.Interaction):
1390
modal = WelcomeMessageModal(self.config)
1391
await interaction.response.send_modal(modal)
1392
1393
@nextcord.ui.button(label="Back to Main", style=nextcord.ButtonStyle.secondary)
1394
async def back_to_main(self, button: Button, interaction: nextcord.Interaction):
1395
main_view = ServerConfigView(self.guild)
1396
main_view.config = self.config # Preserve changes
1397
1398
embed = nextcord.Embed(
1399
title="Server Configuration",
1400
description="Choose a category to configure:",
1401
color=nextcord.Color.blue()
1402
)
1403
1404
await interaction.response.edit_message(embed=embed, view=main_view)
1405
1406
class WelcomeMessageModal(Modal):
1407
def __init__(self, config: dict):
1408
super().__init__(title="Set Welcome Message")
1409
self.config = config
1410
1411
message = nextcord.ui.TextInput(
1412
label="Welcome Message",
1413
style=nextcord.TextInputStyle.paragraph,
1414
placeholder="Welcome {user} to {server}! Available: {user}, {server}, {mention}",
1415
default=config.get("welcome_message", ""),
1416
max_length=1000
1417
)
1418
1419
async def on_submit(self, interaction: nextcord.Interaction):
1420
self.config["welcome_message"] = self.message.value
1421
1422
await interaction.response.send_message(
1423
"✅ Welcome message updated!",
1424
ephemeral=True
1425
)
1426
```
1427
1428
This comprehensive documentation covers all major aspects of nextcord's UI framework, providing developers with the tools to create rich, interactive Discord bot interfaces using views, buttons, select menus, and modals.