or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client.mdcomponents.mdevents.mdguilds.mdindex.mdinteractions.mdmessages.mdutilities.md
tile.json

components.mddocs/

UI Components & Builders

Comprehensive message components system including action rows, buttons, select menus, modals, and text inputs with builder patterns for creating interactive Discord interfaces.

Capabilities

Action Row System

Container component that holds other interactive components in a horizontal row.

/**
 * Builder for creating action rows
 * @template ComponentType The type of components this action row can contain
 */
class ActionRowBuilder<ComponentType extends AnyComponentBuilder = AnyComponentBuilder> {
  constructor(data?: Partial<ActionRowData<ActionRowComponentData | JSONEncodable<APIComponentInActionRow>>>);
  
  // Component management
  addComponents(...components: ComponentType[]): this;
  setComponents(...components: ComponentType[]): this;
  spliceComponents(index: number, deleteCount: number, ...components: ComponentType[]): this;
  
  // Utility methods
  toJSON(): APIActionRowComponent<ReturnType<ComponentType['toJSON']>>;
  static from<T extends AnyComponentBuilder = AnyComponentBuilder>(
    other: JSONEncodable<APIActionRowComponent<ReturnType<T['toJSON']>>> | APIActionRowComponent<ReturnType<T['toJSON']>>
  ): ActionRowBuilder<T>;
}

/**
 * Represents an action row component
 * @template ComponentType The type of components this action row contains
 */
class ActionRow<ComponentType extends MessageActionRowComponent | ModalActionRowComponent> {
  readonly type: ComponentType.ActionRow;
  readonly components: ComponentType[];
  
  toJSON(): APIActionRowComponent<ReturnType<ComponentType['toJSON']>>;
}

interface ActionRowData<ComponentType extends JSONEncodable<APIComponentInActionRow> | ActionRowComponentData> {
  type: ComponentType.ActionRow;
  components: readonly ComponentType[];
}

type MessageActionRowComponent = 
  | ButtonComponent
  | StringSelectMenuComponent
  | UserSelectMenuComponent  
  | RoleSelectMenuComponent
  | MentionableSelectMenuComponent
  | ChannelSelectMenuComponent;

type ModalActionRowComponent = TextInputComponent;

type MessageActionRowComponentBuilder =
  | ButtonBuilder
  | StringSelectMenuBuilder
  | UserSelectMenuBuilder
  | RoleSelectMenuBuilder
  | MentionableSelectMenuBuilder
  | ChannelSelectMenuBuilder;

type ModalActionRowComponentBuilder = TextInputBuilder;

Button Components

Interactive buttons for message interfaces with various styles and states.

/**
 * Builder for creating button components
 */
class ButtonBuilder {
  constructor(data?: Partial<ButtonComponentData | APIButtonComponent>);
  
  // Button configuration
  setCustomId(customId: string): this;
  setLabel(label: string): this;
  setStyle(style: ButtonStyle): this;
  setEmoji(emoji: ComponentEmojiResolvable): this;
  setURL(url: string): this;
  setDisabled(disabled?: boolean): this;
  
  // Utility methods
  toJSON(): APIButtonComponent;
  static from(other: JSONEncodable<APIButtonComponent> | APIButtonComponent): ButtonBuilder;
}

/**
 * Represents a button component
 */
class ButtonComponent extends Component<APIButtonComponent> {
  readonly type: ComponentType.Button;
  readonly style: ButtonStyle;
  readonly label: string | null;
  readonly emoji: ComponentEmoji | null;
  readonly disabled: boolean;
  readonly customId: string | null;
  readonly url: string | null;
  
  toJSON(): APIButtonComponent;
}

const ButtonStyle: {
  Primary: 1;    // Blurple button
  Secondary: 2;  // Grey button  
  Success: 3;    // Green button
  Danger: 4;     // Red button
  Link: 5;       // Link button (requires URL)
};

