DisJSX is a library for declaratively creating Discord message payloads using JSX. It supports both legacy message formats and Discord's V2 component system. With DisJSX, you can define your message structure in a familiar, React-like syntax, and renderDiscordMessage will transform it into the JSON object Discord expects.
Note: This library is for generating Discord message JSON payloads. It does NOT render messages in a browser or provide React components for direct use in a web UI.
DisJSX bridges the gap between the ease of JSX and the structure of Discord messages. Instead of manually crafting complex JSON objects, you can use intuitive components like <Message>, <Embed>, <Button>, and <Section> to build your messages.
Key Benefits:
- Developer Experience: Write message structures in a more readable and maintainable way using JSX.
- Type Safety (with TypeScript): Leverage TypeScript for better autocompletion and error checking when defining message props.
- Support for All Features: Covers legacy content/embeds and the full V2 component system, including layouts, interactive elements, and rich media.
- Simplified JSON Generation: The
renderDiscordMessagefunction handles the conversion to the correct Discord JSON payload.
You define the structure of your Discord message using JSX tags that correspond to Discord's message elements. For example, an embed is created with <Embed>, and its title with <EmbedTitle>.
This is the core function that takes your DisJSX component tree and converts it into the final JSON object that can be sent to the Discord API.
Discord has two primary ways of handling message components:
- Legacy Messages: Primarily use
contentandembeds. Action Rows with buttons or select menus can be added. - V2 Messages: Enabled by passing the
isV2={true}prop to the<Message>component. This activates Discord's newer component system (internally setting the1 << 15orIS_COMPONENTS_V2message flag).- Important: When
isV2={true}:- The
contentandembedsfields (and their DisJSX counterparts<Content>and<Embed>) will no longer work directly under<Message>. - Use
<TextDisplay>for text content and<Container>(which can mimic embeds) or other V2 layout components. - Attachments won't show by default; they must be exposed through components like
<File>or<MediaGalleryItem>. - The
pollandstickersfields are disabled. - Messages allow up to 40 total components.
- The
- Important: When
npm install disjsximport { Message, Content, renderDiscordMessage } from "disjsx";
const message = (
<Message>
<Content>Hello world!</Content>
</Message>
);
console.log(renderDiscordMessage(message));
// Output: { content: "Hello world!" }import {
Message,
Embed,
EmbedTitle,
EmbedDescription,
EmbedAuthor,
EmbedField,
EmbedFields,
EmbedFieldTitle,
EmbedFieldValue,
EmbedFooter,
EmbedImage,
EmbedThumbnail,
Colors,
renderDiscordMessage,
} from "disjsx";
const message = (
<Message>
<Embed color={Colors.Purple} timestamp={new Date()} url="https://discord.com">
<EmbedAuthor name="John Doe" iconUrl="https://cdn.discordapp.com/embed/avatars/0.png" url="https://github.com" />
<EmbedTitle>This is an Embed Title!</EmbedTitle>
<EmbedDescription>
This is a rich embed description. It can contain **markdown** and links like [this one](https://discord.com).
</EmbedDescription>
<EmbedThumbnail url="https://cdn.discordapp.com/embed/avatars/1.png" />
<EmbedFields>
<EmbedField title="Field 1 (Inline)" value="Value 1" inline />
<EmbedField title="Field 2 (Inline)" value="Value 2" inline />
<EmbedField title="Field 3 (Not Inline)" value="Value 3" />
<EmbedField inline>
<EmbedFieldTitle>Field 4 Title (Child)</EmbedFieldTitle>
<EmbedFieldValue>Field 4 Value (Child)</EmbedFieldValue>
</EmbedField>
</EmbedFields>
<EmbedImage url="https://placehold.co/1024/45BFEA/FFFFFF.png?text=Cats%20Are%20Cool" />
<EmbedFooter text="This is the footer text" iconUrl="https://cdn.discordapp.com/embed/avatars/2.png" />
</Embed>
<Embed title="Minimal Embed" description="Just title and description." color={0x00ff00} />
</Message>
);
console.log(renderDiscordMessage(message));
// Output: { embeds: [{...}, {...}] }import { Message, Section, TextDisplay, renderDiscordMessage } from "disjsx";
const message = (
<Message isV2>
<Section>
<TextDisplay>Hello world from a V2 message!</TextDisplay>
</Section>
</Message>
);
console.log(renderDiscordMessage(message));
// Output: { components: [{ type: 9, ... }], flags: 32768 }DisJSX also supports creating Discord modals for collecting user input. Modals are popup dialogs that can contain text input fields.
import { Modal, ActionRow, TextInput, TextInputStyle, renderDiscordModal } from "disjsx";
const userProfileModal = (
<Modal title="Edit Profile" customId="profile_modal">
<ActionRow>
<TextInput
customId="username"
label="Username"
style={TextInputStyle.Short}
placeholder="Enter your username"
maxLength={32}
required={true}
/>
</ActionRow>
<ActionRow>
<TextInput
customId="bio"
label="Bio"
style={TextInputStyle.Paragraph}
placeholder="Tell us about yourself..."
maxLength={1000}
required={false}
/>
</ActionRow>
<ActionRow>
<TextInput
customId="email"
label="Email"
style={TextInputStyle.Short}
placeholder="user@example.com"
required={true}
/>
</ActionRow>
</Modal>
);
console.log(renderDiscordModal(userProfileModal));
// Output: { type: 9, data: { title: "Edit Profile", custom_id: "profile_modal", components: [...] } }DisJSX includes a comprehensive validation system that helps you catch errors before sending your payloads to Discord. The validation system checks for:
- Component Placement Rules: Ensures components are placed in valid containers (e.g., buttons only in Action Rows)
- Character Limits: Validates that text content doesn't exceed Discord's limits
- Component Limits: Checks maximum numbers of components, fields, options, etc.
- Required Properties: Ensures all required props are provided
- Custom ID Uniqueness: Prevents duplicate custom IDs within a message or modal
- Button Style Requirements: Validates that buttons have the correct props for their style
By default, validation is enabled for both renderDiscordMessage and renderDiscordModal:
import { Message, Content, renderDiscordMessage } from "disjsx";
const message = (
<Message>
<Content>{"A".repeat(3000)}</Content> {/* Too long! */}
</Message>
);
// This will log validation errors to the console and return null
const result = renderDiscordMessage(message);
// Console output: "DisJSX Validation Errors: - Message content cannot exceed 2000 characters"You can customize validation behavior with options:
import { Message, Content, renderDiscordMessage } from "disjsx";
const message = (
<Message>
<Content>Valid content</Content>
</Message>
);
// Disable validation entirely
const unvalidatedResult = renderDiscordMessage(message, { validate: false });
// Enable validation with error throwing
try {
const result = renderDiscordMessage(message, {
validate: true,
throwOnValidationError: true,
});
} catch (error) {
console.error("Validation failed:", error.message);
}You can also validate components manually without rendering:
import { Message, Button, ActionRow, ButtonStyle, validateComponent } from "disjsx";
const message = (
<Message>
<ActionRow>
<Button style={ButtonStyle.Primary} customId="test">
Click me
</Button>
</ActionRow>
</Message>
);
const validationResult = validateComponent(message, false, "message");
if (!validationResult.isValid) {
console.log("Validation errors:", validationResult.errors);
console.log("Validation warnings:", validationResult.warnings);
}The validation system provides detailed error information:
interface ValidationError {
type: "error" | "warning";
message: string;
component?: string;
componentId?: number;
path?: string[];
}Common validation rules include:
-
Character Limits:
- Message content: 2000 characters
- Embed title: 256 characters
- Embed description: 4096 characters
- Button label: 80 characters
- Custom ID: 100 characters
-
Component Limits:
- V2 messages: 40 total components
- Legacy messages: 5 Action Rows maximum
- Action Rows: 5 buttons OR 1 select menu
- String Select: 25 options maximum
- Embed fields: 25 maximum
- Media Gallery: 1-10 items
-
Placement Rules:
- Buttons must be in Action Rows
- TextInput only in modals
- Embeds only in legacy messages
- V2 components only in V2 messages
This section details the DisJSX components and how they map to Discord's message structures.
Represents a Discord message, the top-level container for all content. Supports both legacy and V2 component systems via the isV2 prop.
- Props (
MessageProps):isV2?: boolean: Iftrue, enables the V2 components system. This changes how content and embeds are handled (they are disabled in favor of V2 components like<TextDisplay>and<Container>) and allows new V2 component types. Theflagsfield in the output JSON will include32768(Discord's1 << 15flag forIS_COMPONENTS_V2).children: React.ReactNode:- For V2 (
isV2={true}): Can be<Section>,<Container>,<MediaGallery>,<File>,<Separator>,<ActionRow>,<TextDisplay>. - For Legacy (
isV2={false}or omitted): Can be<Content>,<Embed>,<ActionRow>.
- For V2 (
username?: string: Overrides the default username of the webhook/bot.avatarUrl?: string: Overrides the default avatar of the webhook/bot.tts?: boolean: Whether this is a text-to-speech message.flags?: number: Additional message flags combined as a bitfield.32768is automatically added ifisV2is true.
Represents a Discord modal dialog for collecting user input. Modals are popup forms that can contain various input fields and components.
- Props (
ModalProps):title: string: The title of the popup modal (max 45 characters).customId: string: Developer-defined identifier for the modal (max 100 characters). This is sent back when the modal is submitted.children: React.ReactNode: Can contain:<Label>- Recommended wrapper for inputs with label and description<TextDisplay>- For informational text/instructions<FileUpload>- For file upload inputs<ActionRow>with<TextInput>- Deprecated, use<Label>instead
- Example: See the Modal Examples section below for comprehensive examples.
Represents the main text content of a legacy Discord message.
Only used when MessageProps.isV2 is false or not set.
- Props (
ContentProps):children: React.ReactNode: The text content (will be stringified). Markdown is supported.
Represents an embed object within a legacy Discord message. Embeds are rich content blocks that can include titles, descriptions, images, fields, and more.
Only used when MessageProps.isV2 is false or not set. For V2 messages, use <Container> to achieve similar visual grouping, or structure content with <Section> and <TextDisplay>.
- Props (
EmbedProps):title?: string: Title of the embed.description?: string: Description of the embed. Supports markdown.url?: string: URL of the embed title (makes the title a hyperlink).timestamp?: string | Date: Timestamp of the embed content (ISO8601 format or Date object).color?: number | string: Color code of the embed's left border. Can be an integer (e.g.,Colors.Blurple,0x5865F2) or a hex string (e.g.,'#5865F2').children?: React.ReactNode: Can include<EmbedAuthor>,<EmbedFooter>,<EmbedImage>,<EmbedThumbnail>,<EmbedFields>,<EmbedTitle>,<EmbedDescription>to structure the embed.
Represents the author section of an embed.
- Props (
EmbedAuthorProps):name: string: Name of the author.url?: string: URL to link the author's name to.iconUrl?: string: URL of the author icon (a small image next to the author's name).
Represents the title of an embed. Can also be set directly via the title prop on <Embed>.
- Props (
EmbedTitleProps):children: React.ReactNode: Content for the embed title.
Represents the main description text of an embed. Can also be set directly via the description prop on <Embed>. Supports markdown.
- Props (
EmbedDescriptionProps):children: React.ReactNode: Content for the embed description.
A container for multiple <EmbedField> components within an embed.
- Props (
EmbedFieldsProps):children: React.ReactElement<EmbedFieldProps> | React.ReactElement<EmbedFieldProps>[]: One or more<EmbedField>elements.
Represents a single field within an embed, consisting of a name (title) and a value. Fields can be displayed inline.
- Props (
EmbedFieldProps):title?: string: Title of the field (maps to Discord'snamefield in the payload).value?: string: Value of the field (maps to Discord'svaluefield in the payload). Supports markdown.inline?: boolean: Whether the field should be displayed inline with other inline fields.children?: React.ReactNode: Can be used to structure title and value using<EmbedFieldTitle>and<EmbedFieldValue>. Alternatively,titleandvalueprops can be used directly.
Represents the title (name) part of an embed field.
- Props (
EmbedFieldTitleProps):children: React.ReactNode: Content for the field title.
Represents the value part of an embed field.
- Props (
EmbedFieldValueProps):children: React.ReactNode: Content for the field value. Supports markdown.
Represents the footer section of an embed.
- Props (
EmbedFooterProps):text?: string: Text content of the footer. Alternative tochildren.children?: string: Text content of the footer. Alternative totext.iconUrl?: string: URL of the footer icon (a small image next to the footer text).
Represents the main large image of an embed.
- Props (
EmbedImageProps):url: string: URL of the image.
Represents the thumbnail image of an embed, displayed in the top-right corner.
- Props (
EmbedThumbnailProps):url: string: URL of the thumbnail.
A top-level layout component that acts as a container for interactive components.
In V2 messages, an Action Row can also be a child of a <Container>.
- Discord Rules:
- Can contain up to 5
<Button>components. - OR a single Select Menu component (
<StringSelect>,<UserSelect>,<RoleSelect>,<MentionableSelect>,<ChannelSelect>). - OR a single
<TextInput>(only within Modals, not directly in messages). - An Action Row cannot mix buttons and select menus.
- Can contain up to 5
- Props (
ActionRowProps):children: React.ReactNode: Child components (Buttons, Select Menus, or Text Input for modals).id?: number: Optional identifier for the component. If not provided, Discord may assign one.
Represents an interactive button. Buttons can trigger interactions (sending data back to your bot) or link to URLs.
Must be placed within an <ActionRow> or as a <Section> accessory (V2 only).
- Props (
ButtonProps):label?: string: Text that appears on the button (max 80 characters). Alternative tochildren.children?: string: Text that appears on the button (max 80 characters). Alternative tolabel.style: ButtonStyle: The style of the button (e.g.,ButtonStyle.Primary,ButtonStyle.Link). Determines behavior and required fields.Primary,Secondary,Success,Danger: RequirecustomId.Link: Requiresurl, does not send an interaction.Premium: RequiresskuId, for monetized apps, does not send an interaction.
customId?: string: Developer-defined identifier (max 100 characters). Sent back in the interaction payload when the button is clicked. Required for non-link/non-premium styles. Must be unique within the message.url?: string: URL forButtonStyle.Linkbuttons.skuId?: string: Identifier for a purchasable SKU forButtonStyle.Premiumbuttons.emoji?: EmojiObject: Partial emoji object ({ id?: string, name?: string, animated?: boolean }) to display on the button.disabled?: boolean: Whether the button is disabled (defaults tofalse).id?: number: Optional identifier for the component.
A wrapper component for modal inputs that provides a label and optional description. This is the recommended way to structure modal inputs.
- Props (
LabelProps):label: string: The label text (max 45 characters).description?: string: Optional description text for additional context (max 100 characters).children: React.ReactNode: The input component (<TextInput>,<StringSelect>,<UserSelect>,<RoleSelect>,<MentionableSelect>,<ChannelSelect>, or<FileUpload>).id?: number: Optional identifier for the component.
Allows users to upload files in modals.
- Props (
FileUploadProps):customId: string: Developer-defined identifier (max 100 characters). Sent back when the modal is submitted. Must be unique.minValues?: number: Minimum number of files that must be uploaded (0-10). Defaults to 1.maxValues?: number: Maximum number of files that can be uploaded (1-10). Defaults to 1.required?: boolean: Whether the file upload is required (defaults totrue).id?: number: Optional identifier for the component.
Represents a text input field for collecting user input in modals. Text inputs come in two styles: short (single-line) and paragraph (multi-line).
Note: When using <TextInput> in modals, it's recommended to wrap it in a <Label> component instead of an <ActionRow> (deprecated).
- Props (
TextInputProps):customId: string: Developer-defined identifier for the input (max 100 characters). This is sent back when the modal is submitted. Must be unique within the modal.style: TextInputStyle: The style of the text input:TextInputStyle.Short: Single-line input fieldTextInputStyle.Paragraph: Multi-line input field (textarea)
label: string: Label text displayed above the input field (max 45 characters). Deprecated: Use<Label>component'slabelprop instead.placeholder?: string: Placeholder text displayed when the input is empty (max 100 characters).value?: string: Pre-filled value for the input (max 4000 characters).required?: boolean: Whether the input must be filled before submitting the modal (defaults totrue).minLength?: number: Minimum input length (0-4000).maxLength?: number: Maximum input length (1-4000).id?: number: Optional identifier for the component.
Select menus allow users to choose one or more options from a list.
All select menus must be placed as the sole child of an <ActionRow>.
Common Select Menu Props (BaseSelectProps):
customId: string: Developer-defined ID for the select menu (max 100 characters). Sent back in the interaction payload. Must be unique.placeholder?: string: Placeholder text if nothing is selected (max 150 characters).minValues?: number: Minimum number of items that must be chosen (0-25, defaults to 1).maxValues?: number: Maximum number of items that can be chosen (1-25, defaults to 1).disabled?: boolean: Whether the select menu is disabled (defaults tofalse).id?: number: Optional identifier for the component.
Allows users to choose from a list of predefined text options.
- Props (
StringSelectPropsextendsBaseSelectProps):children: React.ReactElement<SelectOptionProps> | React.ReactElement<SelectOptionProps>[]: Array of<SelectOption>elements (max 25 options).
Represents an option within a <StringSelect> menu.
- Props (
SelectOptionProps):label: string: User-facing name of the option (max 100 characters). Alternative tochildren.children?: string: User-facing name of the option (max 100 characters). Alternative tolabel.value: string: Developer-defined value of the option (max 100 characters). This value is sent in the interaction.description?: string: Additional description of the option (max 100 characters).emoji?: EmojiObject: Partial emoji object to display with the option.isDefault?: boolean: Whether this option is selected by default.
These select menus are automatically populated by Discord based on server context (users, roles, channels).
Common Auto-Populated Select Props (AutoPopulatedSelectProps extends BaseSelectProps):
defaultValues?: SelectDefaultValue[]: List of default selected values. Each object is{ id: string, type: "user" | "role" | "channel" }. The number of values must be betweenminValuesandmaxValues.
Allows users to select one or more users from the server.
- Props (
UserSelectPropsextendsAutoPopulatedSelectProps):defaultValues?: SelectDefaultValue[]: Default values must havetype: "user".
Allows users to select one or more roles from the server.
- Props (
RoleSelectPropsextendsAutoPopulatedSelectProps):defaultValues?: SelectDefaultValue[]: Default values must havetype: "role".
Allows users to select users or roles from the server.
- Props (
MentionableSelectPropsextendsAutoPopulatedSelectProps):defaultValues?: SelectDefaultValue[]: Default values can havetype: "user"ortype: "role".
Allows users to select one or more channels from the server. Can be filtered by channel types.
- Props (
ChannelSelectPropsextendsAutoPopulatedSelectProps):defaultValues?: SelectDefaultValue[]: Default values must havetype: "channel".channelTypes?: number[]: List of channel type integers (e.g., fromChannelTypesenum) to filter the selectable channels.
These components require <Message isV2={true}>.
A layout element for V2 messages. Sections display text alongside an optional accessory (like a <Thumbnail> or <Button>).
- Props (
SectionProps):children: React.ReactNode: Child components. Typically processed into 1 to 3<TextDisplay>components and one optional accessory. The accessory should be the last child if provided.id?: number: Optional identifier for the component.
- Structure: Discord expects
components(for TextDisplays) andaccessory(for Thumbnail/Button) fields in the JSON. DisJSX handles this based on children.
Used to display markdown-formatted text within V2 messages. Similar to message content but as a distinct, repeatable component.
- Props (
TextDisplayProps):children: React.ReactNode: The markdown content to be displayed (will be stringified to form thecontentfield in the JSON).id?: number: Optional identifier for the component.
A small image typically used as an accessory within a <Section>.
- Props (
ThumbnailProps):url: string: URL of the media. Supports HTTP/HTTPS URLs orattachment://<filename>references (requires file upload).description?: string: Alt text for the media (max 1024 characters).spoiler?: boolean: Whether the thumbnail should be a spoiler (blurred). Defaults tofalse.id?: number: Optional identifier for the component.
- Note: This component maps to Discord's
thumbnailcomponent (type 11) which has amediaobject containing theurl.
Displays 1-10 media items (images/videos) in an organized gallery format.
- Props (
MediaGalleryProps):children: React.ReactElement<MediaGalleryItemProps> | React.ReactElement<MediaGalleryItemProps>[]: One to ten<MediaGalleryItem>elements.id?: number: Optional identifier for the component.
- Note: Maps to Discord's
media_gallerycomponent (type 12) which has anitemsarray.
Represents a single item within a <MediaGallery>.
- Props (
MediaGalleryItemProps):url: string: URL of the media. Supports HTTP/HTTPS URLs orattachment://<filename>references.description?: string: Alt text for the media (max 1024 characters).spoiler?: boolean: Whether the media should be a spoiler. Defaults tofalse.
- Note: Each item maps to an object in the
itemsarray of themedia_gallerycomponent, containing amediaobject (for theurl) and other props.
Displays an uploaded file as an attachment, referenced by attachment://filename. Requires the file to be uploaded with the message via multipart/form-data.
- Props (
FileProps):url: string: URL of the file, must be an attachment reference usingattachment://<filename>syntax.spoiler?: boolean: Whether the file should be a spoiler. Defaults tofalse.id?: number: Optional identifier for the component.
- Note: Maps to Discord's
filecomponent (type 13) which has afileobject containing theurl.
Adds vertical padding and an optional visual divider between other V2 components.
- Props (
SeparatorProps):divider?: boolean: Whether a visual divider should be displayed. Defaults totrue.spacing?: 1 | 2: Size of separator padding:1for small,2for large. Defaults to1.id?: number: Optional identifier for the component.
- Note: Maps to Discord's
separatorcomponent (type 14).
Visually groups a set of V2 components, with an optional customizable color bar on the left. Can be used to create embed-like structures in V2 messages.
- Props (
ContainerProps):children: React.ReactNode: Child components. Can be<ActionRow>,<TextDisplay>,<Section>,<MediaGallery>,<Separator>, or<File>.accentColor?: number | string: Optional color for the accent bar (RGB integer from0x000000to0xFFFFFF, or a hex string).spoiler?: boolean: Whether the entire container should be a spoiler (blurred out). Defaults tofalse.id?: number: Optional identifier for the component.
- Note: Maps to Discord's
containercomponent (type 17).
DisJSX allows you to use common HTML-like tags directly within components that accept text content (e.g., <Content>, <EmbedDescription>, <TextDisplay>, <EmbedFieldValue>, etc.). These tags will be automatically converted into their corresponding Discord Markdown equivalents when the message is rendered.
This provides a more semantic or familiar way to structure your text content.
Supported Elements and their Markdown Output:
| HTML-like Tag(s) | Discord Markdown Output | Example (JSX) |
|---|---|---|
<h1> |
# Your Text |
<h1>Title</h1> |
<h2> |
## Your Text |
<h2>Subtitle</h2> |
<h3> |
### Your Text |
<h3>Section</h3> |
<strong>, <b> |
**Your Text** |
<strong>Bold</strong> |
<em>, <i> |
*Your Text* |
<em>Italic</em> |
<u> |
__Your Text__ |
<u>Underlined</u> |
<s>, <strike> |
~~Your Text~~ |
<s>Strikethrough</s> |
<small> |
-# Your Text |
<small>Small text</small> |
<blockquote> |
> Your Text (handles multi-line) |
<blockquote>Quote</blockquote> |
<a> |
[Link Text](url) or [Link Text](<url>) (if isEscaped) |
<a href="url">Link</a> |
<ul><li> |
- List Item (handles nesting) |
<ul><li>Item 1</li><li>Item 2</li></ul> |
<ol><li> |
1. List Item (handles nesting) |
<ol><li>First</li><li>Second</li></ol> |
<code> |
`Inline Code` |
<code>var x = 1;</code> |
<pre> |
```lang\nCode Block\n``` |
<pre language="js">{\n "key": "value"\n}</pre> |
<br> |
Newline (\n) |
Line 1<br />Line 2 |
<p> |
Your Text\n (ensures text is followed by a newline) |
<p>Paragraph text.</p> |
DisJSX provides comprehensive support for modals with the new <Label>, <FileUpload>, and <TextDisplay> components. Below are complete examples demonstrating various modal patterns. All examples can be found in the examples/modals/ directory.
The recommended way to structure modals is using <Label> components (replaces the deprecated <ActionRow> pattern):
import { Modal, Label, TextInput, TextInputStyle, renderDiscordModal } from "disjsx";
const modal = (
<Modal title="Edit Profile" customId="profile_modal">
<Label label="Username" description="Choose a display name for your profile">
<TextInput
customId="username"
style={TextInputStyle.Short}
placeholder="Enter your username"
maxLength={32}
required
/>
</Label>
<Label label="Bio" description="Tell the community about yourself">
<TextInput
customId="bio"
style={TextInputStyle.Paragraph}
placeholder="Tell us about yourself..."
maxLength={1000}
required={false}
/>
</Label>
</Modal>
);
const payload = renderDiscordModal(modal);Use <FileUpload> to allow users to upload files:
import { Modal, Label, TextInput, TextInputStyle, FileUpload, TextDisplay, renderDiscordModal } from "disjsx";
const bugReportModal = (
<Modal title="Bug Report" customId="bug_report_modal">
<TextDisplay>
Please provide detailed information about the bug. **Screenshots are required!**
</TextDisplay>
<Label label="Description of the bug" description="Please describe the cause and effects">
<TextInput
customId="description"
style={TextInputStyle.Paragraph}
minLength={30}
placeholder="Whenever I click..."
required
/>
</Label>
<Label label="Provide screenshots" description="At least two pictures required">
<FileUpload customId="screenshots" minValues={2} maxValues={10} required />
</Label>
</Modal>
);Labels can wrap any interactive component, including select menus:
import { Modal, Label, UserSelect, RoleSelect, ChannelSelect, ChannelTypes, TextDisplay, renderDiscordModal } from "disjsx";
const roleAssignmentModal = (
<Modal title="Assign Roles" customId="role_assignment_modal">
<TextDisplay>
## Role Assignment Tool
Select users, roles, and notification channel below.
</TextDisplay>
<Label label="Select Users" description="Choose users to assign roles to">
<UserSelect customId="users_selected" maxValues={10} required />
</Label>
<Label label="Select Roles" description="Choose which roles to assign">
<RoleSelect customId="roles_selected" maxValues={5} required />
</Label>
<Label label="Notification Channel">
<ChannelSelect
customId="announcement_channel"
channelTypes={[ChannelTypes.GuildText, ChannelTypes.GuildAnnouncement]}
required
/>
</Label>
</Modal>
);Combine <TextDisplay>, <Label>, <TextInput>, <FileUpload>, and various selects:
import {
Modal,
Label,
TextInput,
TextInputStyle,
FileUpload,
StringSelect,
SelectOption,
MentionableSelect,
TextDisplay,
renderDiscordModal,
} from "disjsx";
const contentSubmissionModal = (
<Modal title="Submit Content" customId="content_submission_modal">
<TextDisplay>
# Content Submission Form
Submit your creative content to be featured!
**Guidelines:**
- Original content only
- Must follow community guidelines
</TextDisplay>
<Label label="Content Title">
<TextInput
customId="title"
style={TextInputStyle.Short}
maxLength={100}
placeholder="My Amazing Artwork"
required
/>
</Label>
<Label label="Category">
<StringSelect customId="category" placeholder="Select a category" required>
<SelectOption label="π¨ Artwork" value="artwork" />
<SelectOption label="π Writing" value="writing" />
<SelectOption label="π΅ Music" value="music" />
<SelectOption label="π» Code/Project" value="code" />
</StringSelect>
</Label>
<Label label="Upload Files" description="Upload your content files">
<FileUpload customId="content_files" minValues={1} maxValues={5} required />
</Label>
<Label label="Credits (optional)" description="Mention collaborators">
<MentionableSelect
customId="credits"
placeholder="Select users or roles to credit"
maxValues={10}
required={false}
/>
</Label>
</Modal>
);The examples/modals/ directory contains complete, runnable examples:
user-profile.tsx- Basic profile editing modal with text inputsfeedback-form.tsx- Feedback collection with text inputs and select menusbug-report.tsx- Bug report form with file uploads and text inputsrole-assignment.tsx- Role management with user, role, and channel selectspoll-creator.tsx- Poll creation with multiple input typescontent-submission.tsx- Complex submission form with all component types
Run any example with:
bun run examples/modals/feedback-form.tsxDisJSX provides several utility objects/enums that map to Discord constants:
Colors: Provides predefined color integer values for embeds and containers (e.g.,Colors.Blurple,Colors.Red).<Embed color={Colors.Green} /> <Container accentColor={Colors.Yellow} />
ButtonStyle: Defines styles for<Button>components (e.g.,ButtonStyle.Primary,ButtonStyle.Link,ButtonStyle.Danger).<Button style={ButtonStyle.Success} customId="accept"> Accept </Button>
TextInputStyle: Defines styles for<TextInput>components (e.g.,TextInputStyle.Short,TextInputStyle.Paragraph).<TextInput style={TextInputStyle.Paragraph} customId="description" label="Description" />
MessageFlags: For advanced message control (e.g.,MessageFlags.Ephemeral). TheIsComponentsV2flag (1 << 15or32768) is automatically handled by<Message isV2={true}>.<Message flags={MessageFlags.Ephemeral} isV2> <TextDisplay>This message is ephemeral.</TextDisplay> </Message>
ChannelTypes: Provides predefined channel type integers for use with<ChannelSelect>(e.g.,ChannelTypes.GuildText,ChannelTypes.GuildAnnouncement).<ChannelSelect customId="ch_select" channelTypes={[ChannelTypes.GuildText, ChannelTypes.GuildVoice]} />
Remember to always use the appropriate rendering function to convert your DisJSX tree into the JSON payload expected by Discord:
import { Message, Content, renderDiscordMessage } from "disjsx";
const myJsxMessage = (
<Message>
<Content>Hello from DisJSX!</Content>
</Message>
);
const discordPayload = renderDiscordMessage(myJsxMessage);
// Send discordPayload to the Discord API
console.log(discordPayload);import { Modal, Label, TextInput, TextInputStyle, renderDiscordModal } from "disjsx";
const myJsxModal = (
<Modal title="User Input" customId="user_input_modal">
<Label label="Your Name" description="Enter your full name">
<TextInput customId="name" style={TextInputStyle.Short} required />
</Label>
</Modal>
);
const modalPayload = renderDiscordModal(myJsxModal);
// Use modalPayload in interaction responses
console.log(modalPayload);Both renderDiscordMessage and renderDiscordModal support the same validation options for comprehensive error checking and debugging.
Note: The old <ActionRow> pattern for modals is deprecated. Use <Label> to wrap modal inputs instead.