Discord components let users interact with your bot through buttons, select menus, and other UI elements.
Action Rows
Components must be placed in action rows. Each message can have up to 5 action rows.
const row = actionRow()
.addComponents(
button().setCustomId('yes').setLabel('Yes').setStyle(ButtonStyle.Success),
button().setCustomId('no').setLabel('No').setStyle(ButtonStyle.Danger)
)
await ctx.reply({
content: 'Do you agree?',
components: [row.toJSON()]
})
const btn = button()
.setCustomId('click_me')
.setLabel('Click Me')
.setStyle(ButtonStyle.Primary)
Primary
Secondary
Success
Danger
button()
.setCustomId('primary')
.setLabel('Primary')
.setStyle(ButtonStyle.Primary) // Blurple
button()
.setCustomId('secondary')
.setLabel('Secondary')
.setStyle(ButtonStyle.Secondary) // Gray
button()
.setCustomId('success')
.setLabel('Success')
.setStyle(ButtonStyle.Success) // Green
button()
.setCustomId('danger')
.setLabel('Danger')
.setStyle(ButtonStyle.Danger) // Red
Buttons that open URLs don’t need custom IDs:
button()
.setUrl('https://flora.dev')
.setLabel('Documentation')
button()
.setCustomId('like')
.setLabel('Like')
.setEmoji({ name: '👍' })
.setStyle(ButtonStyle.Primary)
button()
.setCustomId('disabled')
.setLabel('Unavailable')
.setDisabled(true)
.setStyle(ButtonStyle.Secondary)
String Select
Let users pick from predefined options:
const select = stringSelect('role_select')
.setPlaceholder('Choose a role')
.addOptions(
{ label: 'Developer', value: 'dev', emoji: { name: '💻' } },
{ label: 'Designer', value: 'design', emoji: { name: '🎨' } },
{ label: 'Manager', value: 'manager', emoji: { name: '📊' } }
)
const row = actionRow().addComponents(select)
await ctx.reply({
content: 'Select your role:',
components: [row.toJSON()]
})
User Select
Let users pick other users:
const select = userSelect('user_picker')
.setPlaceholder('Select a user')
.setMinValues(1)
.setMaxValues(5)
Role Select
Let users pick roles:
const select = roleSelect('role_picker')
.setPlaceholder('Select roles')
.setMaxValues(3)
Channel Select
Let users pick channels:
const select = channelSelect('channel_picker')
.setPlaceholder('Select a channel')
Mentionable Select
Let users pick users or roles:
const select = mentionableSelect('mention_picker')
.setPlaceholder('Select users or roles')
Input Text
Text input fields (used in modals):
const input = inputText('feedback_text')
.setStyle(InputTextStyles.Paragraph)
.setPlaceholder('Enter your feedback...')
.setMinLength(10)
.setMaxLength(500)
.setRequired(true)
inputText('name')
.setStyle(InputTextStyles.Short) // Single line
.setPlaceholder('Your name')
inputText('description')
.setStyle(InputTextStyles.Paragraph) // Multi-line
.setPlaceholder('Detailed description')
Components V2
Discord’s new component system supports advanced layouts. Use the IS_COMPONENTS_V2 flag:
const card = container()
.setAccentColor(0x3366ff)
.addComponents(
section()
.addComponents(textDisplay('Flora Runtime'))
.setAccessory(thumbnail('https://example.com/logo.png'))
)
await ctx.reply({
components: [card.toJSON()],
flags: MessageFlags.IS_COMPONENTS_V2
})
Container
Top-level wrapper for V2 components:
container()
.setAccentColor(0x3366ff)
.setSpoiler(true)
.addComponents(
// sections, separators, media galleries, etc.
)
Section
Groups content with optional accessory:
section()
.addComponents(
textDisplay('Welcome to Flora!'),
textDisplay('Build powerful Discord bots')
)
.setAccessory(thumbnail('https://example.com/icon.png'))
Text Display
textDisplay('This is display text')
Thumbnail
thumbnail('https://example.com/image.png')
.setDescription('Image description')
.setSpoiler(true)
Display multiple images:
mediaGallery()
.addItem('https://example.com/img1.png', { description: 'First image' })
.addItem('https://example.com/img2.png', { spoiler: true })
.addItem('https://example.com/img3.png')
Separator
Visual divider:
separator(true) // With divider line
separator(false) // Just spacing
separator()
.setDivider(true)
.setSpacing('large')
File
file('https://example.com/document.pdf')
.setSpoiler(true)
Label
Label for form elements:
label('Email Address')
.setDescription('Your contact email')
.setComponent(inputText('email'))
File Upload
Let users upload files:
fileUpload('attachment_input')
.setMinValues(1)
.setMaxValues(5)
.setRequired(true)
Handling Interactions
Listen for component interactions:
on('componentInteraction', async (ctx) => {
const customId = ctx.msg.data?.custom_id
if (customId === 'yes') {
await ctx.reply({ content: 'You clicked Yes!', ephemeral: true })
} else if (customId === 'no') {
await ctx.reply({ content: 'You clicked No!', ephemeral: true })
}
})
on('componentInteraction', async (ctx) => {
if (ctx.msg.data?.custom_id === 'role_select') {
const values = ctx.msg.data?.values || []
await ctx.reply({
content: `You selected: ${values.join(', ')}`,
ephemeral: true
})
}
})
Complete Example
const confirm = slash({
name: 'confirm',
description: 'Show a confirmation dialog',
run: async (ctx) => {
const row = actionRow().addComponents(
button()
.setCustomId('confirm_yes')
.setLabel('Confirm')
.setStyle(ButtonStyle.Success),
button()
.setCustomId('confirm_no')
.setLabel('Cancel')
.setStyle(ButtonStyle.Danger)
)
await ctx.reply({
content: 'Are you sure you want to proceed?',
components: [row.toJSON()]
})
}
})
on('componentInteraction', async (ctx) => {
const customId = ctx.msg.data?.custom_id
if (customId === 'confirm_yes') {
await ctx.reply({ content: 'Action confirmed!', ephemeral: true })
} else if (customId === 'confirm_no') {
await ctx.reply({ content: 'Action cancelled.', ephemeral: true })
}
})
createBot({ slashCommands: [confirm] })
Available Builders
Container for up to 5 buttons or 1 select menu.
Select menu with custom options.
Select menu for channels.
mentionableSelect(id)
MentionableSelectMenuBuilder
Select menu for users and roles.
Best Practices
Use descriptive custom IDs
Make IDs descriptive: delete_message_123 instead of btn1.
Limit button count
Max 5 buttons per row, 25 per message. Keep interfaces simple.
Provide visual feedback
Always reply to interactions, even if ephemeral.
Use appropriate styles
Match button colors to actions: red for delete, green for confirm.