Overview
Select menus (also called dropdowns) allow users to choose one or more options from a list. CommandKit provides five types of select menus for different use cases.
String Select Choose from custom text options
User Select Select Discord users
Role Select Select Discord roles
Channel Select Select Discord channels
Mentionable Select Select users or roles
The most common type - allows users to select from custom options:
import {
ActionRow ,
StringSelectMenu ,
StringSelectMenuOption ,
OnStringSelectMenuKitSubmit ,
ChatInputCommand ,
} from 'commandkit' ;
const handleSelect : OnStringSelectMenuKitSubmit = async (
interaction ,
context ,
) => {
const selections = interaction . values . join ( ', ' );
await interaction . reply ({
content: `You selected: ${ selections } ` ,
});
context . dispose ();
};
export const chatInput : ChatInputCommand = async ( ctx ) => {
const select = (
< ActionRow >
< StringSelectMenu onSelect = { handleSelect } placeholder = "Choose a Pokemon" >
< StringSelectMenuOption
label = "Pikachu"
value = "pikachu"
description = "Pikachu is electric"
emoji = "⚡"
/>
< StringSelectMenuOption
label = "Charmander"
value = "charmander"
description = "Charmander is fire"
emoji = "🔥"
/>
< StringSelectMenuOption
label = "Squirtle"
value = "squirtle"
description = "Squirtle is water"
emoji = "💧"
/>
< StringSelectMenuOption
label = "Bulbasaur"
value = "bulbasaur"
description = "Bulbasaur is grass"
emoji = "🌱"
/>
</ StringSelectMenu >
</ ActionRow >
);
await ctx . interaction . reply ({
components: [ select ],
content: 'Select your starter Pokemon:' ,
});
};
String Select Props
Custom identifier. Auto-generated if onSelect is provided.
Placeholder text shown when no option is selected
Minimum number of options that must be selected
Maximum number of options that can be selected
Whether the select menu is disabled
children
StringSelectMenuOptionBuilder[]
The options to display in the select menu
onSelect
OnStringSelectMenuKitSubmit
Handler called when options are selected
Handler called when the interaction collector ends
onError
EventInterceptorErrorHandler
Handler called when an error occurs
options
CommandKitSelectMenuBuilderInteractionCollectorDispatchContextData
Collector configuration options
String Select Option Props
The text displayed for this option
The value returned when this option is selected
Additional description text for this option
emoji
string | { id: string, name: string }
Emoji displayed next to the option
Whether this option is selected by default
Allows users to select Discord members:
import {
ActionRow ,
UserSelectMenu ,
OnUserSelectMenuKitSubmit ,
ChatInputCommand ,
} from 'commandkit' ;
const handleSelect : OnUserSelectMenuKitSubmit = async (
interaction ,
context ,
) => {
const users = interaction . users . map (( user ) => user . toString ()). join ( ', ' );
await interaction . reply ({
content: `You selected: ${ users } ` ,
});
context . dispose ();
};
export const chatInput : ChatInputCommand = async ( ctx ) => {
const select = (
< ActionRow >
< UserSelectMenu
onSelect = { handleSelect }
placeholder = "Select a user"
minValues = { 1 }
maxValues = { 3 }
/>
</ ActionRow >
);
await ctx . interaction . reply ({
components: [ select ],
content: 'Select up to 3 users:' ,
});
};
User Select Props
Same as String Select, plus:
Allows users to select Discord roles:
import {
ActionRow ,
RoleSelectMenu ,
OnRoleSelectMenuKitSubmit ,
} from 'commandkit' ;
const handleSelect : OnRoleSelectMenuKitSubmit = async (
interaction ,
context ,
) => {
const roles = interaction . roles . map (( role ) => role . toString ()). join ( ', ' );
await interaction . reply ({
content: `You selected: ${ roles } ` ,
});
context . dispose ();
};
const select = (
< ActionRow >
< RoleSelectMenu
onSelect = { handleSelect }
placeholder = "Select roles"
/>
</ ActionRow >
);
Role Select Props
Same as String Select, plus:
Allows users to select Discord channels:
import {
ActionRow ,
ChannelSelectMenu ,
OnChannelSelectMenuKitSubmit ,
ChatInputCommand ,
} from 'commandkit' ;
import { ChannelType } from 'discord.js' ;
const handleSelect : OnChannelSelectMenuKitSubmit = async (
interaction ,
context ,
) => {
const channels = interaction . channels . map (( ch ) => ch . toString ()). join ( ', ' );
await interaction . reply ({
content: `You selected: ${ channels } ` ,
});
context . dispose ();
};
export const chatInput : ChatInputCommand = async ( ctx ) => {
const select = (
< ActionRow >
< ChannelSelectMenu
onSelect = { handleSelect }
placeholder = "Select a channel"
channelTypes = { [ ChannelType . GuildText , ChannelType . GuildAnnouncement ] }
/>
</ ActionRow >
);
await ctx . interaction . reply ({
components: [ select ],
content: 'Select a text channel:' ,
});
};
Channel Select Props
Same as String Select, plus:
Filter which channel types can be selected
Channel IDs to pre-select
Allows users to select both users and roles:
import {
ActionRow ,
MentionableSelectMenu ,
OnMentionableSelectMenuKitSubmit ,
} from 'commandkit' ;
const handleSelect : OnMentionableSelectMenuKitSubmit = async (
interaction ,
context ,
) => {
const users = interaction . users . map (( u ) => u . toString ());
const roles = interaction . roles . map (( r ) => r . toString ());
const all = [ ... users , ... roles ]. join ( ', ' );
await interaction . reply ({
content: `You selected: ${ all } ` ,
});
context . dispose ();
};
const select = (
< ActionRow >
< MentionableSelectMenu
onSelect = { handleSelect }
placeholder = "Select users or roles"
/>
</ ActionRow >
);
Multi-Select
Allow users to select multiple options:
< StringSelectMenu
onSelect = { handleSelect }
minValues = { 1 }
maxValues = { 5 }
placeholder = "Select up to 5 options"
>
< StringSelectMenuOption label = "Option 1" value = "1" />
< StringSelectMenuOption label = "Option 2" value = "2" />
< StringSelectMenuOption label = "Option 3" value = "3" />
< StringSelectMenuOption label = "Option 4" value = "4" />
< StringSelectMenuOption label = "Option 5" value = "5" />
</ StringSelectMenu >
Handling Multiple Values
const handleSelect : OnStringSelectMenuKitSubmit = async (
interaction ,
context ,
) => {
// interaction.values is an array
const values = interaction . values ;
await interaction . reply ({
content: `You selected ${ values . length } options: ${ values . join ( ', ' ) } ` ,
});
context . dispose ();
};
Select Handlers
All select menus use the same handler pattern:
import { OnStringSelectMenuKitSubmit } from 'commandkit' ;
const handleSelect : OnStringSelectMenuKitSubmit = async (
interaction ,
context ,
) => {
// Access selected values
const values = interaction . values ; // string[]
// For user/role/channel selects, access the resolved entities
const users = interaction . users ; // Collection<Snowflake, User>
const roles = interaction . roles ; // Collection<Snowflake, Role>
const channels = interaction . channels ; // Collection<Snowflake, Channel>
// Respond to the user
await interaction . reply ({
content: `Selected: ${ values . join ( ', ' ) } ` ,
});
// Update the select menu
context . setDisabled ( true );
// Clean up the collector
context . dispose ();
};
Real-World Examples
Role Assignment
import { RoleSelectMenu , OnRoleSelectMenuKitSubmit } from 'commandkit' ;
const handleRoleSelect : OnRoleSelectMenuKitSubmit = async (
interaction ,
context ,
) => {
const member = interaction . member ;
const roles = interaction . roles ;
// Add selected roles to member
for ( const role of roles . values ()) {
await member . roles . add ( role );
}
await interaction . reply ({
content: `Added ${ roles . size } role(s) to you!` ,
flags: MessageFlags . Ephemeral ,
});
context . dispose ();
};
const select = (
< ActionRow >
< RoleSelectMenu
onSelect = { handleRoleSelect }
placeholder = "Select roles to add"
maxValues = { 5 }
/>
</ ActionRow >
);
Category Selection
import { StringSelectMenu , StringSelectMenuOption } from 'commandkit' ;
const categories = [
{ name: 'Bug Report' , value: 'bug' , emoji: '🐛' },
{ name: 'Feature Request' , value: 'feature' , emoji: '✨' },
{ name: 'Question' , value: 'question' , emoji: '❓' },
{ name: 'Feedback' , value: 'feedback' , emoji: '💬' },
];
const select = (
< ActionRow >
< StringSelectMenu onSelect = { handleSelect } placeholder = "Select a category" >
{ categories . map (( cat ) => (
< StringSelectMenuOption
key = { cat . value }
label = { cat . name }
value = { cat . value }
emoji = { cat . emoji }
/>
)) }
</ StringSelectMenu >
</ ActionRow >
);
Collector Options
Customize select menu behavior:
const options = {
// How long the collector stays active
time: 5 * 60 * 1000 , // 5 minutes
// Reset timer on each interaction
autoReset: false ,
// Only allow one selection
once: true ,
// Filter which interactions are handled
filter : ( interaction ) => {
return interaction . user . id === userId ;
},
};
< StringSelectMenu onSelect = { handleSelect } options = { options } >
{ /* ... */ }
</ StringSelectMenu >
Best Practices
Provide clear placeholders - Help users understand what they’re selecting.
Use appropriate select types - StringSelect for custom options, UserSelect for members, etc.
Limit options - Keep string select menus under 25 options for better UX.
Dispose collectors - Always call context.dispose() after handling selections.
Set min/max values wisely - Use appropriate limits for multi-select menus.
Maximum 25 options per StringSelectMenu
Maximum 1 select menu per ActionRow
Option labels are limited to 100 characters
Option descriptions are limited to 100 characters
Placeholders are limited to 150 characters
Custom IDs are limited to 100 characters
Maximum 25 options can be selected at once
Disable a select menu to prevent interactions:
< StringSelectMenu disabled placeholder = "Currently unavailable" >
< StringSelectMenuOption label = "Option 1" value = "1" />
</ StringSelectMenu >
Dynamically Disable
const handleSelect : OnStringSelectMenuKitSubmit = async (
interaction ,
context ,
) => {
// Disable after first selection
context . setDisabled ( true );
await interaction . update ({
components: [ /* updated components */ ],
});
};
See Also