Discord.Net.ComponentDesigner 0.0.1-nightly.20251020.8
Discord.Net Component Designer
Features
- Component Syntax (CX)
- Component Designer
How does it work?
The Component Designer uses a source generator to find any calls to Discord.ComponentDesigner.cx
, using a custom parser to parse the syntax, validates the structure against Discord's component contraints, and if everything looks good then an interceptor is emitted, replacing the cx
call with the Discord.Net component builder strucutre.
Getting started
Install the Discord.Net.ComponentDesigner
package from one of the following feeds:
Thats it! You can now use the Discord.ComponentDesigner.cx method:
var myComponent = ComponentDesigner.cx(
"""
<text>Hello, World!</text>
"""
);
Component Syntax
The syntax for writing components is based off of JSX, and is referred as 'CX' within the codebase.
Elements
An element consists of a name, some attributes, and some children:
<Example />
<Example foo="bar" />
<Parent>
<Child1 />
Some text
<Child2 />
{DateTime.UtcNow}
</Parent>
Valid children of an element are:
- Another element.
- Text
- Interpolations
Attributes
Attributes consist of a name and an optional value, valid values can be:
- A string literal.
- An interpolation
<Example
foo="abc"
bar='def{123}'
baz={123}
switch1
switch2
/>
String literals
String literals are text and/or interpolations wrapped in quotes. You can either use a single quote or a double quote to denote a string literal. The quote character can be used to simply escapes; double quotes don't need to be escaped in a single-quote literal, and vice versa.
'my string'
"my other string with 'single quotes'"
'escaped \' quotes \''
Escape characters
escaping rules for the syntax are based off of the C# string they're contained in, for example if you're in a multi-line string literal with 3 quotes, you don't need to escape a single quote within the syntax, but if you're in a simple string literal, you may need to.
For the CX language specific escape control, you can use \
to escape characters.
Interpolation Syntax
String interpolation is controlled by the C# syntax, and is parsed based off of what C# says is an interpolation.
Using multiple dollar signs on the containing string to control interpolation character count is supported by the syntax.
Component Designer
The component designer is class Discord.ComponentDesigner
contains the cx
methods used to write components. It's recommended to import the class statically for shorter inline use of CX:
using static Discord.ComponentDesigner;
// you can now just call 'cx'
var myAwesomeComponent = cx(...);
Built-in Components
The Component Designer comes with all the components provided by Discord as built-ins.
Action Row
Action rows wrap buttons or select menus providing top-level layout.
Aliases
row
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optonal identifier for the row |
Valid children
<action-row id={1}>
...
</action-row>
Button
A button is an interactive component that can only be used in messages. Buttons must be placed in an Action Row or as an accessory to a Section
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier for the button |
style? | string | Discord.ButtonStyle | The style of the button |
label? | string | children | The label of the button |
emoji? | string | Discord.IEmote | The emoji of the button |
customId? | string | The custom id of the button |
sku? | ulong | string | A SKU id |
url? | string | A url for a link style button |
disabled? | boolean | Whether the button is disabled |
Example basic button
<button
customId="my-button"
style="success"
>
Click me!
</button>
Example URL button
<button
style="link"
url="http://example.com"
emoji="🔗"
/>
Container
A container is a top-level layout component, offering the ability to visually encapsulate a collection of components and can have an optional accent color.
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier for the container |
color? | string | int | Discord.Color | The accent color of the container |
spoiler | boolean | Whether or not the container is a spoiler |
Valid children
<container color="#00FF00">
...
</container>
File
A file allows you to display an uploaded file as an attachment.
You can use attachment://
to reference attachments uploaded in a message
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier for the file |
url | string | The URL of the file to attach |
spoiler? | boolean | Whether or not the file is a spoiler |
<file
id={123}
url="attachment://file.png"
spoiler
/>
Label
A label wraps modal components with text and an optional description
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier for the label |
value | string | children | The value text of the label |
description? | string | The description of the label |
Valid children
- Text*
- Text Input
- Select Menu
* Text must be the first child of a label to be mapped to the value
<label description="My Description">
My Label
<button {...}/>
</label>
Media Gallery
A Media Gallery allows you to display media attachments in an organized format.
Aliases
gallery
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier for the gallery |
Valid children
<media-gallery id={123}>
...
</media-gallery>
Media Gallery Item
Used within a Media Gallery representing a single media item.
Aliases
gallery-item
media
item
Attributes
Name | Type | Description |
---|---|---|
url | string | The url of the media to display |
description? | string | The description of the media |
spoiler? | boolean | Whether the media is a spoiler |
<media-gallery-item
url="attachment://media1.png"
description="My Description"
spoiler
/>
Section
A Section allows you to contextually associate content with an accessory component.
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier for the section |
accessory | component | children | The accessory of the section |
Valid children
Valid accessories
<section
id={123}
>
<text>...</text>
<accessory>...</accessory>
</section>
<section
id={123}
accessory={myAccessory}
>
...
</section>
Accessory
An Accessory is used within the Section to denote which child is to be treated as an accessory. It does not contain any attributes and must contain a valid accessory type as its child.
Select Menu
A Select Menu allows users to select one or more provided choices.
Aliases
select
Attributes
Name | Type | Description |
---|---|---|
type | Select Menu Type | The type of the select menu |
id? | int | The optional identifier for the select menu |
customId | string | The custom id of the select menu |
placeholder? | string | A placeholder to show if nothing is selected |
min? | int | The minimum number of items that can be chosen |
max? | int | The maximum number of items that can be chosen |
required? | boolean | Whether or not the select menu is required to be answered in a modal |
disabled? | boolean | Whether or not the select menu is disabled |
Select menu types
Valid select menu types are:
string
ortext
user
role
channel
mention
ormentionable
Valid children
user
when type isuser
ormentionable
role
when type isrole
ormentionable
channel
when type ischannel
ormentionable
- Select Menu Option when type is
string
for children of user
, role
, and channel
, the element should contain the ID/entity as the child, ex: <user>{userId}</user>
<select-menu
type="string"
customId="menu-1"
placeholder="Select an option..."
max={1}
min={1}
required
>
<select-menu-option
label="Choice 1"
value="value-1"
/>
<select-menu-option
label="Choice 2"
value="value-2"
description="The better choice"
/>
</select-menu>
<select-menu
type="user"
customId="menu-2"
placeholder="Select a user"
required
>
<user>{userId1}</user>
<user>{user2}</user>
</select-menu>
Select Menu Option
A Select Menu Option represents a choice within a Select Menu
Aliases
option
Attributes
Name | Type | Description |
---|---|---|
label | string | The label of the option |
value | string | The developer-defined value of the option |
description? | string | Additional description of the option |
emoji? | string | Discord.IEmote | An emoji for the option |
default? | boolean | Whether the option is shown as the default option |
<select-menu-option
label="My Option"
value="option1"
description="This is option 1"
emoji={myEmote}
default={false}
/>
Separator
A Separator adds vertical padding and visual division between components
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier for the separator |
divider? | boolean | Whether the separator has a visible divider |
spacing? | string | Discord.SeparatorSpacingSize | The size of the separator |
<separator spacing="large" divider/>
Text Display
A Text Display renders content as markdown
Aliases
text
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier of the text display |
content | string | children | The text to display |
<text-display>
# Hello, World!
</text-display>
Text Input
A Text Input allows users to enter free-form text in a modal
Aliases
input
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier of the text input |
customId | string | The custom id of the text input |
style | string | Discord.TextInputStyle | The style of the text input |
min? | int | The minimum input text length in characters |
max? | int | The maximum input text length in characters |
required? | boolean | Whether the text input is required in a modal |
value? | string | The prefilled value of the input |
placeholder? | string | A placeholder to show if the input is empty |
<text-input
customId="input-1"
style="short"
min={2}
max={36}
required
placeholder="This is a placeholder"
/>
Thumbnail
A Thumbnail displays media in a compact form-factor.
Attributes
Name | Type | Description |
---|---|---|
id? | int | The optional identifier of the thumbnail |
url | string | The URL of the thumbnail |
description? | string | The description of the thumbnail |
spoiler? | boolean | Whether the thumbnail is a spoiler |
<thumbnail url="attachment://image.png" spoiler />
String Interpolation
You can use string interpolations to inject values almost anywhere
using static Discord.ComponentDesigner;
var buttonId = "my-custom-id";
var label = "my label";
var button = cx(
$"""
<button customId={buttonId}>
{label}
</button>
"""
);
[!NOTE] You can't use string interpolations for element identifiers
Most components have constraints on what type of value is allowed (ex select.min should be an integer) and diagnostics will be reported on type mismatches
<select
min={1} // OK
max={"this isn't a number"} // Error: 'String' is not of expected type 'int'
/>
[!NOTE] non-constant values passed into interpolation arguments usually have a runtime fallback (ex
int.Parse
). In the future, you'll be able to enable/disable theses fallbacks and treat them as compile errors.
Interpolated Components
Using string interpolation, you can also supply components as children:
using static Discord.ComponentDesigner;
var myComponent = cx("<text>Hello</text>");
var myOtherComponent = cx(
$"""
<container>
{myComponent}
<separator />
<text>World</text>
</container>
"""
);
Valid types of interpolated components are:
Discord.IMessageComponentBuilder
Discord.IMessageComponent
Discord.MessageComponent
Discord.CXMessageComponent
- Any collection type implementing
IEnumerable<T>
of the previous types.
[!NOTE] By using interpolated components, some compile-time checks can't be done, these include but are not limited to:
- child length checks (ex action row children)
- child component type (ex text in action row)
Compile-time Diagnostics
The generator for the Component Designer comes with compile-time diagnostics for validating most constraints of components, example diagnostics include:
- label/description lengths
- correct types of nested components
- property type validation
- required properties
These are provided as roslyn analyzer diagnostics and can be enabled/disabled using that flow.
<row>
<text>Hello!</text> // Error: 'row' cannot contain 'text' components
<button /> // Error: A button must specify either a 'customId' or 'url'
<button customId="abc" /> // Error: A button must have a 'label' or 'emoji'
</row>
Reusable Custom Components
You can define basic custom components which can be used within the CX syntax. As of now, there is only one type way of writing a custom component, and that is a functional component. In future versions there are plans for more component types and ways of writing components.
Functional components
A functional component is a function that is:
static
.- either
public
orinternal
. - returns a valid interpolation component type.
Functional components can have zero to many arguments, with limited support for compile-time validation against them. Some primitive argument types (string, int, emoji, etc) can make use of the CX syntax, but any complex or non-standard argument types must be supplied using string interpolation
using static Discord.ComponentDesigner;
var user = new User(name: "Example");
var component = cx(
$"""
<container>
<SayHello user={user} />
</container>
"""
)
public static CXMessageComponent SayHello(User user)
=> cx(
$"""
<text>
Hello, {user.Name}!
</text>
"""
);
[!TIP] You can use default values for parameters to indicate an optional property.
Functional components with children
It is possible to take in children of a component using the CXChildren
attribute, children are treated the same way that parameters as props are handles, but with a few key differences:
- Built-in conversion for text -> type (colors, ints, etc) is not supported.
- Children types that are not a string or valid component type are only allowed to be supplied by interpolation.
You can specify the cardinality of the number of children you wish to support by using a collection type, any non-collection type will only support a single child.
using static Discord.ComponentDesigner;
var user = new User(Name: "Example", Avatar: null);
var component = cx(
$"""
<container>
<MaybeIcon url={user.Avatar}>
<text>
Hello, {user.Name}
</text>
</MaybeIcon>
</container>
"""
);
public static CXMessageComponent MaybeIcon(
string? url,
[CXChildren] CXMessageComponent children
) {
if (url is null) return children;
return cx(
$"""
<section>
{children}
<accessory>
<thumbnail url={url} />
</accessory>
</section>
"""
);
}
Component cardinality
Some accepted component types have default cardinality, such as CXMessageComponent
allowing more than one component. This can restrict and effect custom components with children, specifying which component type you accept as children controls how many child components can be supplied to your custom component.
Type | Cardinality |
---|---|
CXMessageComponent | Many |
MessageComponent | Many |
IMessageComponent | One |
IMessageComponentBuilder | One |
Wrapping any of these types in a collection type gives it a cardinality of many, as well as ensuring that the collection represents the actual children supplied to your component.
[!NOTE] Splatting is automatically handled by the generator, an interpolation collection of components is unwrapped and supplied to your child type.
No packages depend on Discord.Net.ComponentDesigner.
.NET 8.0
- Discord.Net.Core (>= 3.18.0)
Version | Downloads | Last updated |
---|---|---|
0.0.1-nightly.20251020.8 | 2 | 10/20/2025 |
0.0.1-nightly.20251020.7 | 0 | 10/20/2025 |
0.0.1-nightly.20251020.6 | 0 | 10/20/2025 |