Skip to main content

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.

Select Menu Types

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

String Select Menu

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

customId
string
Custom identifier. Auto-generated if onSelect is provided.
placeholder
string
Placeholder text shown when no option is selected
minValues
number
default:1
Minimum number of options that must be selected
maxValues
number
default:1
Maximum number of options that can be selected
disabled
boolean
default:false
Whether the select menu is disabled
children
StringSelectMenuOptionBuilder[]
The options to display in the select menu
onSelect
OnStringSelectMenuKitSubmit
Handler called when options are selected
onEnd
OnSelectMenuKitEnd
Handler called when the interaction collector ends
onError
EventInterceptorErrorHandler
Handler called when an error occurs
options
CommandKitSelectMenuBuilderInteractionCollectorDispatchContextData
Collector configuration options

String Select Option Props

label
string
required
The text displayed for this option
value
string
required
The value returned when this option is selected
description
string
Additional description text for this option
emoji
string | { id: string, name: string }
Emoji displayed next to the option
default
boolean
Whether this option is selected by default

User Select Menu

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:
defaultValues
string[] | Snowflake[]
User IDs to pre-select

Role Select Menu

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:
defaultValues
string[] | Snowflake[]
Role IDs to pre-select

Channel Select Menu

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:
channelTypes
ChannelType[]
Filter which channel types can be selected
defaultValues
string[] | Snowflake[]
Channel IDs to pre-select

Mentionable Select Menu

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.

Select Menu Limitations

  • 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

Disabled Select Menus

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

Build docs developers (and LLMs) love