A modern, easy-to-use, feature-rich async-ready API wrapper for Discord written in Python
—
Interactive Discord UI elements including buttons, select menus, modals, and views for creating rich user interfaces. These components enable persistent interactions and complex user workflows within Discord.
Container classes that manage collections of interactive UI components with persistent state and callback handling.
class View:
def __init__(
self,
*,
timeout: Optional[float] = 180.0,
auto_defer: bool = True
):
"""
Create a view for managing UI components.
Parameters:
- timeout: View timeout in seconds (None for no timeout)
- auto_defer: Whether to auto-defer interactions
"""
timeout: Optional[float]
children: List[Item]
def add_item(self, item: Item) -> Self:
"""
Add a UI component to this view.
Parameters:
- item: Component to add
Returns:
Self for method chaining
"""
def remove_item(self, item: Item) -> Self:
"""
Remove a UI component from this view.
Parameters:
- item: Component to remove
Returns:
Self for method chaining
"""
def clear_items(self) -> Self:
"""
Remove all items from this view.
Returns:
Self for method chaining
"""
def get_item(self, custom_id: str) -> Optional[Item]:
"""
Get an item by custom_id.
Parameters:
- custom_id: Custom ID to search for
Returns:
Item if found
"""
async def interaction_check(self, interaction: Interaction) -> bool:
"""
Check if interaction should be processed by this view.
Parameters:
- interaction: Interaction to check
Returns:
True if interaction should be processed
"""
async def on_timeout(self) -> None:
"""Called when the view times out."""
async def on_error(self, interaction: Interaction, error: Exception, item: Item) -> None:
"""
Called when an error occurs in a component callback.
Parameters:
- interaction: Interaction that caused the error
- error: Exception that occurred
- item: Component that caused the error
"""
def stop(self) -> None:
"""Stop the view and prevent further interactions."""
def is_finished(self) -> bool:
"""Whether the view has finished (stopped or timed out)."""
def is_persistent(self) -> bool:
"""Whether the view is persistent (all items have custom_id)."""
@classmethod
def from_message(cls, message: Message, *, timeout: Optional[float] = 180.0) -> View:
"""
Create a view from existing message components.
Parameters:
- message: Message with components
- timeout: View timeout
Returns:
View instance with message components
"""Clickable button components with various styles and callback handling.
class Button(Item):
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
):
"""
Create a button component.
Parameters:
- style: Button style (primary, secondary, success, danger, link)
- label: Button text label
- disabled: Whether button is disabled
- custom_id: Custom ID for persistent buttons
- url: URL for link buttons
- emoji: Button emoji
- row: Action row (0-4)
"""
style: ButtonStyle
label: Optional[str]
disabled: bool
custom_id: Optional[str]
url: Optional[str]
emoji: Optional[Union[str, Emoji, PartialEmoji]]
async def callback(self, interaction: MessageInteraction) -> None:
"""
Button click callback. Override this method.
Parameters:
- interaction: Button click interaction
"""
def button(
*,
label: Optional[str] = None,
custom_id: Optional[str] = None,
disabled: bool = False,
style: ButtonStyle = ButtonStyle.secondary,
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
row: Optional[int] = None
):
"""
Decorator for button callbacks in views.
Parameters:
- label: Button text label
- custom_id: Custom ID for persistent buttons
- disabled: Whether button is disabled
- style: Button style
- emoji: Button emoji
- row: Action row (0-4)
Returns:
Decorated method as a Button component
"""Dropdown selection components with multiple variations for different data types.
class BaseSelect(Item):
def __init__(
self,
*,
custom_id: Optional[str] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
row: Optional[int] = None
):
"""
Base class for select menu components.
Parameters:
- custom_id: Custom ID for persistent components
- placeholder: Placeholder text
- min_values: Minimum selections required
- max_values: Maximum selections allowed
- disabled: Whether component is disabled
- row: Action row (0-4)
"""
custom_id: Optional[str]
placeholder: Optional[str]
min_values: int
max_values: int
disabled: bool
values: List[str]
async def callback(self, interaction: MessageInteraction) -> None:
"""
Selection callback. Override this method.
Parameters:
- interaction: Selection interaction
"""
class StringSelect(BaseSelect):
def __init__(
self,
*,
options: List[SelectOption],
custom_id: Optional[str] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
row: Optional[int] = None
):
"""
String selection menu with predefined options.
Parameters:
- options: List of SelectOption objects
- custom_id: Custom ID for persistent components
- placeholder: Placeholder text
- min_values: Minimum selections required
- max_values: Maximum selections allowed
- disabled: Whether component is disabled
- row: Action row (0-4)
"""
options: List[SelectOption]
class UserSelect(BaseSelect):
def __init__(
self,
*,
custom_id: Optional[str] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
row: Optional[int] = None,
default_values: Optional[List[SelectDefaultValue]] = None
):
"""
User selection menu.
Parameters:
- custom_id: Custom ID for persistent components
- placeholder: Placeholder text
- min_values: Minimum selections required
- max_values: Maximum selections allowed
- disabled: Whether component is disabled
- row: Action row (0-4)
- default_values: Default selected users
"""
default_values: Optional[List[SelectDefaultValue]]
class RoleSelect(BaseSelect):
def __init__(
self,
*,
custom_id: Optional[str] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
row: Optional[int] = None,
default_values: Optional[List[SelectDefaultValue]] = None
):
"""
Role selection menu.
Parameters:
- custom_id: Custom ID for persistent components
- placeholder: Placeholder text
- min_values: Minimum selections required
- max_values: Maximum selections allowed
- disabled: Whether component is disabled
- row: Action row (0-4)
- default_values: Default selected roles
"""
default_values: Optional[List[SelectDefaultValue]]
class ChannelSelect(BaseSelect):
def __init__(
self,
*,
channel_types: Optional[List[ChannelType]] = None,
custom_id: Optional[str] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
row: Optional[int] = None,
default_values: Optional[List[SelectDefaultValue]] = None
):
"""
Channel selection menu.
Parameters:
- channel_types: Allowed channel types
- custom_id: Custom ID for persistent components
- placeholder: Placeholder text
- min_values: Minimum selections required
- max_values: Maximum selections allowed
- disabled: Whether component is disabled
- row: Action row (0-4)
- default_values: Default selected channels
"""
channel_types: Optional[List[ChannelType]]
default_values: Optional[List[SelectDefaultValue]]
class MentionableSelect(BaseSelect):
def __init__(
self,
*,
custom_id: Optional[str] = None,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
row: Optional[int] = None,
default_values: Optional[List[SelectDefaultValue]] = None
):
"""
Mentionable entity (users and roles) selection menu.
Parameters:
- custom_id: Custom ID for persistent components
- placeholder: Placeholder text
- min_values: Minimum selections required
- max_values: Maximum selections allowed
- disabled: Whether component is disabled
- row: Action row (0-4)
- default_values: Default selected entities
"""
default_values: Optional[List[SelectDefaultValue]]
class SelectOption:
def __init__(
self,
*,
label: str,
value: str,
description: Optional[str] = None,
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
default: bool = False
):
"""
Option for string select menus.
Parameters:
- label: Option display label
- value: Option value
- description: Option description
- emoji: Option emoji
- default: Whether option is selected by default
"""
label: str
value: str
description: Optional[str]
emoji: Optional[Union[str, Emoji, PartialEmoji]]
default: bool
class SelectDefaultValue:
def __init__(self, id: int, type: SelectDefaultValueType):
"""
Default value for entity select menus.
Parameters:
- id: Entity ID
- type: Entity type (user, role, channel)
"""
id: int
type: SelectDefaultValueTypeDialog forms for collecting structured user input with multiple text fields.
class Modal:
def __init__(
self,
*,
title: str,
custom_id: Optional[str] = None,
timeout: Optional[float] = None
):
"""
Create a modal dialog form.
Parameters:
- title: Modal title
- custom_id: Custom ID for persistent modals
- timeout: Modal timeout in seconds
"""
title: str
custom_id: Optional[str]
timeout: Optional[float]
children: List[TextInput]
def add_item(self, item: TextInput) -> Self:
"""
Add a text input to this modal.
Parameters:
- item: TextInput to add
Returns:
Self for method chaining
"""
def remove_item(self, item: TextInput) -> Self:
"""
Remove a text input from this modal.
Parameters:
- item: TextInput to remove
Returns:
Self for method chaining
"""
async def on_submit(self, interaction: ModalInteraction) -> None:
"""
Called when modal is submitted. Override this method.
Parameters:
- interaction: Modal submission interaction
"""
async def on_error(self, interaction: ModalInteraction, error: Exception) -> None:
"""
Called when an error occurs during modal submission.
Parameters:
- interaction: Modal interaction
- error: Exception that occurred
"""
async def on_timeout(self) -> None:
"""Called when the modal times out."""
def stop(self) -> None:
"""Stop the modal and prevent further interactions."""
class TextInput(Item):
def __init__(
self,
*,
label: str,
custom_id: Optional[str] = None,
style: TextInputStyle = TextInputStyle.short,
placeholder: Optional[str] = None,
value: Optional[str] = None,
required: bool = True,
min_length: Optional[int] = None,
max_length: Optional[int] = None,
row: Optional[int] = None
):
"""
Text input field for modals.
Parameters:
- label: Input field label
- custom_id: Custom ID for the input
- style: Input style (short or paragraph)
- placeholder: Placeholder text
- value: Pre-filled value
- required: Whether input is required
- min_length: Minimum text length
- max_length: Maximum text length
- row: Action row (0-4)
"""
label: str
custom_id: Optional[str]
style: TextInputStyle
placeholder: Optional[str]
value: Optional[str]
required: bool
min_length: Optional[int]
max_length: Optional[int]
def text_input(
*,
label: str,
custom_id: Optional[str] = None,
style: TextInputStyle = TextInputStyle.short,
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
):
"""
Decorator for text input fields in modals.
Parameters:
- label: Input field label
- custom_id: Custom ID for the input
- style: Input style (short or paragraph)
- placeholder: Placeholder text
- default: Pre-filled value
- required: Whether input is required
- min_length: Minimum text length
- max_length: Maximum text length
- row: Action row (0-4)
Returns:
Decorated attribute as a TextInput component
"""Interaction objects for UI component interactions like button clicks and select menu selections.
class MessageInteraction:
def __init__(self): ...
id: int
type: InteractionType
data: MessageInteractionData
guild: Optional[Guild]
guild_id: Optional[int]
channel: Optional[Union[GuildChannel, PartialMessageable]]
channel_id: Optional[int]
user: Union[User, Member]
token: str
version: int
message: Message
locale: Optional[Locale]
guild_locale: Optional[Locale]
created_at: datetime
expires_at: datetime
@property
def response(self) -> InteractionResponse:
"""Interaction response handler."""
@property
def followup(self) -> Webhook:
"""Webhook for followup messages."""
@property
def component(self) -> Component:
"""The component that triggered this interaction."""
class ModalInteraction:
def __init__(self): ...
id: int
type: InteractionType
data: ModalInteractionData
guild: Optional[Guild]
guild_id: Optional[int]
channel: Optional[Union[GuildChannel, PartialMessageable]]
channel_id: Optional[int]
user: Union[User, Member]
token: str
version: int
message: Optional[Message]
locale: Optional[Locale]
guild_locale: Optional[Locale]
created_at: datetime
expires_at: datetime
@property
def response(self) -> InteractionResponse:
"""Interaction response handler."""
@property
def followup(self) -> Webhook:
"""Webhook for followup messages."""
@property
def text_values(self) -> Dict[str, str]:
"""Dictionary mapping text input custom_ids to their values."""import disnake
class BasicView(disnake.ui.View):
def __init__(self):
super().__init__(timeout=60)
self.value = None
@disnake.ui.button(label="Yes", style=disnake.ButtonStyle.green)
async def yes_button(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
self.value = True
self.stop()
await interaction.response.send_message("You clicked Yes!", ephemeral=True)
@disnake.ui.button(label="No", style=disnake.ButtonStyle.red)
async def no_button(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
self.value = False
self.stop()
await interaction.response.send_message("You clicked No!", ephemeral=True)
# Usage
@bot.slash_command(description="Ask a yes/no question")
async def question(inter: disnake.ApplicationCommandInteraction):
view = BasicView()
await inter.response.send_message("Do you like pizza?", view=view)
await view.wait()
if view.value is None:
await inter.followup.send("You didn't respond in time!")
elif view.value:
await inter.followup.send("Great choice!")
else:
await inter.followup.send("That's okay!")class GameSelect(disnake.ui.View):
@disnake.ui.string_select(
placeholder="Choose your favorite game...",
min_values=1,
max_values=3,
options=[
disnake.SelectOption(
label="Minecraft",
value="minecraft",
description="Block building game",
emoji="🟫"
),
disnake.SelectOption(
label="Among Us",
value="among_us",
description="Social deduction game",
emoji="🔴"
),
disnake.SelectOption(
label="Discord",
value="discord",
description="Chat application",
emoji="💬"
),
]
)
async def select_callback(self, select: disnake.ui.StringSelect, interaction: disnake.MessageInteraction):
games = ", ".join(select.values)
await interaction.response.send_message(f"You selected: {games}")
@bot.slash_command(description="Choose your favorite games")
async def games(inter: disnake.ApplicationCommandInteraction):
view = GameSelect()
await inter.response.send_message("Select your favorites:", view=view)class ModeratorPanel(disnake.ui.View):
@disnake.ui.user_select(placeholder="Select users to moderate...")
async def select_users(self, select: disnake.ui.UserSelect, interaction: disnake.MessageInteraction):
users = [f"{user.mention}" for user in select.values]
await interaction.response.send_message(
f"Selected users: {', '.join(users)}",
ephemeral=True
)
@disnake.ui.role_select(placeholder="Select roles to manage...")
async def select_roles(self, select: disnake.ui.RoleSelect, interaction: disnake.MessageInteraction):
roles = [f"{role.mention}" for role in select.values]
await interaction.response.send_message(
f"Selected roles: {', '.join(roles)}",
ephemeral=True
)
@bot.slash_command(description="Moderator panel")
async def mod_panel(inter: disnake.ApplicationCommandInteraction):
view = ModeratorPanel()
await inter.response.send_message("Moderator Controls:", view=view)class FeedbackModal(disnake.ui.Modal):
def __init__(self):
super().__init__(title="Submit Feedback", timeout=300)
name = disnake.ui.TextInput(
label="Your Name",
placeholder="Enter your name here...",
required=True,
max_length=100
)
feedback = disnake.ui.TextInput(
label="Feedback",
style=disnake.TextInputStyle.paragraph,
placeholder="Tell us what you think...",
required=True,
min_length=10,
max_length=1000
)
async def on_submit(self, interaction: disnake.ModalInteraction):
embed = disnake.Embed(
title="New Feedback",
color=disnake.Color.blue()
)
embed.add_field(name="From", value=self.name.value, inline=True)
embed.add_field(name="User", value=interaction.author.mention, inline=True)
embed.add_field(name="Feedback", value=self.feedback.value, inline=False)
# Send to feedback channel
feedback_channel = bot.get_channel(FEEDBACK_CHANNEL_ID)
await feedback_channel.send(embed=embed)
await interaction.response.send_message(
"Thank you for your feedback!",
ephemeral=True
)
@bot.slash_command(description="Submit feedback")
async def feedback(inter: disnake.ApplicationCommandInteraction):
modal = FeedbackModal()
await inter.response.send_modal(modal)class PersistentView(disnake.ui.View):
def __init__(self):
super().__init__(timeout=None) # No timeout for persistent views
@disnake.ui.button(
label="Get Support",
style=disnake.ButtonStyle.primary,
custom_id="support_button" # Custom ID for persistence
)
async def support_button(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
# This button will work even after bot restarts
await interaction.response.send_message(
"Support ticket created! A moderator will be with you shortly.",
ephemeral=True
)
# Add persistent view on bot startup
@bot.event
async def on_ready():
bot.add_view(PersistentView())
print(f'{bot.user} is ready!')
@bot.slash_command(description="Send support panel")
async def support_panel(inter: disnake.ApplicationCommandInteraction):
embed = disnake.Embed(
title="Support Center",
description="Click the button below to get help."
)
view = PersistentView()
await inter.response.send_message(embed=embed, view=view)class ConfigurationWizard(disnake.ui.View):
def __init__(self):
super().__init__(timeout=300)
self.config = {}
@disnake.ui.button(label="Step 1: Choose Channel", style=disnake.ButtonStyle.primary)
async def step1(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
view = ChannelSelectView(self)
await interaction.response.edit_message(
content="Step 1: Select a channel for notifications:",
view=view
)
@disnake.ui.button(label="Step 2: Set Permissions", style=disnake.ButtonStyle.secondary, disabled=True)
async def step2(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
view = RoleSelectView(self)
await interaction.response.edit_message(
content="Step 2: Select roles that can use this feature:",
view=view
)
@disnake.ui.button(label="Finish", style=disnake.ButtonStyle.green, disabled=True)
async def finish(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
embed = disnake.Embed(title="Configuration Complete!")
embed.add_field(name="Channel", value=self.config.get('channel', 'None'))
embed.add_field(name="Roles", value=', '.join(self.config.get('roles', [])))
await interaction.response.edit_message(
content="Configuration saved!",
embed=embed,
view=None
)
class ChannelSelectView(disnake.ui.View):
def __init__(self, parent):
super().__init__(timeout=300)
self.parent = parent
@disnake.ui.channel_select(
placeholder="Select notification channel...",
channel_types=[disnake.ChannelType.text]
)
async def select_channel(self, select: disnake.ui.ChannelSelect, interaction: disnake.MessageInteraction):
self.parent.config['channel'] = select.values[0].mention
# Enable step 2
self.parent.children[1].disabled = False
await interaction.response.edit_message(
content="✅ Channel selected! Now proceed to step 2:",
view=self.parent
)
class RoleSelectView(disnake.ui.View):
def __init__(self, parent):
super().__init__(timeout=300)
self.parent = parent
@disnake.ui.role_select(
placeholder="Select authorized roles...",
min_values=1,
max_values=5
)
async def select_roles(self, select: disnake.ui.RoleSelect, interaction: disnake.MessageInteraction):
self.parent.config['roles'] = [role.mention for role in select.values]
# Enable finish button
self.parent.children[2].disabled = False
await interaction.response.edit_message(
content="✅ Roles selected! Click Finish to complete setup:",
view=self.parent
)
@bot.slash_command(description="Configure bot settings")
async def configure(inter: disnake.ApplicationCommandInteraction):
view = ConfigurationWizard()
await inter.response.send_message("Welcome to the configuration wizard!", view=view)Install with Tessl CLI
npx tessl i tessl/pypi-disnake