interface ButtonComponentData extends BaseComponentData {
  type: ComponentType.Button;
  style: ButtonStyle;
  label?: string;
  emoji?: ComponentEmojiResolvable;
  custom_id?: string;
  url?: string;
  disabled?: boolean;
}

interface ComponentEmoji {
  id: Snowflake | null;
  name: string | null;
  animated?: boolean;
}

type ComponentEmojiResolvable = 
  | string 
  | ComponentEmoji 
  | EmojiIdentifierResolvable;

Usage Examples:

// Basic buttons
const primaryButton = new ButtonBuilder()
  .setCustomId('primary_action')
  .setLabel('Primary Action')
  .setStyle(ButtonStyle.Primary);

const secondaryButton = new ButtonBuilder()
  .setCustomId('secondary_action')
  .setLabel('Secondary')
  .setStyle(ButtonStyle.Secondary)
  .setEmoji('βš™οΈ');

const linkButton = new ButtonBuilder()
  .setLabel('Visit Website')
  .setStyle(ButtonStyle.Link)
  .setURL('https://discord.js.org');

// Action row with buttons
const buttonRow = new ActionRowBuilder<ButtonBuilder>()
  .addComponents(primaryButton, secondaryButton, linkButton);

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

// Disabled button
const disabledButton = new ButtonBuilder()
  .setCustomId('disabled_action')
  .setLabel('Disabled')
  .setStyle(ButtonStyle.Secondary)
  .setDisabled(true);

Select Menu Components

Dropdown menus for user selection with different target types.

/**
 * Base class for select menu builders
 */
class SelectMenuBuilder {
  constructor(data?: Partial<SelectMenuComponentData>);
  
  // Menu configuration
  setCustomId(customId: string): this;
  setPlaceholder(placeholder: string): this;
  setMinValues(minValues: number): this;
  setMaxValues(maxValues: number): this;
  setDisabled(disabled?: boolean): this;
  
  toJSON(): APISelectMenuComponent;
}

/**
 * Builder for string select menus
 */
class StringSelectMenuBuilder extends SelectMenuBuilder {
  // Option management
  addOptions(...options: (StringSelectMenuOptionBuilder | APISelectMenuOption)[]): this;
  setOptions(...options: (StringSelectMenuOptionBuilder | APISelectMenuOption)[]): this;
  spliceOptions(index: number, deleteCount: number, ...options: (StringSelectMenuOptionBuilder | APISelectMenuOption)[]): this;
  
  toJSON(): APIStringSelectComponent;
}

/**
 * Builder for user select menus
 */
class UserSelectMenuBuilder extends SelectMenuBuilder {
  // Default values for user selection
  setDefaultUsers(...users: UserResolvable[]): this;
  addDefaultUsers(...users: UserResolvable[]): this;
  
  toJSON(): APIUserSelectComponent;
}

/**
 * Builder for role select menus
 */
class RoleSelectMenuBuilder extends SelectMenuBuilder {
  setDefaultRoles(...roles: RoleResolvable[]): this;
  addDefaultRoles(...roles: RoleResolvable[]): this;
  
  toJSON(): APIRoleSelectComponent;
}

/**
 * Builder for channel select menus
 */
class ChannelSelectMenuBuilder extends SelectMenuBuilder {
  // Channel type filtering
  setChannelTypes(...channelTypes: RestOrArray<ChannelType>): this;
  addChannelTypes(...channelTypes: RestOrArray<ChannelType>): this;
  setDefaultChannels(...channels: ChannelResolvable[]): this;
  addDefaultChannels(...channels: ChannelResolvable[]): this;
  
  toJSON(): APIChannelSelectComponent;
}

/**
 * Builder for mentionable select menus (users and roles)
 */
class MentionableSelectMenuBuilder extends SelectMenuBuilder {
  setDefaultUsers(...users: UserResolvable[]): this;
  addDefaultUsers(...users: UserResolvable[]): this;
  setDefaultRoles(...roles: RoleResolvable[]): this;
  addDefaultRoles(...roles: RoleResolvable[]): this;
  
