Skip to content

Modals & Select Menus

cordless has builders for all five Discord select menu kinds: StringSelect, where you supply the options, and four “entity” selects that resolve to real Discord objects: UserSelect, RoleSelect, ChannelSelect, MentionableSelect.

from cordless import StringSelect, SelectOption, ActionRow
@bot.command("class", description="Pick a class")
async def pick_class(ctx):
await ctx.send(
"Choose your class:",
components=[ActionRow([
StringSelect("class_select", [
SelectOption("Warrior", "warrior", description="Tank"),
SelectOption("Mage", "mage", description="Ranged damage"),
])
])],
)
@bot.select("class_select")
async def class_selected(ctx):
choice = ctx.values[0] # ctx.values is always a list, even for single-select
await ctx.edit(content=f"You picked {choice}!")

Entity selects work the same way, just without an options list. Discord resolves the user’s picks for you:

from cordless import UserSelect, RoleSelect, ChannelSelect, MentionableSelect
UserSelect("pick_user", placeholder="Choose a member")
RoleSelect("pick_role", min_values=1, max_values=3)
ChannelSelect("pick_channel", channel_types=[0, 2]) # text, voice
MentionableSelect("pick_anyone")

ctx.values holds the selected ids for every select type. min_values/max_values (both default 1) control how many picks are required/allowed: set max_values above 1 for multi-select.

Same as buttons: set defer=True for select handlers that take a while:

@bot.select("slow_select", defer=True)
async def slow_select(ctx):
...

Modals pop up a form. Build one with Modal and one or more TextInput fields, and send it with ctx.send_modal(). This must be the first response to the interaction (you can’t defer, then show a modal):

from cordless import Modal, TextInput, TextInputStyle
@bot.command("feedback", description="Leave feedback")
async def feedback(ctx):
await ctx.send_modal(Modal(
"feedback_modal", "Send Feedback",
TextInput("summary", "Summary", style=TextInputStyle.SHORT),
TextInput("details", "Details", style=TextInputStyle.PARAGRAPH, required=False),
))
@bot.modal("feedback_modal")
async def feedback_submitted(ctx):
summary = ctx.modal_values["summary"]
details = ctx.modal_values["details"]
await ctx.send(f"Thanks! Got: {summary}")

ctx.modal_values is a flat dict of custom_id → submitted value for every field in the modal.

TextInput(custom_id, label, style=1, min_length=None, max_length=None, required=True, value=None, placeholder=None): style is TextInputStyle.SHORT (single line) or TextInputStyle.PARAGRAPH (multi-line); value pre-fills the field.

@bot.modal("slow_modal", defer=True)
async def slow_modal(ctx):
...