Components

Athena ships two builders: ComponentBuilder for classic components (V1) and NewComponentBuilder for Components V2. Use NewComponentBuilder for anything...

Athena ships two builders: ComponentBuilder for classic components (V1) and NewComponentBuilder for Components V2. Use NewComponentBuilder for anything new; it supports containers, sections, separators, media galleries, and files alongside the classic rows.

Classic components (V1)

Up to five action rows, attached as components.

import { ComponentBuilder, ButtonStyle } from 'athena';
 
const components = new ComponentBuilder().addActionRow((row) =>
  row
    .addNormalButton({ custom_id: 'confirm', style: ButtonStyle.Success, label: 'Confirm' })
    .addNormalButton({ custom_id: 'cancel', style: ButtonStyle.Danger, label: 'Cancel' })
);
 
await client.createMessage(channelID, { content: 'Proceed?', components: components.toJSON() });

Pass a prefix to namespace custom IDs, which the framework uses for routing:

new ComponentBuilder('ticket').addActionRow((row) =>
  row.addNormalButton({ custom_id: 'close', style: ButtonStyle.Danger, label: 'Close' })
); // custom_id becomes "ticket:close"

Limits: 5 rows per message, 5 buttons per row, 1 select per row (no mixing with buttons).

Components V2

Richer layouts. Send with toMessageData(), which sets the V2 flag and omits fields V2 messages cannot carry (content, embeds, sticker_ids, poll).

import { NewComponentBuilder, ButtonStyle } from 'athena';
 
const components = new NewComponentBuilder().addContainer((c) =>
  c
    .setAccentColor(0x5865f2)
    .addTextDisplay('# Welcome')
    .addSeparator({ spacing: 2 })
    .addActionRow((row) => row.addNormalButton({ custom_id: 'role:gamer', style: ButtonStyle.Primary, label: 'Gamer' }))
);
 
await client.createMessage(channelID, components.toMessageData());

V2 primitives

  • addTextDisplay(content, id?) - markdown text.
  • addSeparator({ spacing: 1 | 2, divider? }) - a rule or spacer.
  • addSection(cb) - up to 3 text displays plus one accessory (setAccessoryAsNormalButton, setAccessoryAsURLButton, setAccessoryAsPremiumButton, setAccessoryAsThumbnail).
  • addMediaGallery(cb) - up to 10 media items.
  • addFile(media, options?) - an inline file reference.
  • addContainer(cb) - a panel with optional accent colour and spoiler, holding any of the above.
  • addActionRow(cb) - same buttons/selects as V1.

Limits: 40 components total per message; 5 per row; 1 select per row; 3 text displays per section (a section requires an accessory); 10 items per gallery.

Buttons

row.addNormalButton({ custom_id: 'click', style: ButtonStyle.Primary, label: 'Click', emoji: 'thumbsup', disabled: false });
row.addURLButton({ url: 'https://example.com', label: 'Open' });
row.addPremiumButton({ sku_id: '123', disabled: false });

Emoji accepts a unicode character or <:name:id> / <a:name:id>. URL and premium buttons have no custom_id.

Select menus

row.addStringSelect({ custom_id: 'pick', placeholder: 'Choose', min_values: 1, max_values: 1, options: [
  { label: 'Vanilla', value: 'vanilla', default: true },
  { label: 'Chocolate', value: 'chocolate' }
]});
row.addChannelSelect({ custom_id: 'ch', channel_types: [ChannelType.GuildText], default_values: ['123'] });
row.addRoleSelect({ custom_id: 'role' });
row.addUserSelect({ custom_id: 'user' });
row.addMentionableSelect({ custom_id: 'target' });

default_values is an array of IDs; the builder wraps them correctly.

Handling component interactions

client.on('interactionCreate', async (interaction) => {
  if (!interaction.isComponent()) return;
  if (interaction.isButton() && interaction.data.custom_id === 'confirm') {
    await interaction.deferUpdate();
    await interaction.editParent({ content: 'Confirmed.' });
  }
  if (interaction.isStringSelect()) {
    const picked = interaction.selected; // string[]
  }
});

With the commands framework, implement handleComponent on your Command and route by custom_id prefix instead of wiring interactionCreate.

Tips

  • Prefix custom IDs by feature to avoid collisions and to route in the framework.
  • Do not mix V1 and V2 on the same message; the V2 flag is sticky.