Buttons
Buttons are clickable components that can trigger actions:Basic Button
import discord
class MyView(discord.ui.View):
@discord.ui.button(label="Click Me!", style=discord.ButtonStyle.primary)
async def button_callback(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message("Button clicked!", ephemeral=True)
@bot.slash_command()
async def buttons(ctx: discord.ApplicationContext):
view = MyView()
await ctx.respond("Press the button!", view=view)
Button Styles
class StyledButtons(discord.ui.View):
@discord.ui.button(label="Primary", style=discord.ButtonStyle.primary)
async def primary(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message("Primary!", ephemeral=True)
@discord.ui.button(label="Secondary", style=discord.ButtonStyle.secondary)
async def secondary(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message("Secondary!", ephemeral=True)
@discord.ui.button(label="Success", style=discord.ButtonStyle.success)
async def success(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message("Success!", ephemeral=True)
@discord.ui.button(label="Danger", style=discord.ButtonStyle.danger)
async def danger(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message("Danger!", ephemeral=True)
Button with Emoji
@discord.ui.button(label="Like", emoji="👍", style=discord.ButtonStyle.success)
async def like_button(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message("Thanks for the like!", ephemeral=True)
Link Button
class LinkView(discord.ui.View):
def __init__(self):
super().__init__()
# Link buttons don't have callbacks
self.add_item(discord.ui.Button(
label="Visit Pycord",
url="https://pycord.dev",
style=discord.ButtonStyle.link
))
Counter Example
Create a button that tracks state:class Counter(discord.ui.View):
@discord.ui.button(label="0", style=discord.ButtonStyle.red)
async def count(self, button: discord.ui.Button, interaction: discord.Interaction):
number = int(button.label) if button.label else 0
if number >= 4:
button.style = discord.ButtonStyle.green
button.disabled = True
button.label = str(number + 1)
# Update the message with the modified view
await interaction.response.edit_message(view=self)
Disabling Buttons
@discord.ui.button(label="Disabled", style=discord.ButtonStyle.secondary, disabled=True)
async def disabled_button(self, button: discord.ui.Button, interaction: discord.Interaction):
# This will never be called
pass
Select Menus
Select menus (dropdowns) let users choose from multiple options:String Select
class Dropdown(discord.ui.Select):
def __init__(self):
options = [
discord.SelectOption(
label="Red",
description="Your favorite color is red",
emoji="🟥"
),
discord.SelectOption(
label="Green",
description="Your favorite color is green",
emoji="🟩"
),
discord.SelectOption(
label="Blue",
description="Your favorite color is blue",
emoji="🟦"
),
]
super().__init__(
placeholder="Choose your favorite color...",
min_values=1,
max_values=1,
options=options,
)
async def callback(self, interaction: discord.Interaction):
await interaction.response.send_message(
f"Your favorite color is {self.values[0]}",
ephemeral=True
)
class DropdownView(discord.ui.View):
def __init__(self):
super().__init__()
self.add_item(Dropdown())
@bot.slash_command()
async def color(ctx: discord.ApplicationContext):
view = DropdownView()
await ctx.respond("Pick your favorite color:", view=view)
User Select
class UserSelect(discord.ui.View):
@discord.ui.select(
select_type=discord.ComponentType.user_select,
placeholder="Select a user",
min_values=1,
max_values=1,
)
async def user_select_callback(self, select: discord.ui.Select, interaction: discord.Interaction):
user = select.values[0]
await interaction.response.send_message(
f"You selected {user.mention}",
ephemeral=True
)
Role Select
class RoleSelect(discord.ui.View):
@discord.ui.select(
select_type=discord.ComponentType.role_select,
placeholder="Select a role",
min_values=1,
max_values=3, # Allow up to 3 roles
)
async def role_select_callback(self, select: discord.ui.Select, interaction: discord.Interaction):
roles = ", ".join([role.mention for role in select.values])
await interaction.response.send_message(
f"Selected roles: {roles}",
ephemeral=True
)
Channel Select
class ChannelSelect(discord.ui.View):
@discord.ui.select(
select_type=discord.ComponentType.channel_select,
placeholder="Select a channel",
channel_types=[discord.ChannelType.text, discord.ChannelType.voice],
)
async def channel_select_callback(self, select: discord.ui.Select, interaction: discord.Interaction):
channel = select.values[0]
await interaction.response.send_message(
f"You selected {channel.mention}",
ephemeral=True
)
Mentionable Select
class MentionableSelect(discord.ui.View):
@discord.ui.select(
select_type=discord.ComponentType.mentionable_select,
placeholder="Select users or roles",
min_values=1,
max_values=5,
)
async def mentionable_callback(self, select: discord.ui.Select, interaction: discord.Interaction):
mentions = ", ".join([item.mention for item in select.values])
await interaction.response.send_message(
f"Selected: {mentions}",
ephemeral=True
)
Modals
Modals are pop-up forms that collect user input:Basic Modal
class MyModal(discord.ui.DesignerModal):
def __init__(self, *args, **kwargs):
first_input = discord.ui.Label(
"Short Input",
discord.ui.InputText(
placeholder="Type something...",
),
)
second_input = discord.ui.Label(
"Long Input",
discord.ui.InputText(
placeholder="Type a longer response...",
style=discord.InputTextStyle.long,
),
description="This input accepts longer text.",
)
super().__init__(first_input, second_input, *args, **kwargs)
async def callback(self, interaction: discord.Interaction):
# Access input values
short_input = self.children[0].item.value
long_input = self.children[1].item.value
await interaction.response.send_message(
f"Short: {short_input}\nLong: {long_input}",
ephemeral=True
)
@bot.slash_command()
async def form(ctx: discord.ApplicationContext):
modal = MyModal(title="Feedback Form")
await ctx.send_modal(modal)
Modal with Select
class AdvancedModal(discord.ui.DesignerModal):
def __init__(self, *args, **kwargs):
name_input = discord.ui.Label(
"Your Name",
discord.ui.InputText(placeholder="Enter your name"),
)
color_select = discord.ui.Label(
"Favorite Color",
discord.ui.Select(
placeholder="Select a color",
options=[
discord.SelectOption(label="Red", emoji="🟥"),
discord.SelectOption(label="Green", emoji="🟩"),
discord.SelectOption(label="Blue", emoji="🟦"),
],
required=False,
),
description="Optional: Select your favorite color",
)
super().__init__(name_input, color_select, *args, **kwargs)
async def callback(self, interaction: discord.Interaction):
name = self.children[0].item.value
color = self.children[1].item.values[0] if self.children[1].item.values else "None"
await interaction.response.send_message(
f"Name: {name}\nColor: {color}",
ephemeral=True
)
Modal from Button
class ModalView(discord.ui.View):
@discord.ui.button(label="Open Form", style=discord.ButtonStyle.primary)
async def button_callback(self, button: discord.ui.Button, interaction: discord.Interaction):
modal = MyModal(title="Button Modal")
await interaction.response.send_modal(modal)
View Management
Timeout
class TimedView(discord.ui.View):
def __init__(self):
super().__init__(timeout=60) # 60 seconds
async def on_timeout(self):
# Called when view times out
for item in self.children:
item.disabled = True
# Update the message to disable all components
Persistent Views
Views that persist across bot restarts:class PersistentView(discord.ui.View):
def __init__(self):
super().__init__(timeout=None) # No timeout
@discord.ui.button(
label="Persistent Button",
style=discord.ButtonStyle.success,
custom_id="persistent_button", # Must be set for persistent views
)
async def persistent_button(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message("Still working!", ephemeral=True)
@bot.event
async def on_ready():
# Re-register persistent view on startup
bot.add_view(PersistentView())
Disable on Use
class OneTimeButton(discord.ui.View):
@discord.ui.button(label="Use Once", style=discord.ButtonStyle.primary)
async def one_time(self, button: discord.ui.Button, interaction: discord.Interaction):
button.disabled = True
await interaction.response.edit_message(view=self)
await interaction.followup.send("Button disabled!", ephemeral=True)
Stop View
class StoppableView(discord.ui.View):
@discord.ui.button(label="Action", style=discord.ButtonStyle.primary)
async def action(self, button: discord.ui.Button, interaction: discord.Interaction):
await interaction.response.send_message("Action performed!", ephemeral=True)
@discord.ui.button(label="Close", style=discord.ButtonStyle.danger)
async def close(self, button: discord.ui.Button, interaction: discord.Interaction):
self.stop() # Stop listening for interactions
await interaction.response.edit_message(view=None) # Remove components
Advanced Patterns
Paginator
class Paginator(discord.ui.View):
def __init__(self, pages: list[discord.Embed]):
super().__init__()
self.pages = pages
self.current_page = 0
@discord.ui.button(emoji="⬅️", style=discord.ButtonStyle.secondary)
async def previous(self, button: discord.ui.Button, interaction: discord.Interaction):
self.current_page = (self.current_page - 1) % len(self.pages)
await interaction.response.edit_message(embed=self.pages[self.current_page])
@discord.ui.button(emoji="➡️", style=discord.ButtonStyle.secondary)
async def next(self, button: discord.ui.Button, interaction: discord.Interaction):
self.current_page = (self.current_page + 1) % len(self.pages)
await interaction.response.edit_message(embed=self.pages[self.current_page])
Confirmation Dialog
class Confirm(discord.ui.View):
def __init__(self):
super().__init__()
self.value = None
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.success)
async def confirm(self, button: discord.ui.Button, interaction: discord.Interaction):
self.value = True
self.stop()
await interaction.response.send_message("Confirmed!", ephemeral=True)
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.danger)
async def cancel(self, button: discord.ui.Button, interaction: discord.Interaction):
self.value = False
self.stop()
await interaction.response.send_message("Cancelled", ephemeral=True)
@bot.slash_command()
async def delete(ctx: discord.ApplicationContext):
view = Confirm()
await ctx.respond("Are you sure you want to delete?", view=view)
await view.wait()
if view.value:
await ctx.send("Deleted!")
Role Assignment
class RoleButton(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
@discord.ui.button(
label="Get Member Role",
style=discord.ButtonStyle.success,
custom_id="member_role",
)
async def member_role(self, button: discord.ui.Button, interaction: discord.Interaction):
role = discord.utils.get(interaction.guild.roles, name="Member")
if role in interaction.user.roles:
await interaction.user.remove_roles(role)
await interaction.response.send_message("Role removed!", ephemeral=True)
else:
await interaction.user.add_roles(role)
await interaction.response.send_message("Role added!", ephemeral=True)
Best Practices
Component Limits:
- Maximum 5 action rows per message
- Maximum 5 buttons per row
- Only 1 select menu per row
- Maximum 25 options per select menu
- Button labels must be 80 characters or fewer
- Custom IDs must be 100 characters or fewer
See Also
- Slash Commands - Trigger UI components from commands
- Error Handling - Handle interaction errors
- Prefix Commands - Traditional commands with UI
