Components

Buttons

Learn how to create and use button components with djs-core.

Buttons are interactive components that users can click to trigger actions in your Discord bot.

Buttons are automatically registered when placed in src/components/buttons/. The customId is generated from the file path, so you don't need to set it manually.

Creating a Button

Buttons in djs-core are created using the Button class. Each button component file in src/components/buttons/ is automatically registered.

Basic Button

import { Button } from "@djs-core/runtime";
import { ButtonStyle } from "discord.js";

export default new Button()
    .setLabel("Click me!")
    .setStyle(ButtonStyle.Primary)
    .setCustomId("my-button")
    .run(async (interaction) => {
        await interaction.reply("Button clicked!");
    });

Button Styles

Discord provides several button styles to visually communicate different actions:

ButtonStyle.Primary
string
Blurple button - Use for primary actions like "Confirm" or "Submit"
ButtonStyle.Secondary
string
Gray button - Use for secondary actions or neutral options
ButtonStyle.Success
string
Green button - Use for positive actions like "Accept" or "Save"
ButtonStyle.Danger
string
Red button - Use for destructive actions like "Delete" or "Remove"
ButtonStyle.Link
string
Link button - Navigates to external URLs (doesn't trigger interaction events)

Example with Different Styles

import { Button } from "@djs-core/runtime";
import { ButtonStyle } from "discord.js";

export default new Button()
  .setLabel("Confirm")
  .setStyle(ButtonStyle.Primary)
  .setCustomId("confirm-button")
  .run(async (interaction) => {
    await interaction.reply("Confirmed!");
  });

Using Buttons in Commands

Buttons are typically sent as part of a command or context menu response:

import { Command, Button } from "@djs-core/runtime";
import { ButtonStyle, ActionRowBuilder } from "discord.js";
import myButton from "../../components/buttons/my-button";

export default new Command()
    .setDescription("Show a button")
    .run(async (interaction) => {
        const row = new ActionRowBuilder<Button>().addComponents(myButton);
        
        await interaction.reply({
            content: "Click the button below!",
            components: [row],
        });
    });

Button Data

Buttons can receive custom data that is passed to the handler when clicked. Important: The data is not set in the component definition, but rather when you use the component from a command or another component.

Component Definition (without data)

In your component file, define the button with the expected data type, but don't set the data:

src/components/buttons/confirm.ts
import { Button } from "@djs-core/runtime";
import { ButtonStyle } from "discord.js";

export default new Button<{ userId: string; action: string }>()
    .setLabel("Confirm")
    .setStyle(ButtonStyle.Success)
    .setCustomId("confirm-button")
    .run(async (interaction, data) => {
        await interaction.reply(
            `Processing ${data.action} for user ${data.userId}`,
        );
    });

Using Button with Data

When using the button from a command, set the data directly on the imported component:

src/interactions/commands/delete.ts
import { Command, Button } from "@djs-core/runtime";
import { ActionRowBuilder } from "discord.js";
import confirmButton from "../../components/buttons/confirm";

export default new Command()
    .setDescription("Delete an item")
    .run(async (interaction) => {
        const userId = interaction.user.id;
        
        // Set data when using the button
        const row = new ActionRowBuilder<Button>().addComponents(
            confirmButton.setData({ userId, action: "delete" }),
        );
        
        await interaction.reply({
            content: "Are you sure you want to delete?",
            components: [row],
        });
    });

Dynamic Data from Command Context

You can pass dynamic data based on the command context:

import { Command, Button } from "@djs-core/runtime";
import { ActionRowBuilder } from "discord.js";
import confirmButton from "../../components/buttons/confirm";

export default new Command()
    .setDescription("Delete item")
    .addStringOption((option) =>
        option
            .setName("item-id")
            .setDescription("The item ID to delete")
            .setRequired(true),
    )
    .run(async (interaction) => {
        const itemId = interaction.options.getString("item-id");
        const userId = interaction.user.id;
        
        // Set data dynamically based on command input
        const row = new ActionRowBuilder<Button>().addComponents(
            confirmButton.setData({ userId, action: `delete_item_${itemId}` }),
        );
        
        await interaction.reply({
            content: `Are you sure you want to delete item ${itemId}?`,
            components: [row],
        });
    });

Data Time-to-Live (TTL)

You can optionally specify a time-to-live (in seconds) for the data:

import { Command, Button } from "@djs-core/runtime";
import { ActionRowBuilder } from "discord.js";
import confirmButton from "../../components/buttons/confirm";

export default new Command()
    .setDescription("Time-limited action")
    .run(async (interaction) => {
        // Data expires after 60 seconds
        const row = new ActionRowBuilder<Button>().addComponents(
            confirmButton.setData({ userId: interaction.user.id }, 60),
        );
        
        await interaction.reply({
            content: "This button will expire in 60 seconds",
            components: [row],
        });
    });

Button Emojis

You can add emojis to buttons:

import { Button } from "@djs-core/runtime";
import { ButtonStyle } from "discord.js";

export default new Button()
    .setLabel("React")
    .setStyle(ButtonStyle.Primary)
    .setCustomId("react-button")
    .setEmoji("👍")
    .run(async (interaction) => {
        await interaction.reply("Thanks!");
    });

Disabled Buttons

Buttons can be disabled:

import { Button } from "@djs-core/runtime";
import { ButtonStyle } from "discord.js";

export default new Button()
    .setLabel("Disabled")
    .setStyle(ButtonStyle.Secondary)
    .setCustomId("disabled-button")
    .setDisabled(true)
    .run(async (interaction) => {
        await interaction.reply("This won't be called when disabled");
    });

Ephemeral Responses

Button responses can be ephemeral (only visible to the user who clicked):

import { Button } from "@djs-core/runtime";
import { ButtonStyle, MessageFlags } from "discord.js";

export default new Button()
    .setLabel("Private Action")
    .setStyle(ButtonStyle.Primary)
    .setCustomId("private-button")
    .run(async (interaction) => {
        await interaction.reply({
            content: "This is only visible to you!",
            flags: [MessageFlags.Ephemeral],
        });
    });

Button Organization

Buttons can be organized in subdirectories:

src/components/buttons/
  ├── confirm.ts
  ├── cancel.ts
  └── admin/
      ├── delete.ts
      └── ban.ts

Link buttons navigate to external URLs and don't trigger interaction events. Since they don't have a .run() handler, they're often simpler to create.

If a link button is only used in one place, you can create it directly in your command without creating a separate component file:

src/interactions/commands/help.ts
import { Command, Button } from "@djs-core/runtime";
import { ButtonStyle, ActionRowBuilder } from "discord.js";

export default new Command()
    .setDescription("Visit our website")
    .run(async (interaction) => {
        // Create the link button directly in the command
        const linkButton = new Button()
            .setLabel("Visit Website")
            .setStyle(ButtonStyle.Link)
            .setURL("https://example.com");
        
        const row = new ActionRowBuilder<Button>().addComponents(linkButton);
        
        await interaction.reply({
            content: "Check out our website!",
            components: [row],
        });
    });

If you need to reuse a link button across multiple commands, create it as a separate component file:

src/components/buttons/website-link.ts
import { Button } from "@djs-core/runtime";
import { ButtonStyle } from "discord.js";

export default new Button()
    .setLabel("Visit Website")
    .setStyle(ButtonStyle.Link)
    .setURL("https://example.com");
// Note: Link buttons don't have a .run() handler

Then import and use it in your commands:

src/interactions/commands/info.ts
import { Command, Button } from "@djs-core/runtime";
import { ActionRowBuilder } from "discord.js";
import websiteLink from "../../components/buttons/website-link";

export default new Command()
    .setDescription("Get help")
    .run(async (interaction) => {
        const row = new ActionRowBuilder<Button>().addComponents(websiteLink);
        await interaction.reply({
            content: "Visit our website for more information!",
            components: [row],
        });
    });