Skip to main content

Buttons

Buttons are defined via .setButtons() in embeds mode, or via the button() helper in layout mode. Each button is a config object with a label, style, and an action or link.

Button properties

PropertyTypeRequiredDescription
labelstringYesText displayed on the button
styleButtonStyleYesButton color (see below)
actionActionConditionalCallback executed on click
opensModalboolean | stringConditionalTrigger a modal instead of running an action
urlstringConditionalURL for link buttons — requires ButtonStyle.Link
disabledbooleanNoRenders the button as unclickable
emojistringNoEmoji displayed on the button
idstringNoCustom component ID — auto-assigned if omitted
fixedPosition'start' | 'end'NoPins button across pagination pages (embeds mode only)

action or opensModal must be present unless disabled: true or the button is a link button.

Button styles

ButtonStyle is imported from discord.js:

import { ButtonStyle } from 'discord.js';
StyleColorUse
ButtonStyle.PrimaryBluePrimary action
ButtonStyle.SecondaryGreySecondary or neutral action
ButtonStyle.SuccessGreenConfirm, save, submit
ButtonStyle.DangerRedDelete, remove, destructive action
ButtonStyle.LinkNo backgroundURL link — no interaction sent to bot

Basic usage

.setButtons((ctx) => [
{
label: 'Confirm',
style: ButtonStyle.Success,
action: async (ctx) => {
await db.save(ctx.state.get('item'));
await ctx.goTo('confirmation');
},
},
{
label: 'Cancel',
style: ButtonStyle.Danger,
action: closeMenu(),
},
])

Link buttons navigate the user to a URL without sending an interaction to your bot. They require ButtonStyle.Link and a urlaction and opensModal cannot be set alongside url.

{
label: 'View on GitHub',
style: ButtonStyle.Link,
url: 'https://github.com/flowcord-dev/flowcord-core',
}

Disabled buttons

Set disabled: true to render a button as greyed out and unclickable. Useful for showing unavailable options conditionally:

{
label: 'Purchase',
style: ButtonStyle.Success,
disabled: ctx.state.get('coins') < item.price,
action: async (ctx) => { /* ... */ },
}

A disabled button does not require action or opensModal — the validator skips that check when disabled: true. You can omit both if the button is purely decorative (e.g., a greyed-out label for context).

To open a modal when a button is clicked, set opensModal instead of action. When both are set, opensModal takes precedence.

// Single modal — opensModal: true
{
label: 'Fill Out Form',
style: ButtonStyle.Primary,
opensModal: true,
}

// Multi-modal — opensModal: 'modal-id'
{
label: 'Edit Details',
style: ButtonStyle.Secondary,
opensModal: 'edit-details',
}

See Modals for how to define the modal the button triggers.

Fixed position buttons (pagination)

Button pagination activates automatically when the total number of buttons would exceed Discord's component limits. FlowCord splits the overflow into pages and injects Next and Previous navigation buttons — no additional configuration required.

When pagination is active, all buttons cycle through pages by default. fixedPosition pins a button to the start or end of every page so it's always visible regardless of which page is active.

.setButtons((ctx) => [
{
label: '← Back to Menu',
style: ButtonStyle.Secondary,
fixedPosition: 'start', // Always visible, doesn't paginate
action: goBack(),
},
// ... paginated buttons in between ...
{
label: 'Done',
style: ButtonStyle.Primary,
fixedPosition: 'end', // Always visible at the end
action: closeMenu(),
},
], { pagination: { perPage: 5 } })

Fixed buttons count toward the total button limit per page.