or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-commands.mdchannels.mdclient.mdcommands.mderrors.mdevents.mdguild.mdindex.mdmessages.mdpermissions.mdtasks.mdui.mdusers.mdutilities.mdvoice.mdwebhooks.md

ui.mddocs/

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.