Skip to main content
Version: Next

Using JSX

CommandKit provides first-class JSX support for both TypeScript and JavaScript projects. This allows you to write Discord message components using a familiar and intuitive syntax.

Why Use JSX Instead of Builders?

Using JSX for Discord message components offers several advantages over traditional builder patterns:

Cleaner and More Readable

JSX makes your code significantly more readable by providing a declarative structure that resembles the final output:

// JSX approach - clean and intuitive
const buttons = (
<ActionRow>
<Button
onClick={async (interaction) => {
await interaction.reply({
content: 'The item was not deleted.',
flags: MessageFlags.Ephemeral,
});
}}
style={ButtonStyle.Primary}
>
Cancel
</Button>
<Button
onClick={async (interaction) => {
await interaction.reply({
content: 'The item was deleted successfully.',
flags: MessageFlags.Ephemeral,
});
}}
style={ButtonStyle.Danger}
>
Confirm
</Button>
</ActionRow>
);

await interaction.reply({
content: 'Are you sure you want to delete this item?',
components: [buttons],
});
// Builder approach - more verbose and harder to visualize
const cancel = new ButtonBuilder()
.setCustomId('cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Primary);

const confirm = new ButtonBuilder()
.setCustomId('confirm')
.setLabel('Confirm')
.setStyle(ButtonStyle.Danger);

const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
cancel,
confirm,
);

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

const confirmation = await response.resource.message.awaitMessageComponent();

if (confirmation.customId === 'confirm') {
await confirmation.reply({
content: 'The item was deleted successfully.',
ephemeral: true,
});
} else if (confirmation.customId === 'cancel') {
await confirmation.reply({
content: 'The item was not deleted.',
ephemeral: true,
});
}

Less Boilerplate

JSX reduces the amount of code you need to write. No more chaining multiple method calls or creating separate builder instances:

  • No repetitive .addComponents() calls
  • No need to explicitly create builder instances
  • Fewer imports to manage

Improved Developer Experience

  • Better IDE support with syntax highlighting and autocompletion
  • Easier to spot errors with JSX's structured format
  • More intuitive nesting of components

Familiar Syntax

If you've worked with React or other modern frameworks, JSX will feel immediately familiar, reducing the learning curve for building Discord interfaces.

Maintainability

JSX makes your code more maintainable:

  • Easier to refactor and modify complex component structures
  • Simpler to identify the component hierarchy
  • More straightforward to extract reusable components

Setup

CommandKit automatically configures JSX support in your project. If you are doing a manual setup, ensure you have the following in your tsconfig.json or jsconfig.json:

{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "commandkit"
}
}

Available Components

CommandKit provides JSX versions of all Discord.js builders:

Interactive Components

  • ActionRow - Container for interactive components
  • Button - Interactive button component
  • StringSelectMenu - Dropdown menu with string options
  • ChannelSelectMenu - Dropdown menu for channel selection
  • RoleSelectMenu - Dropdown menu for role selection
  • UserSelectMenu - Dropdown menu for user selection
  • MentionableSelectMenu - Dropdown menu for user/role selection
  • Modal - Popup form component

Message Components

  • TextDisplay - Text content component
  • Container - Message container component
  • MediaGallery - Image/video gallery component
  • Separator - Visual separator component
  • File - File attachment component

Example Usage

import { Button, ActionRow } from 'commandkit';

const message = (
<Container>
<TextDisplay>Welcome to our server!</TextDisplay>
<ActionRow>
<Button style="primary">Click me!</Button>
</ActionRow>
</Container>
);

TypeScript Support

CommandKit provides full TypeScript support for JSX components. All components are properly typed, providing autocomplete and type checking:

import { Button, OnButtonKitClick } from 'commandkit';

const handleClick: OnButtonKitClick = async (interaction, context) => {
await interaction.reply('Button clicked!');
context.dispose();
};

const button = <Button onClick={handleClick}>Click me!</Button>;

Custom Components

You can create your own JSX components by defining functions that return Discord.js objects. These components can be used anywhere in your code:

import { Client, ClientOptions } from 'discord.js';

function Bot({ options }: { options: ClientOptions }): Client {
return new Client(options);
}

const client = <Bot options={{ intents: ['Guilds'] }} />;

JSX Fragments

CommandKit fully supports JSX fragments for grouping multiple elements without adding extra nodes:

const elements = (
<>
<TextDisplay>First message</TextDisplay>
<TextDisplay>Second message</TextDisplay>
</>
);

Configuration

The CommandKit compiler automatically configures:

  • JSX runtime settings
  • TypeScript/JavaScript configuration
  • Component type definitions
  • Build pipeline optimizations

No additional configuration is needed to use JSX in your CommandKit project.

Best Practices

  1. Use named imports for specific components:
import { Button, ActionRow } from 'commandkit';
  1. Keep component trees readable with proper indentation:
const message = (
<Container>
<TextDisplay>Hello!</TextDisplay>
<ActionRow>
<Button>Button 1</Button>
<Button>Button 2</Button>
</ActionRow>
</Container>
);
  1. Use TypeScript for better type safety and developer experience:
import type { OnButtonKitClick } from 'commandkit';
  1. Create custom components for reusable Discord.js objects:
function CustomEmbed({
title,
description,
}: {
title: string;
description: string;
}) {
return new EmbedBuilder().setTitle(title).setDescription(description);
}

const embed = <CustomEmbed title="Hello" description="World" />;