Why JSX for Discord Components?
CommandKit’s JSX component system provides a declarative way to build Discord UI components. Instead of chaining builder methods, you write components using familiar JSX syntax that’s easier to read and maintain.
Benefits
Declarative syntax - Components are self-documenting and easier to understand
Type-safe - Full TypeScript support with prop validation
Composable - Build reusable component patterns
Event handlers - Attach interaction handlers directly to components
Auto-cleanup - Automatic disposal of interaction collectors
How it Works
Traditional Builder Pattern
import { ActionRowBuilder , ButtonBuilder , ButtonStyle } from 'discord.js' ;
const button = new ButtonBuilder ()
. setCustomId ( 'my-button' )
. setLabel ( 'Click Me' )
. setStyle ( ButtonStyle . Primary );
const row = new ActionRowBuilder ()
. addComponents ( button );
await interaction . reply ({
components: [ row ]
});
JSX Component Pattern
import { ActionRow , Button } from 'commandkit' ;
import { ButtonStyle } from 'discord.js' ;
const row = (
< ActionRow >
< Button style = { ButtonStyle . Primary } > Click Me </ Button >
</ ActionRow >
);
await interaction . reply ({
components: [ row ]
});
Getting Started
Import Components
CommandKit exports JSX components from the main package:
import {
ActionRow ,
Button ,
Modal ,
ShortInput ,
ParagraphInput ,
StringSelectMenu ,
StringSelectMenuOption ,
UserSelectMenu ,
RoleSelectMenu ,
ChannelSelectMenu ,
MentionableSelectMenu
} from 'commandkit' ;
Add to your tsconfig.json:
{
"compilerOptions" : {
"jsx" : "react-jsx" ,
"jsxImportSource" : "commandkit"
}
}
Basic Example
Here’s a complete example showing buttons with click handlers:
import {
Button ,
ActionRow ,
CommandData ,
OnButtonKitClick ,
ChatInputCommand ,
} from 'commandkit' ;
import { ButtonStyle , MessageFlags } from 'discord.js' ;
export const command : CommandData = {
name: 'confirm' ,
description: 'Confirm an action' ,
};
const handleConfirm : OnButtonKitClick = async ( interaction , context ) => {
await interaction . reply ({
content: 'Action confirmed!' ,
flags: MessageFlags . Ephemeral ,
});
// Clean up the interaction collector
context . dispose ();
};
const handleCancel : OnButtonKitClick = async ( interaction , context ) => {
await interaction . reply ({
content: 'Action cancelled.' ,
flags: MessageFlags . Ephemeral ,
});
context . dispose ();
};
export const chatInput : ChatInputCommand = async ({ interaction }) => {
const buttons = (
< ActionRow >
< Button onClick = { handleCancel } style = { ButtonStyle . Secondary } >
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 ],
});
};
Component Reference
ActionRow
Container for interactive components. Required for buttons, select menus, and text inputs.
< ActionRow >
{ /* Add up to 5 buttons or 1 select menu */ }
</ ActionRow >
Props:
children
AnyCommandKitElement | AnyCommandKitElement[]
The components to display in this action row
Available Components
Buttons - Interactive buttons with click handlers
Modals - Forms with text inputs
Select Menus - Dropdowns for selecting options
Event Handlers
All interactive components support event handlers:
const handleClick : OnButtonKitClick = async ( interaction , context ) => {
// Handle the button click
await interaction . reply ( 'Clicked!' );
// Clean up the collector
context . dispose ();
};
< Button onClick = { handleClick } > Click Me </ Button >
onSubmit (Modals)
const handleSubmit : OnModalKitSubmit = async ( interaction , context ) => {
const value = interaction . fields . getTextInputValue ( 'input-id' );
await interaction . reply ( `You entered: ${ value } ` );
context . dispose ();
};
< Modal title = "My Modal" onSubmit = { handleSubmit } >
< ShortInput customId = "input-id" />
</ Modal >
const handleSelect : OnStringSelectMenuKitSubmit = async ( interaction , context ) => {
const values = interaction . values . join ( ', ' );
await interaction . reply ( `Selected: ${ values } ` );
context . dispose ();
};
< StringSelectMenu onSelect = { handleSelect } >
< StringSelectMenuOption label = "Option 1" value = "1" />
< StringSelectMenuOption label = "Option 2" value = "2" />
</ StringSelectMenu >
Auto-Generated Custom IDs
When you attach event handlers, CommandKit automatically generates unique custom IDs:
// No customId needed - one is generated automatically
< Button onClick = { handleClick } > Click Me </ Button >
// Equivalent to:
< Button customId = "buttonkit::a1b2c3d4-..." onClick = { handleClick } >
Click Me
</ Button >
You can still provide your own customId if you need a specific value for tracking or persistence.
Interaction Collectors
CommandKit’s JSX components use an event interceptor system for handling interactions:
Auto-reset - Collectors automatically reset their timeout on each interaction
Configurable timeout - Default 5 minutes, customizable via options prop
Manual disposal - Call context.dispose() to clean up
Custom Collector Options
const options = {
time: 10 * 60 * 1000 , // 10 minutes
autoReset: true , // Reset timer on each interaction
};
< Button onClick = { handleClick } options = { options } >
Click Me
</ Button >
Next Steps
Buttons Learn about button styles and click handlers
Modals Build forms with text inputs
Select Menus Create dropdowns for user input