  toJSON(): APIMentionableSelectComponent;
}

/**
 * Builder for select menu options (string select only)
 */
class StringSelectMenuOptionBuilder {
  constructor(data?: Partial<APISelectMenuOption>);
  
  setLabel(label: string): this;
  setValue(value: string): this;
  setDescription(description: string): this;
  setEmoji(emoji: ComponentEmojiResolvable): this;
  setDefault(isDefault?: boolean): this;
  
  toJSON(): APISelectMenuOption;
  static from(other: JSONEncodable<APISelectMenuOption> | APISelectMenuOption): StringSelectMenuOptionBuilder;
}

Usage Examples:

// String select menu with options
const stringSelect = new StringSelectMenuBuilder()
  .setCustomId('music_genre')
  .setPlaceholder('Choose a music genre')
  .setMinValues(1)
  .setMaxValues(3)
  .addOptions(
    new StringSelectMenuOptionBuilder()
      .setLabel('Rock')
      .setValue('rock')
      .setDescription('Rock music')
      .setEmoji('🎸'),
    new StringSelectMenuOptionBuilder()
      .setLabel('Jazz')
      .setValue('jazz')
      .setDescription('Jazz music')
      .setEmoji('🎷'),
    new StringSelectMenuOptionBuilder()
      .setLabel('Classical')
      .setValue('classical')
      .setDescription('Classical music')
      .setEmoji('🎻')
  );

// User select menu
const userSelect = new UserSelectMenuBuilder()
  .setCustomId('select_moderators')
  .setPlaceholder('Select moderators')
  .setMinValues(1)
  .setMaxValues(5);

// Role select menu  
const roleSelect = new RoleSelectMenuBuilder()
  .setCustomId('assign_roles')
  .setPlaceholder('Choose roles to assign')
  .setMaxValues(10);

// Channel select menu with type filtering
const channelSelect = new ChannelSelectMenuBuilder()
  .setCustomId('select_channel')
  .setPlaceholder('Choose a text channel')
  .setChannelTypes(ChannelType.GuildText, ChannelType.GuildNews)
  .setMaxValues(1);

// Create message with select menus
const selectRow1 = new ActionRowBuilder<StringSelectMenuBuilder>()
  .addComponents(stringSelect);

const selectRow2 = new ActionRowBuilder<UserSelectMenuBuilder>()
  .addComponents(userSelect);

await interaction.reply({
  content: 'Make your selections:',
  components: [selectRow1, selectRow2]
});

Modal Components

Pop-up forms for collecting user input with text fields.

/**
 * Builder for creating modals
 */
class ModalBuilder {
  constructor(data?: Partial<ModalData>);
  
  // Modal configuration
  setCustomId(customId: string): this;
  setTitle(title: string): this;
  addComponents(...components: ActionRowBuilder<ModalActionRowComponentBuilder>[]): this;
  setComponents(...components: ActionRowBuilder<ModalActionRowComponentBuilder>[]): this;
  spliceComponents(index: number, deleteCount: number, ...components: ActionRowBuilder<ModalActionRowComponentBuilder>[]): this;
  
  toJSON(): APIModalInteractionResponseCallbackData;
  static from(other: JSONEncodable<APIModalInteractionResponseCallbackData> | APIModalInteractionResponseCallbackData): ModalBuilder;
}

/**
 * Builder for text input components
 */
class TextInputBuilder {
  constructor(data?: Partial<TextInputComponentData | APITextInputComponent>);
  
  // Input configuration
  setCustomId(customId: string): this;
  setLabel(label: string): this;
  setStyle(style: TextInputStyle): this;
  setPlaceholder(placeholder: string): this;
  setValue(value: string): this;
  setRequired(required?: boolean): this;
  setMinLength(minLength: number): this;
  setMaxLength(maxLength: number): this;
  
