-
-
Notifications
You must be signed in to change notification settings - Fork 4k
feat(guide): updated modal page for label #11169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c4ce41d
7de1d62
20fbe64
acc545b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,7 +2,7 @@ | |||||
title: Modals | ||||||
--- | ||||||
|
||||||
With modals you can create pop-up forms that allow users to provide you with formatted inputs through submissions. We'll cover how to create, show, and receive modal forms using discord.js! | ||||||
With modals you can create pop-up forms that allow users to provide you with formatted inputs through submissions. We'll cover how to create, show, and receive modals | ||||||
|
||||||
<Callout> | ||||||
This page is a follow-up to the [interactions (slash commands) page](../slash-commands/advanced-creation). Please | ||||||
|
@@ -14,8 +14,8 @@ With modals you can create pop-up forms that allow users to provide you with for | |||||
Unlike message components, modals aren't strictly components themselves. They're a callback structure used to respond to interactions. | ||||||
|
||||||
<Callout> | ||||||
You can have a maximum of five `ActionRowBuilder`s per modal builder, and one `TextInputBuilder` within an | ||||||
`ActionRowBuilder`. Currently, you can only use `TextInputBuilder`s in modal action rows builders. | ||||||
You can have a maximum of five `Label` or `Text Display` components per modal. Similarly a `Label` must only contain | ||||||
one component. | ||||||
</Callout> | ||||||
|
||||||
To create a modal you construct a new `ModalBuilder`. You can then use the setters to add the custom id and title. | ||||||
|
@@ -36,25 +36,15 @@ client.on(Events.InteractionCreate, async (interaction) => { | |||||
|
||||||
<Callout> | ||||||
The custom id is a developer-defined string of up to 100 characters. Use this field to ensure you can uniquely define | ||||||
all incoming interactions from your modals! | ||||||
all incoming interactions from your modals. | ||||||
Comment on lines
38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. while we're changing this page, i think we can use much clearer wording here. |
||||||
</Callout> | ||||||
|
||||||
The next step is to add the input fields in which users responding can enter free-text. Adding inputs is similar to adding components to messages. | ||||||
The next step is to add a Modal components to the `modalBuilder`. Which users responding can enter free-text. Adding inputs is similar to adding components to messages. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Modal components" shouldn't be plural. |
||||||
|
||||||
At the end, we then call `ChatInputCommandInteraction#showModal` to display the modal to the user. | ||||||
|
||||||
<Callout type="warn"> | ||||||
If you're using typescript you'll need to specify the type of components your action row holds. This can be done by specifying the generic parameter in `ActionRowBuilder`: | ||||||
|
||||||
```diff | ||||||
- new ActionRowBuilder() | ||||||
+ new ActionRowBuilder<ModalActionRowComponentBuilder>() | ||||||
``` | ||||||
|
||||||
</Callout> | ||||||
|
||||||
```js | ||||||
const { ActionRowBuilder, Events, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js'); | ||||||
const { Events, LabelBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js'); | ||||||
|
||||||
client.on(Events.InteractionCreate, async (interaction) => { | ||||||
if (!interaction.isChatInputCommand()) return; | ||||||
|
@@ -63,70 +53,51 @@ client.on(Events.InteractionCreate, async (interaction) => { | |||||
// Create the modal | ||||||
const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal'); | ||||||
|
||||||
// Add components to modal | ||||||
|
||||||
// [!code focus:31] | ||||||
// Create the text input components | ||||||
const favoriteColorInput = new TextInputBuilder() | ||||||
.setCustomId('favoriteColorInput') | ||||||
// The label is the prompt the user sees for this input | ||||||
.setLabel("What's your favorite color?") | ||||||
// Short means only a single line of text | ||||||
.setStyle(TextInputStyle.Short); | ||||||
|
||||||
const hobbiesInput = new TextInputBuilder() | ||||||
.setCustomId('hobbiesInput') | ||||||
.setLabel("What's some of your favorite hobbies?") | ||||||
// Paragraph means multiple lines of text. | ||||||
.setStyle(TextInputStyle.Paragraph); | ||||||
.setStyle(TextInputStyle.Paragraph) | ||||||
// Uninteractable text inside of the text input | ||||||
.setPlaceholder('card games, films, books, etc.'); | ||||||
|
||||||
// An action row only holds one text input, | ||||||
// so you need one action row per text input. | ||||||
const firstActionRow = new ActionRowBuilder().addComponents(favoriteColorInput); | ||||||
const secondActionRow = new ActionRowBuilder().addComponents(hobbiesInput); | ||||||
// Creating labels for the text input components | ||||||
const favoriteColorLabel = new LabelBuilder() | ||||||
// The label is the prompt the user sees for this component | ||||||
.setLabel("What's your favorite color?") | ||||||
// Add the text input to the label | ||||||
.setTextInputComponent(favoriteColorInput); | ||||||
|
||||||
const hobbiesLabel = new LabelBuilder() | ||||||
.setLabel("What's some of your favorite hobbies?") | ||||||
// The description is a small text under the label and above the interactive component | ||||||
.setDescription('Activities you like to participate in') | ||||||
.setTextInputComponent(hobbiesInput); | ||||||
|
||||||
// Add inputs to the modal | ||||||
modal.addComponents(firstActionRow, secondActionRow); | ||||||
// Add labels to the modal | ||||||
modal.addLabelComponents(favoriteColorLabel, hobbiesLabel); | ||||||
|
||||||
// Show the modal to the user | ||||||
await interaction.showModal(modal); // [!code word:showModal] | ||||||
} | ||||||
}); | ||||||
``` | ||||||
|
||||||
Restart your bot and invoke the `/ping` command again. You should see a popup form resembling the image below: | ||||||
Restart your bot and invoke the `/ping` command again. You should see the modal as imaged below: | ||||||
|
||||||
 | ||||||
|
||||||
<Callout type="warn"> | ||||||
Showing a modal must be the first response to an interaction. You cannot `defer()` or `deferUpdate()` then show a | ||||||
Showing a modal must be the first response to an interaction. You cannot `deferReply()` or `deferUpdate()` then show a | ||||||
modal later. | ||||||
</Callout> | ||||||
|
||||||
### Input styles | ||||||
|
||||||
Currently there are two different input styles available: | ||||||
|
||||||
- `Short`, a single-line text entry; | ||||||
- `Paragraph`, a multi-line text entry similar to the HTML `<textarea>`; | ||||||
|
||||||
### Input properties | ||||||
|
||||||
In addition to the `customId`, `label` and `style`, a text input can be customised in a number of ways to apply validation, prompt the user, or set default values via the `TextInputBuilder` methods: | ||||||
|
||||||
```js | ||||||
const input = new TextInputBuilder() | ||||||
// set the maximum number of characters to allow | ||||||
.setMaxLength(1_000) | ||||||
// set the minimum number of characters required for submission | ||||||
.setMinLength(10) | ||||||
// set a placeholder string to prompt the user | ||||||
.setPlaceholder('Enter some text!') | ||||||
// set a default value to pre-fill the input | ||||||
.setValue('Default') | ||||||
// require a value in this input field | ||||||
.setRequired(true); | ||||||
``` | ||||||
|
||||||
## Receiving modal submissions | ||||||
|
||||||
### Interaction collectors | ||||||
|
@@ -165,14 +136,15 @@ If the modal was shown from a `ButtonInteraction` or `StringSelectMenuInteractio | |||||
```js | ||||||
client.on(Events.InteractionCreate, async (interaction) => { | ||||||
if (!interaction.isModalSubmit()) return; | ||||||
// [!code focus:3] | ||||||
if (interaction.customId === 'myModal') { | ||||||
await interaction.reply({ content: 'Your submission was received successfully!' }); | ||||||
} | ||||||
}); | ||||||
``` | ||||||
|
||||||
<Callout> | ||||||
If you're using typescript, you can use the `ModalSubmitInteraction#isFromMessage` typeguard, to make sure the | ||||||
If you're using typescript, you can use the `ModalSubmitInteraction#isFromMessage` type guard, to make sure the | ||||||
received interaction was from a `MessageComponentInteraction`. | ||||||
</Callout> | ||||||
|
||||||
|
@@ -183,10 +155,89 @@ You'll most likely need to read the data sent by the user in the modal. You can | |||||
```js | ||||||
client.on(Events.InteractionCreate, (interaction) => { | ||||||
if (!interaction.isModalSubmit()) return; | ||||||
if (interaction.customId === 'myModal') { | ||||||
await interaction.reply({ content: 'Your submission was received successfully!' }); | ||||||
|
||||||
// Get the data entered by the user | ||||||
const favoriteColor = interaction.fields.getTextInputValue('favoriteColorInput'); | ||||||
const hobbies = interaction.fields.getTextInputValue('hobbiesInput'); | ||||||
console.log({ favoriteColor, hobbies }); | ||||||
// [!code focus:5] | ||||||
// Get the data entered by the user | ||||||
const favoriteColor = interaction.fields.getTextInputValue('favoriteColorInput'); | ||||||
const hobbies = interaction.fields.getTextInputValue('hobbiesInput'); | ||||||
|
||||||
console.log({ favoriteColor, hobbies }); | ||||||
} | ||||||
}); | ||||||
``` | ||||||
|
||||||
## Modal Components | ||||||
|
||||||
Modals are built using components. | ||||||
|
||||||
<Callout> | ||||||
Current supported component for modals are: - [Label](#label) - A layout component to add interactive components to | ||||||
modals - [Text Display](#text-display) - A content component used to contain text | ||||||
</Callout> | ||||||
Comment on lines
+175
to
+178
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this callout is beneficial to the guide and may cause further maintenance work if more components are added. |
||||||
|
||||||
### Label | ||||||
|
||||||
A Layout component, labels are used to display a label and description ore interactive components in modals. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
<Callout type="warn"> | ||||||
`label` can be a max length of 45 characters `description` can be a max length of 100 characters | ||||||
</Callout> | ||||||
<Callout> | ||||||
Labels need to have one interactive components. Current supported component for labels are: - [Text | ||||||
Input](#text-input) - An interactive component allowing free form text input - [Select | ||||||
Menus](../interactive-components/select-menus#using-select-menus-in-modals) - Interactive components allowing for | ||||||
limiting user input to users, roles, channels, and preselected options | ||||||
</Callout> | ||||||
Comment on lines
+187
to
+192
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, not a huge fan of sections that need to be remembered if more components are added in the future. |
||||||
|
||||||
### Text Input | ||||||
|
||||||
### Input styles | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Assuming this should be nested under "Input". H3 (empty) > H3 is not valid |
||||||
|
||||||
Currently there are two different input styles available: | ||||||
|
||||||
- `Short`, a single-line text entry | ||||||
- `Paragraph`, a multi-line text entry | ||||||
|
||||||
### Input properties | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
same as above |
||||||
|
||||||
In addition to the `customId` and `style`, a text input can be customised in a number of ways to apply validation, prompt the user, or set default values via the `TextInputBuilder` methods: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prefer using the american english word for "customised" ("customized") |
||||||
|
||||||
```js | ||||||
const input = new TextInputBuilder() | ||||||
// set the component id (this is not the custom id) | ||||||
.setId(0) | ||||||
// Set the maximum number of characters to allowed | ||||||
.setMaxLength(1_000) | ||||||
// Set the minimum number of characters required for submission | ||||||
.setMinLength(10) | ||||||
// Set a default value to pre-fill the text input | ||||||
.setValue('Default') | ||||||
// Require a value in this text input field (defaults to true) | ||||||
.setRequired(true); | ||||||
``` | ||||||
|
||||||
### Text Display | ||||||
|
||||||
Modals support adding a texts display. Unlike interactive components a text display is added to the modal builder, without being put in a label first. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "texts" is not supposed to be plural. |
||||||
|
||||||
<Callout> | ||||||
Adding text display components decrees the number of labels that can be added to the modal. The modal only has maximum | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. did you mean "decreases"? |
||||||
of five `Label` or `Text Display` components. | ||||||
</Callout> | ||||||
|
||||||
```js | ||||||
const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal'); | ||||||
|
||||||
// Set the content of the text display | ||||||
const text = new TextDisplayBuilder().setContent( | ||||||
"## Important Information\nAccording to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off the ground. The bee, of course, flies anyway because bees don't care what humans think is impossible.", | ||||||
); | ||||||
|
||||||
// Add the text display to the Modal | ||||||
modal.addTextDisplayComponents(text); | ||||||
``` | ||||||
|
||||||
Below image is an example of a modal with only one text display in it: | ||||||
 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as per Discord devs, display-only modals being supported is more a side effect and a "not explicitly handled to be denied" rather than a fully thought-through feature. (It having 2 buttons makes little sense, for example). i'd prefer showcasing a modal that shows all the "base" types of components |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.