A Python wrapper for the Discord API forked from discord.py
—
Interactive Discord UI components including views, buttons, select menus, modals, and text inputs for rich user interactions.
Container classes for UI components with interaction handling and lifecycle management.
import nextcord
from nextcord.ui import View, Item, Button, Select, Modal
from typing import Optional, List, Any, Callable
class View:
"""Represents a UI view with interactive components.
A view is a collection of UI components that can be attached to messages
to create interactive interfaces.
Attributes
----------
timeout: Optional[float]
The timeout duration in seconds. None means no timeout.
children: List[Item]
The list of UI components in this view.
"""
def __init__(self, *, timeout: Optional[float] = 180.0):
"""Initialize a new view.
Parameters
----------
timeout: Optional[float]
The timeout for this view in seconds. Defaults to 180 seconds (3 minutes).
Set to None for no timeout.
"""
...
def add_item(self, item: Item) -> Self:
"""Add an item to this view.
Parameters
----------
item: Item
The UI component to add to this view.
Returns
-------
Self
The view instance for method chaining.
"""
...
def remove_item(self, item: Item) -> Self:
"""Remove an item from this view.
Parameters
----------
item: Item
The UI component to remove from this view.
Returns
-------
Self
The view instance for method chaining.
"""
...
def clear_items(self) -> Self:
"""Remove all items from this view.
Returns
-------
Self
The view instance for method chaining.
"""
...
def stop(self) -> None:
"""Stop listening for interactions on this view."""
...
def is_finished(self) -> bool:
"""bool: Whether this view has finished running."""
...
def is_persistent(self) -> bool:
"""bool: Whether this view is persistent across bot restarts."""
...
async def wait(self) -> bool:
"""Wait until the view finishes running.
Returns
-------
bool
True if the view finished due to timeout, False otherwise.
"""
...
# Callback methods that can be overridden
async def on_timeout(self) -> None:
"""Called when the view times out."""
...
async def on_error(
self,
interaction: nextcord.Interaction,
error: Exception,
item: Item
) -> None:
"""Called when an error occurs in a view interaction.
Parameters
----------
interaction: nextcord.Interaction
The interaction that caused the error.
error: Exception
The error that occurred.
item: Item
The UI component that caused the error.
"""
...
async def interaction_check(self, interaction: nextcord.Interaction) -> bool:
"""Check if an interaction should be processed by this view.
Parameters
----------
interaction: nextcord.Interaction
The interaction to check.
Returns
-------
bool
True if the interaction should be processed, False otherwise.
"""
return True
# Basic view example
class MyView(View):
def __init__(self):
super().__init__(timeout=60.0) # 1 minute timeout
@nextcord.ui.button(label="Click Me!", style=nextcord.ButtonStyle.primary)
async def click_me(self, button: Button, interaction: nextcord.Interaction):
await interaction.response.send_message("Button clicked!", ephemeral=True)
async def on_timeout(self):
# Disable all components when the view times out
for child in self.children:
child.disabled = True
# You would typically edit the message here to show disabled state
# This requires keeping a reference to the message
# Usage
@bot.slash_command(description="Show a view with a button")
async def show_view(interaction: nextcord.Interaction):
view = MyView()
await interaction.response.send_message("Here's a button:", view=view)class PersistentView(View):
"""A persistent view that survives bot restarts.
Persistent views must be added to the bot during startup
and use custom_id for their components.
"""
def __init__(self):
super().__init__(timeout=None) # Persistent views don't timeout
@nextcord.ui.button(
label="Persistent Button",
style=nextcord.ButtonStyle.secondary,
custom_id="persistent:button:1" # Must have custom_id
)
async def persistent_button(self, button: Button, interaction: nextcord.Interaction):
await interaction.response.send_message(
"This button works even after bot restart!",
ephemeral=True
)
# Add persistent views during bot startup
@bot.event
async def on_ready():
print(f'{bot.user} has connected to Discord!')
# Add persistent views
bot.add_view(PersistentView())
# Example: Role assignment view
class RoleView(View):
def __init__(self):
super().__init__(timeout=None)
@nextcord.ui.button(
label="Get Member Role",
emoji="👤",
style=nextcord.ButtonStyle.green,
custom_id="role:member"
)
async def member_role(self, button: Button, interaction: nextcord.Interaction):
role = nextcord.utils.get(interaction.guild.roles, name="Member")
if role:
if role in interaction.user.roles:
await interaction.user.remove_roles(role)
await interaction.response.send_message("Member role removed!", ephemeral=True)
else:
await interaction.user.add_roles(role)
await interaction.response.send_message("Member role added!", ephemeral=True)
else:
await interaction.response.send_message("Member role not found!", ephemeral=True)
@nextcord.ui.button(
label="Get Notifications",
emoji="🔔",
style=nextcord.ButtonStyle.blurple,
custom_id="role:notifications"
)
async def notifications_role(self, button: Button, interaction: nextcord.Interaction):
role = nextcord.utils.get(interaction.guild.roles, name="Notifications")
if role:
if role in interaction.user.roles:
await interaction.user.remove_roles(role)
await interaction.response.send_message("Notifications role removed!", ephemeral=True)
else:
await interaction.user.add_roles(role)
await interaction.response.send_message("Notifications role added!", ephemeral=True)
else:
await interaction.response.send_message("Notifications role not found!", ephemeral=True)Clickable buttons with various styles and callback handling.
class Button(Item):
"""Represents a button component.
Attributes
----------
style: ButtonStyle
The style of the button.
custom_id: Optional[str]
The custom ID of the button.
url: Optional[str]
The URL this button links to (for link buttons).
disabled: bool
Whether the button is disabled.
label: Optional[str]
The label text displayed on the button.
emoji: Optional[Union[str, Emoji, PartialEmoji]]
The emoji displayed on the button.
row: Optional[int]
The row this button should be placed in (0-4).
"""
def __init__(
self,
*,
style: ButtonStyle = ButtonStyle.secondary,
label: Optional[str] = None,
disabled: bool = False,
custom_id: Optional[str] = None,
url: Optional[str] = None,
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
row: Optional[int] = None,
):
"""Initialize a button.
Parameters
----------
style: ButtonStyle
The visual style of the button.
label: Optional[str]
The text displayed on the button.
disabled: bool
Whether the button is disabled.
custom_id: Optional[str]
A custom ID for the button (required for non-link buttons).
url: Optional[str]
URL for link-style buttons.
emoji: Optional[Union[str, Emoji, PartialEmoji]]
Emoji to display on the button.
row: Optional[int]
Which row to place the button in (0-4).
"""
...
async def callback(self, interaction: nextcord.Interaction) -> None:
"""The callback function called when the button is pressed.
This method should be overridden in subclasses or set via decorator.
Parameters
----------
interaction: nextcord.Interaction
The interaction that triggered this button press.
"""
...
# Button styles
class ButtonStyle(Enum):
primary = 1 # Blurple
secondary = 2 # Grey
success = 3 # Green
danger = 4 # Red
link = 5 # Grey with link
# Aliases
blurple = 1
grey = 2
gray = 2
green = 3
red = 4
url = 5
# Button decorator usage
class ButtonView(View):
@nextcord.ui.button(label="Primary", style=nextcord.ButtonStyle.primary)
async def primary_button(self, button: Button, interaction: nextcord.Interaction):
await interaction.response.send_message("Primary button pressed!", ephemeral=True)
@nextcord.ui.button(label="Success", style=nextcord.ButtonStyle.success, emoji="✅")
async def success_button(self, button: Button, interaction: nextcord.Interaction):
await interaction.response.send_message("Success button pressed!", ephemeral=True)
@nextcord.ui.button(label="Danger", style=nextcord.ButtonStyle.danger, emoji="❌")
async def danger_button(self, button: Button, interaction: nextcord.Interaction):
await interaction.response.send_message("Danger button pressed!", ephemeral=True)
@nextcord.ui.button(label="Visit GitHub", style=nextcord.ButtonStyle.link, url="https://github.com")
async def link_button(self, button: Button, interaction: nextcord.Interaction):
# This callback will never be called for link buttons
pass
# Dynamic button creation
class DynamicButtonView(View):
def __init__(self):
super().__init__()
# Add buttons dynamically
for i in range(3):
button = Button(
label=f"Button {i+1}",
style=nextcord.ButtonStyle.secondary,
custom_id=f"dynamic_button_{i}"
)
button.callback = self.dynamic_button_callback
self.add_item(button)
async def dynamic_button_callback(self, interaction: nextcord.Interaction):
# Get which button was pressed from custom_id
button_num = interaction.data["custom_id"].split("_")[-1]
await interaction.response.send_message(f"Dynamic button {button_num} pressed!", ephemeral=True)
# Button with state management
class CounterView(View):
def __init__(self):
super().__init__()
self.counter = 0
self.update_button_label()
def update_button_label(self):
# Find and update the counter button
for child in self.children:
if hasattr(child, 'custom_id') and child.custom_id == "counter":
child.label = f"Count: {self.counter}"
break
@nextcord.ui.button(label="Count: 0", style=nextcord.ButtonStyle.primary, custom_id="counter")
async def counter_button(self, button: Button, interaction: nextcord.Interaction):
self.counter += 1
self.update_button_label()
# Update the message with the new view
await interaction.response.edit_message(view=self)
@nextcord.ui.button(label="Reset", style=nextcord.ButtonStyle.secondary)
async def reset_button(self, button: Button, interaction: nextcord.Interaction):
self.counter = 0
self.update_button_label()
await interaction.response.edit_message(view=self)Dropdown select menus for choosing from predefined options.
from nextcord.ui import Select, UserSelect, RoleSelect, MentionableSelect, ChannelSelect
from nextcord import SelectOption, ChannelType
class Select(Item):
"""String-based select menu with custom options.
Attributes
----------
custom_id: Optional[str]
The custom ID of the select menu.
placeholder: Optional[str]
Placeholder text shown when nothing is selected.
min_values: int
Minimum number of options that must be selected.
max_values: int
Maximum number of options that can be selected.
options: List[SelectOption]
The list of options available in this select menu.
disabled: bool
Whether the select menu is disabled.
row: Optional[int]
The row this select menu should be placed in (0-4).
"""
def __init__(
self,
*,
custom_id: Optional[str] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
options: List[SelectOption] = None,
disabled: bool = False,
row: Optional[int] = None,
):
"""Initialize a select menu.
Parameters
----------
custom_id: Optional[str]
A custom ID for the select menu.
placeholder: Optional[str]
Placeholder text to display.
min_values: int
Minimum number of selections required.
max_values: int
Maximum number of selections allowed.
options: List[SelectOption]
List of options for the select menu.
disabled: bool
Whether the select menu is disabled.
row: Optional[int]
Which row to place the select menu in (0-4).
"""
...
def add_option(
self,
*,
label: str,
value: str,
description: Optional[str] = None,
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
default: bool = False,
) -> None:
"""Add an option to this select menu.
Parameters
----------
label: str
The user-facing name of the option.
value: str
The developer-defined value of the option.
description: Optional[str]
An additional description of the option.
emoji: Optional[Union[str, Emoji, PartialEmoji]]
An emoji for the option.
default: bool
Whether this option is selected by default.
"""
...
async def callback(self, interaction: nextcord.Interaction) -> None:
"""Called when an option is selected.
Parameters
----------
interaction: nextcord.Interaction
The interaction containing the selected values.
"""
...
class SelectOption:
"""Represents an option in a select menu.
Attributes
----------
label: str
The user-facing name of the option.
value: str
The developer-defined value of the option.
description: Optional[str]
An additional description of the option.
emoji: Optional[Union[str, Emoji, PartialEmoji]]
An emoji that appears on the option.
default: bool
Whether this option is selected by default.
"""
def __init__(
self,
*,
label: str,
value: str,
description: Optional[str] = None,
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
default: bool = False,
):
"""Create a select option.
Parameters
----------
label: str
The display name for this option.
value: str
The value returned when this option is selected.
description: Optional[str]
Additional description text for this option.
emoji: Optional[Union[str, Emoji, PartialEmoji]]
Emoji to display with this option.
default: bool
Whether this option is selected by default.
"""
...
# Basic select menu example
class ColorSelectView(View):
@nextcord.ui.select(
placeholder="Choose your favorite color...",
min_values=1,
max_values=1,
options=[
nextcord.SelectOption(
label="Red",
description="The color of fire",
emoji="🔴",
value="red"
),
nextcord.SelectOption(
label="Blue",
description="The color of water",
emoji="🔵",
value="blue"
),
nextcord.SelectOption(
label="Green",
description="The color of nature",
emoji="🟢",
value="green"
),
]
)
async def select_callback(self, select: Select, interaction: nextcord.Interaction):
selected_color = select.values[0]
await interaction.response.send_message(
f"You selected {selected_color}!",
ephemeral=True
)
# Multi-select example
class HobbiesSelectView(View):
@nextcord.ui.select(
placeholder="Select your hobbies (up to 3)...",
min_values=0,
max_values=3,
options=[
nextcord.SelectOption(label="Gaming", value="gaming", emoji="🎮"),
nextcord.SelectOption(label="Reading", value="reading", emoji="📚"),
nextcord.SelectOption(label="Music", value="music", emoji="🎵"),
nextcord.SelectOption(label="Sports", value="sports", emoji="⚽"),
nextcord.SelectOption(label="Art", value="art", emoji="🎨"),
nextcord.SelectOption(label="Cooking", value="cooking", emoji="👨🍳"),
]
)
async def hobbies_callback(self, select: Select, interaction: nextcord.Interaction):
if select.values:
hobbies = ", ".join(select.values)
await interaction.response.send_message(
f"Your selected hobbies: {hobbies}",
ephemeral=True
)
else:
await interaction.response.send_message(
"You didn't select any hobbies.",
ephemeral=True
)# User select menu
class UserSelect(Item):
"""A select menu for choosing users/members.
Attributes
----------
custom_id: Optional[str]
The custom ID of the select menu.
placeholder: Optional[str]
Placeholder text shown when nothing is selected.
min_values: int
Minimum number of users that must be selected.
max_values: int
Maximum number of users that can be selected.
disabled: bool
Whether the select menu is disabled.
"""
pass
class RoleSelect(Item):
"""A select menu for choosing roles.
Attributes
----------
custom_id: Optional[str]
The custom ID of the select menu.
placeholder: Optional[str]
Placeholder text shown when nothing is selected.
min_values: int
Minimum number of roles that must be selected.
max_values: int
Maximum number of roles that can be selected.
disabled: bool
Whether the select menu is disabled.
"""
pass
class MentionableSelect(Item):
"""A select menu for choosing mentionable entities (users and roles).
Attributes
----------
custom_id: Optional[str]
The custom ID of the select menu.
placeholder: Optional[str]
Placeholder text shown when nothing is selected.
min_values: int
Minimum number of entities that must be selected.
max_values: int
Maximum number of entities that can be selected.
disabled: bool
Whether the select menu is disabled.
"""
pass
class ChannelSelect(Item):
"""A select menu for choosing channels.
Attributes
----------
custom_id: Optional[str]
The custom ID of the select menu.
placeholder: Optional[str]
Placeholder text shown when nothing is selected.
min_values: int
Minimum number of channels that must be selected.
max_values: int
Maximum number of channels that can be selected.
channel_types: Optional[List[ChannelType]]
The types of channels that can be selected.
disabled: bool
Whether the select menu is disabled.
"""
pass
# Entity select examples
class EntitySelectView(View):
@nextcord.ui.user_select(placeholder="Select users...", min_values=1, max_values=3)
async def user_select_callback(self, select: UserSelect, interaction: nextcord.Interaction):
users = [user.mention for user in select.values]
await interaction.response.send_message(
f"Selected users: {', '.join(users)}",
ephemeral=True
)
@nextcord.ui.role_select(placeholder="Select roles...", min_values=1, max_values=2)
async def role_select_callback(self, select: RoleSelect, interaction: nextcord.Interaction):
roles = [role.mention for role in select.values]
await interaction.response.send_message(
f"Selected roles: {', '.join(roles)}",
ephemeral=True
)
@nextcord.ui.channel_select(
placeholder="Select channels...",
channel_types=[nextcord.ChannelType.text, nextcord.ChannelType.voice]
)
async def channel_select_callback(self, select: ChannelSelect, interaction: nextcord.Interaction):
channels = [channel.mention for channel in select.values]
await interaction.response.send_message(
f"Selected channels: {', '.join(channels)}",
ephemeral=True
)
# Moderation panel example
class ModerationView(View):
def __init__(self):
super().__init__(timeout=300.0) # 5 minute timeout
@nextcord.ui.user_select(
placeholder="Select user to moderate...",
min_values=1,
max_values=1
)
async def select_user(self, select: UserSelect, interaction: nextcord.Interaction):
user = select.values[0]
# Create a follow-up view with moderation actions
mod_view = UserModerationView(user)
embed = nextcord.Embed(
title="User Moderation",
description=f"Selected user: {user.mention}",
color=nextcord.Color.orange()
)
await interaction.response.send_message(
embed=embed,
view=mod_view,
ephemeral=True
)
class UserModerationView(View):
def __init__(self, user: nextcord.Member):
super().__init__(timeout=180.0)
self.user = user
@nextcord.ui.button(label="Timeout", style=nextcord.ButtonStyle.secondary, emoji="⏰")
async def timeout_user(self, button: Button, interaction: nextcord.Interaction):
# Open modal for timeout duration
modal = TimeoutModal(self.user)
await interaction.response.send_modal(modal)
@nextcord.ui.button(label="Kick", style=nextcord.ButtonStyle.danger, emoji="👢")
async def kick_user(self, button: Button, interaction: nextcord.Interaction):
try:
await self.user.kick(reason=f"Kicked by {interaction.user}")
await interaction.response.send_message(
f"✅ {self.user.mention} has been kicked.",
ephemeral=True
)
except nextcord.Forbidden:
await interaction.response.send_message(
"❌ I don't have permission to kick this user.",
ephemeral=True
)
@nextcord.ui.button(label="Ban", style=nextcord.ButtonStyle.danger, emoji="🔨")
async def ban_user(self, button: Button, interaction: nextcord.Interaction):
try:
await self.user.ban(reason=f"Banned by {interaction.user}")
await interaction.response.send_message(
f"✅ {self.user.mention} has been banned.",
ephemeral=True
)
except nextcord.Forbidden:
await interaction.response.send_message(
"❌ I don't have permission to ban this user.",
ephemeral=True
)Dialog forms for collecting text input from users.
from nextcord.ui import Modal, TextInput
from nextcord import TextInputStyle
class Modal:
"""Represents a modal dialog form.
Modals are popup forms that can collect text input from users.
They can contain up to 5 TextInput components.
Attributes
----------
title: str
The title of the modal.
timeout: Optional[float]
The timeout for this modal in seconds.
children: List[TextInput]
The text inputs in this modal.
"""
def __init__(self, *, title: str, timeout: Optional[float] = None):
"""Initialize a modal.
Parameters
----------
title: str
The title displayed at the top of the modal.
timeout: Optional[float]
The timeout for this modal in seconds.
"""
...
def add_item(self, item: TextInput) -> None:
"""Add a text input to this modal.
Parameters
----------
item: TextInput
The text input to add.
"""
...
def remove_item(self, item: TextInput) -> None:
"""Remove a text input from this modal.
Parameters
----------
item: TextInput
The text input to remove.
"""
...
def clear_items(self) -> None:
"""Remove all text inputs from this modal."""
...
async def on_submit(self, interaction: nextcord.Interaction) -> None:
"""Called when the modal is submitted.
This method should be overridden to handle modal submissions.
Parameters
----------
interaction: nextcord.Interaction
The interaction containing the submitted data.
"""
...
async def on_error(
self,
interaction: nextcord.Interaction,
error: Exception
) -> None:
"""Called when an error occurs in modal handling.
Parameters
----------
interaction: nextcord.Interaction
The interaction that caused the error.
error: Exception
The error that occurred.
"""
...
async def on_timeout(self) -> None:
"""Called when the modal times out."""
...
class TextInput(Item):
"""A text input field for modals.
Attributes
----------
label: str
The label for this text input.
style: TextInputStyle
The style of the text input (short or paragraph).
custom_id: Optional[str]
The custom ID of this text input.
placeholder: Optional[str]
Placeholder text shown when the input is empty.
default: Optional[str]
Default text to pre-fill the input with.
required: bool
Whether this input is required.
min_length: Optional[int]
Minimum length of the input.
max_length: Optional[int]
Maximum length of the input.
value: Optional[str]
The current value of the text input (available after submission).
"""
def __init__(
self,
*,
label: str,
style: TextInputStyle = TextInputStyle.short,
custom_id: Optional[str] = None,
placeholder: Optional[str] = None,
default: Optional[str] = None,
required: bool = True,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
row: Optional[int] = None,
):
"""Initialize a text input.
Parameters
----------
label: str
The label displayed above the text input.
style: TextInputStyle
Whether this is a short (single-line) or paragraph (multi-line) input.
custom_id: Optional[str]
A custom ID for this text input.
placeholder: Optional[str]
Placeholder text to show when empty.
default: Optional[str]
Default text to pre-fill the input.
required: bool
Whether this input must be filled out.
min_length: Optional[int]
Minimum character length.
max_length: Optional[int]
Maximum character length.
row: Optional[int]
Which row to place this input in.
"""
...
class TextInputStyle(Enum):
"""Text input styles for modals."""
short = 1 # Single line input
paragraph = 2 # Multi-line input
# Basic modal example
class FeedbackModal(Modal):
def __init__(self):
super().__init__(title="Feedback Form", timeout=300.0)
name = nextcord.ui.TextInput(
label="Name",
placeholder="Enter your name here...",
required=True,
max_length=50
)
feedback = nextcord.ui.TextInput(
label="Feedback",
style=nextcord.TextInputStyle.paragraph,
placeholder="Tell us what you think...",
required=True,
max_length=1000
)
rating = nextcord.ui.TextInput(
label="Rating (1-10)",
placeholder="Rate from 1 to 10",
required=False,
max_length=2
)
async def on_submit(self, interaction: nextcord.Interaction):
embed = nextcord.Embed(
title="Feedback Received",
color=nextcord.Color.green()
)
embed.add_field(name="Name", value=self.name.value, inline=True)
embed.add_field(name="Rating", value=self.rating.value or "Not provided", inline=True)
embed.add_field(name="Feedback", value=self.feedback.value, inline=False)
await interaction.response.send_message(
"Thank you for your feedback!",
embed=embed,
ephemeral=True
)
# Button that opens a modal
class FeedbackView(View):
@nextcord.ui.button(label="Give Feedback", style=nextcord.ButtonStyle.primary, emoji="📝")
async def feedback_button(self, button: Button, interaction: nextcord.Interaction):
modal = FeedbackModal()
await interaction.response.send_modal(modal)
# Advanced modal with validation
class UserRegistrationModal(Modal):
def __init__(self):
super().__init__(title="User Registration", timeout=600.0)
username = nextcord.ui.TextInput(
label="Username",
placeholder="Choose a username...",
required=True,
min_length=3,
max_length=20
)
email = nextcord.ui.TextInput(
label="Email",
placeholder="your.email@example.com",
required=True,
max_length=100
)
age = nextcord.ui.TextInput(
label="Age",
placeholder="Enter your age",
required=True,
max_length=3
)
bio = nextcord.ui.TextInput(
label="Bio (Optional)",
style=nextcord.TextInputStyle.paragraph,
placeholder="Tell us about yourself...",
required=False,
max_length=500
)
async def on_submit(self, interaction: nextcord.Interaction):
# Validate age
try:
age = int(self.age.value)
if age < 13 or age > 120:
await interaction.response.send_message(
"❌ Age must be between 13 and 120.",
ephemeral=True
)
return
except ValueError:
await interaction.response.send_message(
"❌ Please enter a valid age (numbers only).",
ephemeral=True
)
return
# Validate email (basic check)
import re
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, self.email.value):
await interaction.response.send_message(
"❌ Please enter a valid email address.",
ephemeral=True
)
return
# Registration successful
embed = nextcord.Embed(
title="Registration Successful!",
description="Welcome to our community!",
color=nextcord.Color.green()
)
embed.add_field(name="Username", value=self.username.value, inline=True)
embed.add_field(name="Age", value=self.age.value, inline=True)
embed.add_field(name="Email", value=self.email.value, inline=False)
if self.bio.value:
embed.add_field(name="Bio", value=self.bio.value, inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
async def on_error(self, interaction: nextcord.Interaction, error: Exception):
await interaction.response.send_message(
"❌ An error occurred during registration. Please try again.",
ephemeral=True
)
print(f"Modal error: {error}")
# Timeout modal example
class TimeoutModal(Modal):
def __init__(self, user: nextcord.Member):
super().__init__(title=f"Timeout {user.display_name}")
self.user = user
duration = nextcord.ui.TextInput(
label="Duration (minutes)",
placeholder="Enter timeout duration in minutes (1-40320)",
required=True,
max_length=5
)
reason = nextcord.ui.TextInput(
label="Reason",
style=nextcord.TextInputStyle.paragraph,
placeholder="Reason for timeout (optional)",
required=False,
max_length=200
)
async def on_submit(self, interaction: nextcord.Interaction):
try:
duration_minutes = int(self.duration.value)
if duration_minutes < 1 or duration_minutes > 40320: # Max 4 weeks
await interaction.response.send_message(
"❌ Duration must be between 1 and 40320 minutes (4 weeks).",
ephemeral=True
)
return
from datetime import datetime, timedelta
until = datetime.utcnow() + timedelta(minutes=duration_minutes)
reason = self.reason.value or f"Timed out by {interaction.user}"
await self.user.timeout(until=until, reason=reason)
embed = nextcord.Embed(
title="User Timed Out",
description=f"{self.user.mention} has been timed out for {duration_minutes} minutes.",
color=nextcord.Color.orange()
)
embed.add_field(name="Reason", value=reason, inline=False)
embed.add_field(
name="Until",
value=until.strftime("%Y-%m-%d %H:%M UTC"),
inline=True
)
await interaction.response.send_message(embed=embed, ephemeral=True)
except ValueError:
await interaction.response.send_message(
"❌ Please enter a valid number for duration.",
ephemeral=True
)
except nextcord.Forbidden:
await interaction.response.send_message(
"❌ I don't have permission to timeout this user.",
ephemeral=True
)
except Exception as e:
await interaction.response.send_message(
"❌ An error occurred while timing out the user.",
ephemeral=True
)Complex UI implementations combining multiple components.
class PaginatorView(View):
"""A paginated interface for displaying multiple pages of content."""
def __init__(self, pages: List[nextcord.Embed]):
super().__init__(timeout=300.0)
self.pages = pages
self.current_page = 0
self.update_buttons()
def update_buttons(self):
"""Update button states based on current page."""
self.first_page.disabled = self.current_page == 0
self.previous_page.disabled = self.current_page == 0
self.next_page.disabled = self.current_page == len(self.pages) - 1
self.last_page.disabled = self.current_page == len(self.pages) - 1
@nextcord.ui.button(label="<<", style=nextcord.ButtonStyle.secondary)
async def first_page(self, button: Button, interaction: nextcord.Interaction):
self.current_page = 0
self.update_buttons()
embed = self.pages[self.current_page]
embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
await interaction.response.edit_message(embed=embed, view=self)
@nextcord.ui.button(label="<", style=nextcord.ButtonStyle.primary)
async def previous_page(self, button: Button, interaction: nextcord.Interaction):
self.current_page -= 1
self.update_buttons()
embed = self.pages[self.current_page]
embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
await interaction.response.edit_message(embed=embed, view=self)
@nextcord.ui.button(label=">", style=nextcord.ButtonStyle.primary)
async def next_page(self, button: Button, interaction: nextcord.Interaction):
self.current_page += 1
self.update_buttons()
embed = self.pages[self.current_page]
embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
await interaction.response.edit_message(embed=embed, view=self)
@nextcord.ui.button(label=">>", style=nextcord.ButtonStyle.secondary)
async def last_page(self, button: Button, interaction: nextcord.Interaction):
self.current_page = len(self.pages) - 1
self.update_buttons()
embed = self.pages[self.current_page]
embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
await interaction.response.edit_message(embed=embed, view=self)
@nextcord.ui.button(label="Jump to Page", style=nextcord.ButtonStyle.secondary, emoji="🔢")
async def jump_to_page(self, button: Button, interaction: nextcord.Interaction):
modal = JumpToPageModal(self)
await interaction.response.send_modal(modal)
async def on_timeout(self):
# Disable all buttons when view times out
for child in self.children:
child.disabled = True
class JumpToPageModal(Modal):
def __init__(self, paginator: PaginatorView):
super().__init__(title="Jump to Page")
self.paginator = paginator
page_number = nextcord.ui.TextInput(
label=f"Page Number (1-{len(paginator.pages)})",
placeholder="Enter page number...",
required=True,
max_length=3
)
async def on_submit(self, interaction: nextcord.Interaction):
try:
page_num = int(self.page_number.value)
if 1 <= page_num <= len(self.paginator.pages):
self.paginator.current_page = page_num - 1
self.paginator.update_buttons()
embed = self.paginator.pages[self.paginator.current_page]
embed.set_footer(text=f"Page {self.paginator.current_page + 1} of {len(self.paginator.pages)}")
await interaction.response.edit_message(embed=embed, view=self.paginator)
else:
await interaction.response.send_message(
f"❌ Page number must be between 1 and {len(self.paginator.pages)}.",
ephemeral=True
)
except ValueError:
await interaction.response.send_message(
"❌ Please enter a valid page number.",
ephemeral=True
)
# Usage example
@bot.slash_command(description="Show paginated help")
async def help_pages(interaction: nextcord.Interaction):
# Create multiple embed pages
pages = []
# Page 1 - Commands
embed1 = nextcord.Embed(title="Bot Help - Commands", color=nextcord.Color.blue())
embed1.add_field(name="/help", value="Show this help message", inline=False)
embed1.add_field(name="/info", value="Get bot information", inline=False)
embed1.add_field(name="/ping", value="Check bot latency", inline=False)
pages.append(embed1)
# Page 2 - Features
embed2 = nextcord.Embed(title="Bot Help - Features", color=nextcord.Color.green())
embed2.add_field(name="Moderation", value="Timeout, kick, ban users", inline=False)
embed2.add_field(name="Utility", value="Server info, user info", inline=False)
embed2.add_field(name="Fun", value="Games and entertainment", inline=False)
pages.append(embed2)
# Page 3 - Support
embed3 = nextcord.Embed(title="Bot Help - Support", color=nextcord.Color.red())
embed3.add_field(name="Support Server", value="[Join Here](https://discord.gg/example)", inline=False)
embed3.add_field(name="Bug Reports", value="Use `/report` command", inline=False)
embed3.add_field(name="Feature Requests", value="Use `/suggest` command", inline=False)
pages.append(embed3)
# Set initial page footer
pages[0].set_footer(text=f"Page 1 of {len(pages)}")
view = PaginatorView(pages)
await interaction.response.send_message(embed=pages[0], view=view)class ServerConfigView(View):
"""A comprehensive server configuration panel."""
def __init__(self, guild: nextcord.Guild):
super().__init__(timeout=600.0) # 10 minute timeout
self.guild = guild
self.config = self.load_config(guild.id) # Load from database/file
def load_config(self, guild_id: int) -> dict:
"""Load server configuration (implement your storage here)."""
return {
"welcome_channel": None,
"log_channel": None,
"auto_role": None,
"prefix": "!",
"moderation": True,
"auto_mod": False
}
def save_config(self) -> None:
"""Save server configuration (implement your storage here)."""
# Save self.config to database/file
pass
@nextcord.ui.select(
placeholder="Configure server settings...",
options=[
nextcord.SelectOption(
label="Welcome System",
description="Set up welcome messages and channel",
emoji="👋",
value="welcome"
),
nextcord.SelectOption(
label="Logging",
description="Configure audit log channel",
emoji="📝",
value="logging"
),
nextcord.SelectOption(
label="Auto Role",
description="Set role to assign to new members",
emoji="👤",
value="auto_role"
),
nextcord.SelectOption(
label="Moderation",
description="Toggle moderation features",
emoji="🔨",
value="moderation"
),
]
)
async def config_select(self, select: Select, interaction: nextcord.Interaction):
selection = select.values[0]
if selection == "welcome":
view = WelcomeConfigView(self.guild, self.config)
elif selection == "logging":
view = LoggingConfigView(self.guild, self.config)
elif selection == "auto_role":
view = AutoRoleConfigView(self.guild, self.config)
elif selection == "moderation":
view = ModerationConfigView(self.guild, self.config)
embed = nextcord.Embed(
title=f"{selection.replace('_', ' ').title()} Configuration",
description="Use the buttons below to configure this feature.",
color=nextcord.Color.blue()
)
await interaction.response.edit_message(embed=embed, view=view)
@nextcord.ui.button(label="Save & Exit", style=nextcord.ButtonStyle.success, emoji="💾")
async def save_and_exit(self, button: Button, interaction: nextcord.Interaction):
self.save_config()
embed = nextcord.Embed(
title="Configuration Saved",
description="All changes have been saved successfully!",
color=nextcord.Color.green()
)
await interaction.response.edit_message(embed=embed, view=None)
self.stop()
class WelcomeConfigView(View):
def __init__(self, guild: nextcord.Guild, config: dict):
super().__init__(timeout=300.0)
self.guild = guild
self.config = config
@nextcord.ui.channel_select(
placeholder="Select welcome channel...",
channel_types=[nextcord.ChannelType.text]
)
async def welcome_channel_select(self, select: ChannelSelect, interaction: nextcord.Interaction):
channel = select.values[0]
self.config["welcome_channel"] = channel.id
await interaction.response.send_message(
f"✅ Welcome channel set to {channel.mention}",
ephemeral=True
)
@nextcord.ui.button(label="Set Welcome Message", style=nextcord.ButtonStyle.primary)
async def set_welcome_message(self, button: Button, interaction: nextcord.Interaction):
modal = WelcomeMessageModal(self.config)
await interaction.response.send_modal(modal)
@nextcord.ui.button(label="Back to Main", style=nextcord.ButtonStyle.secondary)
async def back_to_main(self, button: Button, interaction: nextcord.Interaction):
main_view = ServerConfigView(self.guild)
main_view.config = self.config # Preserve changes
embed = nextcord.Embed(
title="Server Configuration",
description="Choose a category to configure:",
color=nextcord.Color.blue()
)
await interaction.response.edit_message(embed=embed, view=main_view)
class WelcomeMessageModal(Modal):
def __init__(self, config: dict):
super().__init__(title="Set Welcome Message")
self.config = config
message = nextcord.ui.TextInput(
label="Welcome Message",
style=nextcord.TextInputStyle.paragraph,
placeholder="Welcome {user} to {server}! Available: {user}, {server}, {mention}",
default=config.get("welcome_message", ""),
max_length=1000
)
async def on_submit(self, interaction: nextcord.Interaction):
self.config["welcome_message"] = self.message.value
await interaction.response.send_message(
"✅ Welcome message updated!",
ephemeral=True
)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.
Install with Tessl CLI
npx tessl i tessl/pypi-nextcord