  toJSON(): APITextInputComponent;
  static from(other: JSONEncodable<APITextInputComponent> | APITextInputComponent): TextInputBuilder;
}

/**
 * Represents a text input component
 */
class TextInputComponent extends Component<APITextInputComponent> {
  readonly type: ComponentType.TextInput;
  readonly customId: string;
  readonly style: TextInputStyle;
  readonly label: string;
  readonly minLength: number | null;
  readonly maxLength: number | null;
  readonly required: boolean;
  readonly value: string | null;
  readonly placeholder: string | null;
  
  toJSON(): APITextInputComponent;
}

const TextInputStyle: {
  Short: 1;     // Single-line input
  Paragraph: 2; // Multi-line input
};

interface ModalData {
  title: string;
  custom_id: string;
  components: ActionRowData<ModalActionRowComponentData>[];
}

interface TextInputComponentData extends BaseComponentData {
  type: ComponentType.TextInput;
  custom_id: string;
  style: TextInputStyle;
  label: string;
  min_length?: number;
  max_length?: number;
  required?: boolean;
  value?: string;
  placeholder?: string;
}

Usage Examples:

// Create a feedback modal
const feedbackModal = new ModalBuilder()
  .setCustomId('feedback_modal')
  .setTitle('Feedback Form')
  .addComponents(
    new ActionRowBuilder<TextInputBuilder>()
      .addComponents(
        new TextInputBuilder()
          .setCustomId('feedback_title')
          .setLabel('Title')
          .setStyle(TextInputStyle.Short)
          .setPlaceholder('Brief title for your feedback')
          .setRequired(true)
          .setMaxLength(100)
      ),
    new ActionRowBuilder<TextInputBuilder>()
      .addComponents(
        new TextInputBuilder()
          .setCustomId('feedback_description')
          .setLabel('Description')
          .setStyle(TextInputStyle.Paragraph)
          .setPlaceholder('Detailed description of your feedback...')
          .setRequired(true)
          .setMaxLength(2000)
      ),
    new ActionRowBuilder<TextInputBuilder>()
      .addComponents(
        new TextInputBuilder()
          .setCustomId('feedback_rating')
          .setLabel('Rating (1-10)')
          .setStyle(TextInputStyle.Short)
          .setPlaceholder('Rate from 1 to 10')
          .setRequired(false)
          .setMinLength(1)
          .setMaxLength(2)
      )
  );

// Show modal in response to button interaction
if (interaction.isButton() && interaction.customId === 'show_feedback') {
  await interaction.showModal(feedbackModal);
}

// Handle modal submission
if (interaction.isModalSubmit() && interaction.customId === 'feedback_modal') {
  const title = interaction.fields.getTextInputValue('feedback_title');
  const description = interaction.fields.getTextInputValue('feedback_description');
  const rating = interaction.fields.getTextInputValue('feedback_rating') || 'Not provided';
  
  await interaction.reply({
    content: `Thank you for your feedback!\nTitle: ${title}\nRating: ${rating}`,
    ephemeral: true
  });
}

Advanced Component Features

Additional component features and utilities for complex interfaces.

/**
 * Base component class
 * @template DataType The API component data type
 */  
class Component<DataType extends APIComponentInMessageActionRow | APIComponentInModalActionRow = APIComponentInMessageActionRow | APIComponentInModalActionRow> {
  constructor(data: DataType);
  
  readonly type: ComponentType;
  
  toJSON(): DataType;
  valueOf(): string;
}

/**
 * Container component for organizing UI elements
 */
class ContainerComponent extends Component<APIContainerComponent> {
  readonly type: ComponentType.Container;
  readonly components: APIComponentInContainer[];
  
  toJSON(): APIContainerComponent;
}

/**
 * Section component for grouping related elements  
 */
