Select Menus
Shortly after the buttons were added, Discord added their second message component: Select Menus. Select Menus allow users to choose from a list of items sent by a bot. These are a great substitute for having a user send a number that corresponds to an option. You can even allow users to select multiple options from the Select Menus. This guide will show you the easy and painless ways of using them with Pycord.
Concept
Select Menus aren't the only message component in Discord. There's also Buttons and Modal Dialogs. Select Menus make it easy for users to pick one or multiple options from a list provided by a bot.
These UI elements reside in a "view". To learn more about views, please refer to the interactions page.
Usage Syntax
Let's see how to create a simple responsive select menu.
import discord
bot = discord.Bot()
class MyView(discord.ui.View):
@discord.ui.select( # the decorator that lets you specify the properties of the select menu
placeholder = "Choose a Flavor!", # the placeholder text that will be displayed if nothing is selected
min_values = 1, # the minimum number of values that must be selected by the users
max_values = 1, # the maximum number of values that can be selected by the users
options = [ # the list of options from which users can choose, a required field
discord.SelectOption(
label="Vanilla",
description="Pick this if you like vanilla!"
),
discord.SelectOption(
label="Chocolate",
description="Pick this if you like chocolate!"
),
discord.SelectOption(
label="Strawberry",
description="Pick this if you like strawberry!"
)
]
)
async def select_callback(self, select, interaction): # the function called when the user is done selecting options
await interaction.response.send_message(f"Awesome! I like {select.values[0]} too!")
@bot.command()
async def flavor(ctx):
await ctx.send("Choose a flavor!", view=MyView())
bot.run("TOKEN")
There's a lot going on over here! Don't worry, we will go over the code and explain it.
As you can see, we create a class called MyView
that subclasses
discord.ui.View
.
Then, we add a function called select_callback
to the View
class with the decorator
discord.ui.select
.
This decorator adds a select menu to the view. This decorator takes a few arguments to customize your select menu. Read them in the Customization section.
That was the decorator. Now, the function itself is pretty simple. It takes two parameters, not including self
. The parameters are select
: The select menu, and interaction
: a discord.InteractionResponse
object. Both of these are passed by Pycord, so you just need to specify them in the function!
In the callback, you could do anything you want. You get the two parameters select
and interaction
to play around with. Here, we send a message using await interaction.response.send_message
(where interaction is discord.InteractionResponse
) with content select.values[0]
, which sends the label of the first/only option the user selected. Obviously, this is only an example, and you could do just about anything you want.
Finally, we create a global slash command called flavour
that sends a message "Choose a flavor!" along with the view
that contains our select menu.
This is the basic syntax of creating a select menu. What you create with it is up to you. You can worry about making your code do amazing things, while Pycord handles the rest!
Customization
Select Menu Properties
select_type
: The type of select to create. This must be adiscord.ComponentType
value.channel_types
: A list of channel types that can be selected in the menu. This is only valid for selects ofselect_type
discord.ComponentType.channel_select
.options
: A list ofdiscord.SelectOption
values. These are the options that can be selected in this menu.placeholder
is the placeholder text shown in the select menu if no option is selected.custom_id
: The ID of the select menu that gets received during an interaction. It is recommended not to set this to anything unless you are trying to create a persistent view.row
: The relative row this select menu belongs to. A Discord component can only have 5 rows. By default, items are arranged automatically into those 5 rows. If you’d like to control the relative positioning of the row then passing an index is advised. For example, row=1 will show up before row=2. Defaults to None, which is automatic ordering. The row number must be between 0 and 4 (i.e. zero indexed).min_values
: The minimum number of items that must be chosen for this select menu. Defaults to 1 and must be between 1 and 25.max_values
: The maximum number of items that must be chosen for this select menu. Defaults to 1 and must be between 1 and 25.disabled
: Whether the select is disabled or not. Defaults to False.
Select Option Properties
In the options
parameter, you pass a list of discord.SelectOption
values. This class also has a few parameters:
default
(whether the option is selected by default)description
(an additional description, if any)emoji
(a string or an emoji object, if any)label
(the name displayed to users, can be up to 100 characters)value
(a special value of the option, defaults to the label).
Select Types
In addition to regular string selects, you can also have your select menu contain users, roles, mentionables, and channels as its options. You can use these alternative select types by passing a discord.ComponentType
value for the select_type
parameter when creating the Select.
class MyView(discord.ui.View):
@discord.ui.select(
select_type=discord.ComponentType.user_select
)
async def select_callback(self, select, interaction):
await interaction.response.send_message(f"Hello, {select.values[0].mention}")
Additionally, you can use shortcut decorators to create a discord.ui.Select
with a predetermined discord.ComponentType
value. Using a shortcut decorator, the above code can be rewritten like this:
class MyView(discord.ui.View):
@discord.ui.user_select()
async def select_callback(self, select, interaction):
await interaction.response.send_message(f"Hello, {select.values[0].mention}")
Specifying Channel Types
When using a discord.ComponentType.channel_select
type select menu, you can pass in a list of discord.ChannelType
values to limit which types of channels users can choose when interacting with the select menu.
class MyView(discord.ui.View):
@discord.ui.select(
select_type=discord.ComponentType.channel_select,
channel_types=[discord.ChannelType.text, discord.ChannelType.voice]
)
async def select_callback(self, select, interaction):
await interaction.response.send_message(f"You selected {select.values[0].mention}")
Action Rows
We have discussed that Views can have 5 rows. Each row has 5 slots. A button takes a single slot, while a select menu takes up all 5 of them. This means a view can have a maximum of 5 select menus, or any possible combination of select menus and buttons.
The arrangement of buttons and select menus is generally adjusted by Pycord. However, it is possible to move them to specific relative rows. This is done by specifying the row
argument.
row
argumentThe row argument specifies the relative row this select menu belongs to. A Discord component can only have 5 rows. By default, items are arranged automatically into those 5 rows. If you’d like to control the relative positioning of the row then passing an index is advised. For example, row=1 will show up before row=2. Defaults to None, which is automatic ordering. The row number must be between 0 and 4 (i.e. zero-indexed).
class MyView(discord.ui.View):
@discord.ui.button(label="Button 1", row=0, style=discord.ButtonStyle.primary)
async def first_button_callback(self, button, interaction):
await interaction.response.send_message("You pressed me!")
@discord.ui.button(label="Button 2", row=0, style=discord.ButtonStyle.primary)
async def second_button_callback(self, button, interaction):
await interaction.response.send_message("You pressed me!")
@discord.ui.select(
row = 1,
options = [...]
)
async def select_callback(self, select, interaction):
await interaction.response.send_message(f"Awesome! I like {select.values[0]} too!")
Disabling Select Menus
Pre-Disabled Menus
class MyView(discord.ui.View):
@discord.ui.select(
disabled = True, # pass disabled=True to set the menu as disabled
options = [...]
)
async def select_callback(self, select, interaction):
...
@bot.command()
async def select_menu(ctx):
await ctx.send("Select and option from the menu!", view=MyView())
Disabling Menus on Press
- Disabling a single component
- Disabling all the components of a view
class MyView(discord.ui.View):
@discord.ui.select(options = [...])
async def select_callback(self, select, interaction):
select.disabled = True # set the status of the select as disabled
await interaction.response.edit_message(view=self) # edit the message to show the changes
class MyView(discord.ui.View):
@discord.ui.select(options = [...])
async def first_select_callback(self, select, interaction):
self.disable_all_items()
await interaction.response.edit_message(view=self) # edit the message to show the changes
@discord.ui.select(options = [...])
async def second_select_callback(self, select, interaction):
self.disable_all_items()
await interaction.response.edit_message(view=self)
Timeouts
You may want a select menu to automatically stop working after a certain amount of time. This is where timeouts come in.
- Specifying time when creating a view object
- Specifying the time when subclassing
class MyView(discord.ui.View):
async def on_timeout(self):
self.disable_all_items()
await self.message.edit(content="You took too long! Disabled all the components.", view=self)
@discord.ui.select(options = [...])
async def select_callback(self, select, interaction):
...
@bot.command()
async def select(ctx):
await ctx.send(view=MyView(timeout=30)) # specify the timeout here
class MyView(discord.ui.View):
def __init__(self):
super().__init__(timeout=10) # specify the timeout here
async def on_timeout(self):
self.disable_all_items()
await self.message.edit(content="You took too long! Disabled all the components.", view=self)
@discord.ui.select(options = [...])
async def select_callback(self, select, interaction):
...
Here, we disable all buttons and select menus in the view. Then, we edit the message to show that the timeout was reached.
If the on_timeout
coroutine is not present, the components will simply stop working after the specified time.
Persistent Views
Sometimes, instead of a select menu that is disabled after a certain amount of time, you want to have a select menu that is always working.
Normally, when the bot goes offline, all of its views stop working, even if they don't have a timeout. You will be able to see the views, but nothing will happen when you try to interact with them. This is a problem if you are trying to create a self-role system, for example. This is where persistent views come in.
Persistent views work forever. When the bot goes offline, the buttons and select menus will stop working. However, when the bot comes back online, the buttons and select menus will start working again.
In a Persistent View, the timeout must be set to None
and all the children in the view much have a custom_id
attribute set.
@bot.event
async def on_ready():
bot.add_view(MyView()) # Registers a View for persistent listening
class MyView(discord.ui.View):
def __init__(self):
super().__init__(timeout=None) # timeout of the view must be set to None
@discord.ui.select(custom_id="select-1", options = [...]) # a custom_id must be set
async def select_callback(self, select, interaction):
...
@bot.command()
async def select_menu(ctx):
await ctx.send(f"View persistence status: {MyView.is_persistent(MyView())}", view=MyView())
FAQ
How many select menus can I have in a message?
Each message can have a maximum of 5 select menus. Views can have up to 5 rows, and each row has 5 slots. A button takes up one slot, while a select menu takes up all five slots.
Can I add more than one view to a message?
No. As a Discord limitation, you can only have one view per message.
Why are UI Components so confusing?
They cannot be simple like commands. This system makes them flexible and doesn't limit your imagination. There are loads of different ways you can use UI Components. For example, you could subclass Buttons or Select Menus and add them to a view using the view's add_item
function.
UI Components aren't hard to use if you know Python. We recommend learning Object-Oriented Programming with Python.
What is OOP? What is subclassing?
OOP (object-oriented programming) is a programming paradigm that allows you to create objects that have
their own properties and methods. Almost everything in python is an object or a class. discord.Embed
and discord.ui.View
are both classes. When you use view = discord.ui.View()
to create a view, you are actually creating an object of type discord.ui.View
.
Subclassing is a Python OOP concept. It means that you can create a class that inherits from another class. In other words, the class that subclasses another class can inherit all the methods and attributes of that class.
We highly recommend you learn about basic Python concepts like classes and inheritance before you start learning Pycord.
Resources:
Do select menus need any special permissions?
No new permissions are needed in the bot or in the server.