Skip to main content

Overview

Buttons are interactive components that users can click to trigger actions. CommandKit’s Button component supports all Discord button styles, emojis, and automatic interaction handling.

Basic Usage

import { ActionRow, Button } from 'commandkit';
import { ButtonStyle } from 'discord.js';

const buttons = (
  <ActionRow>
    <Button style={ButtonStyle.Primary}>Primary</Button>
    <Button style={ButtonStyle.Secondary}>Secondary</Button>
    <Button style={ButtonStyle.Success}>Success</Button>
    <Button style={ButtonStyle.Danger}>Danger</Button>
  </ActionRow>
);

await interaction.reply({
  content: 'Choose an option:',
  components: [buttons],
});

Button Styles

Discord provides 5 button styles:
<Button style={ButtonStyle.Primary}>Primary Button</Button>
Blurple color - used for main actions

Props

style
ButtonStyle
default:"ButtonStyle.Primary"
The visual style of the button
customId
string
Custom identifier for the button. Auto-generated if onClick is provided.
label
string
The text displayed on the button. Can also be set via children.
children
string | number | boolean
Alternative way to set the button label
emoji
ComponentEmojiResolvable
Emoji to display on the button (Unicode or custom emoji)
disabled
boolean
default:false
Whether the button is disabled and cannot be clicked
url
string
URL for link-style buttons (requires style={ButtonStyle.Link})
onClick
OnButtonKitClick
Handler called when the button is clicked
onError
EventInterceptorErrorHandler
Handler called when an error occurs in the interaction collector
onEnd
OnButtonKitEnd
Handler called when the interaction collector ends
options
CommandKitButtonBuilderInteractionCollectorDispatchContextData
Configuration for the interaction collector (timeout, filters, etc.)

Click Handlers

Basic Handler

import { OnButtonKitClick } from 'commandkit';
import { MessageFlags } from 'discord.js';

const handleClick: OnButtonKitClick = async (interaction, context) => {
  await interaction.reply({
    content: 'Button clicked!',
    flags: MessageFlags.Ephemeral,
  });
  
  // Dispose of the interaction collector
  context.dispose();
};

<Button onClick={handleClick}>Click Me</Button>

Handler with Context

The second parameter provides access to the ButtonKit instance:
const handleClick: OnButtonKitClick = async (interaction, context) => {
  // Update the button
  context.setDisabled(true);
  context.setLabel('Clicked!');
  
  await interaction.update({
    components: [/* updated components */],
  });
  
  // Clean up after 5 seconds
  setTimeout(() => context.dispose(), 5000);
};

Real-World Examples

Confirmation Dialog

import {
  Button,
  ActionRow,
  CommandData,
  OnButtonKitClick,
  ChatInputCommand,
} from 'commandkit';
import { ButtonStyle, MessageFlags } from 'discord.js';

export const command: CommandData = {
  name: 'delete',
  description: 'Delete an item with confirmation',
};

const handleConfirm: OnButtonKitClick = async (interaction, context) => {
  await interaction.reply({
    content: 'The item was deleted successfully.',
    flags: MessageFlags.Ephemeral,
  });

  context.dispose();
};

const handleCancel: OnButtonKitClick = async (interaction, context) => {
  await interaction.reply({
    content: 'The item was not deleted.',
    flags: MessageFlags.Ephemeral,
  });

  context.dispose();
};

export const chatInput: ChatInputCommand = async ({ interaction }) => {
  const buttons = (
    <ActionRow>
      <Button onClick={handleCancel} style={ButtonStyle.Primary}>
        Cancel
      </Button>
      <Button onClick={handleConfirm} style={ButtonStyle.Danger}>
        Confirm
      </Button>
    </ActionRow>
  );

  await interaction.reply({
    content: 'Are you sure you want to delete this item?',
    components: [buttons],
  });
};

Button Grid

const handleButtonClick: OnButtonKitClick = async (interaction) => {
  const { customId } = interaction;

  await interaction.reply({
    content: `You clicked button "${customId}"!`,
    flags: MessageFlags.Ephemeral,
  });
};

function ButtonGrid() {
  return (
    <>
      {Array.from({ length: 5 }, (_, i) => (
        <ActionRow key={i}>
          {Array.from({ length: 5 }, (_, j) => (
            <Button
              key={j}
              onClick={handleButtonClick}
              customId={`button-${i}-${j}`}
            >
              {i * 5 + j + 1}
            </Button>
          ))}
        </ActionRow>
      ))}
    </>
  );
}
<ActionRow>
  <Button style={ButtonStyle.Link} url="https://commandkit.dev">
    Documentation
  </Button>
  <Button style={ButtonStyle.Link} url="https://discord.gg/commandkit">
    Support Server
  </Button>
</ActionRow>

Buttons with Emojis

Unicode Emoji

<Button emoji="⚡" style={ButtonStyle.Primary}>
  Electric
</Button>

Custom Emoji

<Button 
  emoji={{ id: '123456789', name: 'customEmoji' }}
  style={ButtonStyle.Primary}
>
  Custom
</Button>

Emoji Only

<Button emoji="🔥" style={ButtonStyle.Primary} />

Disabled Buttons

<ActionRow>
  <Button disabled>Cannot Click</Button>
  <Button>Can Click</Button>
</ActionRow>

Interaction Collector Options

Customize how long buttons remain active and how they behave:
const options = {
  // How long the collector stays active (default: 5 minutes)
  time: 10 * 60 * 1000, // 10 minutes
  
  // Reset timer on each interaction (default: true)
  autoReset: true,
  
  // Only allow one interaction (default: false)
  once: false,
  
  // Filter which interactions are handled
  filter: (interaction) => interaction.user.id === userId,
  
  // Called when collector ends
  onEnd: (reason) => console.log(`Ended: ${reason}`),
};

<Button onClick={handleClick} options={options}>
  Click Me
</Button>

Best Practices

Always dispose collectors - Call context.dispose() in your click handlers to clean up interaction collectors and prevent memory leaks.
Use appropriate styles - Choose button styles that match the action’s intent (e.g., Danger for destructive actions).
Limit buttons per row - Discord allows a maximum of 5 buttons per ActionRow.
Handle errors gracefully - Use the onError prop to handle interaction collector errors.

Button Limitations

  • Maximum 5 buttons per ActionRow
  • Maximum 5 ActionRows per message (25 buttons total)
  • Link buttons cannot have click handlers
  • Button labels are limited to 80 characters
  • Custom IDs are limited to 100 characters

See Also

Build docs developers (and LLMs) love