class SectionComponent extends Component<APISectionComponent> {
  readonly type: ComponentType.Section;
  readonly components: APIComponentInContainer[];
  
  toJSON(): APISectionComponent;
}

/**
 * Separator component for visual separation
 */
class SeparatorComponent extends Component<APISeparatorComponent> {
  readonly type: ComponentType.Separator;
  readonly spacing: SeparatorSpacingSize;
  
  toJSON(): APISeparatorComponent;
}

/**
 * Text display component for formatted text
 */
class TextDisplayComponent extends Component<APITextDisplayComponent> {
  readonly type: ComponentType.TextDisplay;
  readonly content: string;
  
  toJSON(): APITextDisplayComponent;
}

const ComponentType: {
  ActionRow: 1;
  Button: 2;
  StringSelect: 3;
  TextInput: 4;
  UserSelect: 5;
  RoleSelect: 6;
  MentionableSelect: 7;
  ChannelSelect: 8;
  Container: 9;
  Section: 10;
  Separator: 11;
  TextDisplay: 12;
  // ... more component types
};

Component State Management

interface BaseComponentData {
  type: ComponentType;
}

type AnyComponentBuilder = 
  | ActionRowBuilder<any>
  | ButtonBuilder
  | StringSelectMenuBuilder
  | UserSelectMenuBuilder
  | RoleSelectMenuBuilder
  | MentionableSelectMenuBuilder
  | ChannelSelectMenuBuilder
  | TextInputBuilder;

type MessageActionRowComponentData =
  | ButtonComponentData
  | StringSelectMenuComponentData
  | UserSelectMenuComponentData
  | RoleSelectMenuComponentData
  | MentionableSelectMenuComponentData
  | ChannelSelectMenuComponentData;

type ModalActionRowComponentData = TextInputComponentData;

type ActionRowComponentData = MessageActionRowComponentData | ModalActionRowComponentData;

// Component resolvers for finding components by custom ID
interface ComponentResolver {
  resolveComponent(customId: string): MessageActionRowComponent | null;
  resolveComponents(customId?: string): MessageActionRowComponent[];
}

Advanced Usage Examples:

// Complex multi-row interface
const createComplexInterface = () => {
  const buttonRow = new ActionRowBuilder<ButtonBuilder>()
    .addComponents(
      new ButtonBuilder()
        .setCustomId('action_1')
        .setLabel('Action 1')
        .setStyle(ButtonStyle.Primary),
      new ButtonBuilder()
        .setCustomId('action_2')
        .setLabel('Action 2')
        .setStyle(ButtonStyle.Secondary),
      new ButtonBuilder()
        .setCustomId('cancel')
        .setLabel('Cancel')
        .setStyle(ButtonStyle.Danger)
    );

  const selectRow = new ActionRowBuilder<StringSelectMenuBuilder>()
    .addComponents(
      new StringSelectMenuBuilder()
        .setCustomId('category_select')
        .setPlaceholder('Choose a category')
        .addOptions(
          { label: 'General', value: 'general', description: 'General category' },
          { label: 'Music', value: 'music', description: 'Music related' },
          { label: 'Gaming', value: 'gaming', description: 'Gaming content' }
        )
    );

  const userSelectRow = new ActionRowBuilder<UserSelectMenuBuilder>()
    .addComponents(
      new UserSelectMenuBuilder()
        .setCustomId('user_select')
        .setPlaceholder('Select users')
        .setMaxValues(5)
    );

  return [buttonRow, selectRow, userSelectRow];
};

// Dynamic component updating
const updateComponentState = async (interaction: ButtonInteraction) => {
  const components = interaction.message.components;
  
  // Find and update button states
  const updatedComponents = components.map(row => {
    const newRow = new ActionRowBuilder<ButtonBuilder>();
    
    row.components.forEach(component => {
      if (component.type === ComponentType.Button) {
        const button = ButtonBuilder.from(component);
        
        // Disable the clicked button
        if (component.customId === interaction.customId) {
          button.setDisabled(true);
        }
        
        newRow.addComponents(button);
      }
    });
    
    return newRow;
  });
  
  await interaction.update({
    content: 'Button clicked!',
    components: updatedComponents
  });
};

// Component validation
const validateComponents = (components: ActionRowBuilder[]) => {
  components.forEach((row, rowIndex) => {
    if (row.components.length > 5) {
      throw new Error(`Row ${rowIndex} has too many components (max 5)`);
    }
    
    const buttonCount = row.components.filter(c => c instanceof ButtonBuilder).length;
    const selectCount = row.components.filter(c => 
      c instanceof StringSelectMenuBuilder || 
      c instanceof UserSelectMenuBuilder ||
      c instanceof RoleSelectMenuBuilder ||
      c instanceof ChannelSelectMenuBuilder ||
      c instanceof MentionableSelectMenuBuilder
    ).length;
    
    if (buttonCount > 0 && selectCount > 0) {
      throw new Error(`Row ${rowIndex} cannot mix buttons and select menus`);
    }
    
    if (selectCount > 1) {
      throw new Error(`Row ${rowIndex} cannot have multiple select menus`);
    }
  });
};

Additional Builder Classes

Essential builders for attachments and select menu options.

/**
 * Builder for message attachments
 */
class AttachmentBuilder {
  constructor(attachment: BufferResolvable | Stream, options?: AttachmentData);
  
  // Set attachment metadata
  setName(name: string): this;
  setDescription(description: string): this;
  setSpoiler(spoiler?: boolean): this;
  
  // Static methods
  static from(other: JSONEncodable<AttachmentPayload> | BufferResolvable | Stream): AttachmentBuilder;
  
  // Properties
  readonly attachment: BufferResolvable | Stream;
  readonly name: string | undefined;
  readonly description: string | undefined;
  readonly spoiler: boolean;
}

/**
 * Base builder for select menu options
 */
class SelectMenuOptionBuilder {
  constructor(data?: Partial<APISelectMenuOption>);
  
  // Option configuration
  setLabel(label: string): this;
  setValue(value: string): this;
  setDescription(description?: string): this;
  setEmoji(emoji: ComponentEmojiResolvable): this;
  setDefault(isDefault?: boolean): this;
  
  // Static methods
  static from(other: JSONEncodable<APISelectMenuOption> | APISelectMenuOption): SelectMenuOptionBuilder;
  
  // Convert to API format
  toJSON(): APISelectMenuOption;
}

interface AttachmentData {
  name?: string;
  description?: string;
}

interface AttachmentPayload {
  id: number;
  filename: string;
  description?: string;
  content_type?: string;
  size: number;
  url: string;
  proxy_url: string;
  height?: number | null;
  width?: number | null;
  ephemeral?: boolean;
}

Usage Examples:

// Create attachment builders
const imageAttachment = new AttachmentBuilder('./path/to/image.png', {
  name: 'discord-logo.png',
  description: 'The Discord logo'
});

const spoilerAttachment = new AttachmentBuilder(buffer)
  .setName('secret.txt')
  .setSpoiler(true)
  .setDescription('Confidential information');

// Send message with attachments
await channel.send({
  content: 'Here are some files!',
  files: [imageAttachment, spoilerAttachment]
});

// Create generic select menu options
const genericOption = new SelectMenuOptionBuilder()
  .setLabel('Generic Option')
  .setValue('generic_value')
  .setDescription('A generic select menu option')
  .setEmoji('πŸ”§');

// Use in string select menu
const stringSelect = new StringSelectMenuBuilder()
  .setCustomId('generic_select')
  .setPlaceholder('Choose an option')
  .addOptions(
    SelectMenuOptionBuilder.from({
      label: 'Option 1',
      value: 'opt1',
      description: 'First option'
    }),
    genericOption
  );