diff --git a/guide/interactions/modals.md b/guide/interactions/modals.md
index 6ef916486..2c4ebfa44 100644
--- a/guide/interactions/modals.md
+++ b/guide/interactions/modals.md
@@ -11,7 +11,7 @@ This page is a follow-up to the [interactions (slash commands) page](/slash-comm
Unlike message components, modals aren't strictly components themselves. They're a callback structure used to respond to interactions.
::: tip
-You can have a maximum of five s per modal builder, and one within an . Currently, you can only use s in modal action rows builders.
+You can have a maximum of five s per modal builder, and one or within an .
:::
To create a modal you construct a new . You can then use the setters to add the custom id and title.
@@ -35,21 +35,12 @@ client.on(Events.InteractionCreate, async interaction => {
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!
:::
-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 the components to which users can respond. Adding components to modals is similar to adding components to messages.
-At the end, we then call to display the modal to the user.
-
-::: warning
-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
-
-```diff
-- new ActionRowBuilder()
-+ new ActionRowBuilder()
-```
-:::
+At the end, we then call to display the modal to the user.
```js {1,12-34}
-const { ActionRowBuilder, Events, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
+const { Events, ModalBuilder, TextInputBuilder, TextInputStyle, LabelBuilder, StringSelectMenuBuilder } = require('discord.js');
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
@@ -62,27 +53,58 @@ client.on(Events.InteractionCreate, async interaction => {
// Add components to modal
- // Create the text input components
+ // Create the interactive 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);
- // 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);
+ const favoriteStarterSelect = new StringSelectMenuBuilder()
+ .setCustomId('starter')
+ .setPlaceholder('Make a selection!')
+ .addOptions(
+ // String select menu options
+ new StringSelectMenuOptionBuilder()
+ // Label displayed to user
+ .setLabel('Bulbasaur')
+ // Description of option
+ .setDescription('The dual-type Grass/Poison Seed Pokémon.')
+ // Value returned to in modal submission
+ .setValue('bulbasaur'),
+ new StringSelectMenuOptionBuilder()
+ .setLabel('Charmander')
+ .setDescription('The Fire-type Lizard Pokémon.')
+ .setValue('charmander'),
+ new StringSelectMenuOptionBuilder()
+ .setLabel('Squirtle')
+ .setDescription('The Water-type Tiny Turtle Pokémon.')
+ .setValue('squirtle'),
+ );
+ // An Label component only holds one interactive component,
+ // so you need one Label component per text input or string select menu.
+ const favoriteColorLabel = new LabelBuilder()
+ // The label is the prompt the user sees for this component
+ .setLabel("What's your favorite color?")
+ .setTextInputComponent(favoriteColorInput);
+
+ const hobbiesLabel = new LabelBuilder()
+ .setLabel("What's some of your favorite hobbies?")
+ // The Description is small text under the label above the interactive component
+ .setDescription('card game, film, book, etc.')
+ .setTextInputComponent(hobbiesInput);
+
+ const favoriteStarterLabel = new LabelBuilder()
+ .setLabel("What's some of your favorite Gen 1 Pokémon starter?")
+ // The Description is small text under the label above the interactive component
+ .setStringSelectMenuComponent(favoriteStarterSelect);
- // Add inputs to the modal
- modal.addComponents(firstActionRow, secondActionRow);
+ // Add Label components to the modal
+ modal.setLabelComponents(favoriteColorLabel, hobbiesLabel, favoriteStarterLabel);
// Show the modal to the user
await interaction.showModal(modal);
@@ -92,10 +114,11 @@ client.on(Events.InteractionCreate, async interaction => {
Restart your bot and invoke the `/ping` command again. You should see a popup form resembling the image below:
-
+
+
::: warning
-Showing a modal must be the first response to an interaction. You cannot `defer()` or `deferUpdate()` then show a modal later.
+Showing a modal must be the first response to an interaction. You cannot `deferReply()` or `deferUpdate()` then show a modal later.
:::
### Input styles
@@ -106,10 +129,12 @@ Currently there are two different input styles available:
### 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 methods:
+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 methods:
```js
const input = new TextInputBuilder()
+ // sets the id (not the custom id) for this component.
+ .setId(33)
// set the maximum number of characters to allow
.setMaxLength(1_000)
// set the minimum number of characters required for submission
@@ -122,6 +147,31 @@ const input = new TextInputBuilder()
.setRequired(true);
```
+### Select Menu properties
+
+In addition to the `customId`, `placeholder` and `options`, a text input can be customised in a number of ways to apply validation, prompt the user the methods:
+
+```js
+const input = new StringSelectMenuBuilder()
+ // sets the id (not the custom id) for this component.
+ .setId(33)
+ // maximum number of option that could be selected
+ .setMaxValues(10)
+ // minimum number of option that could be selected
+ .setMinValues(2)
+ // require a selection for this select menu
+ .setRequired(true);
+```
+To add set default values of a select menu use `setDefault(true)` in the option builder
+
+```js
+const option = StringSelectMenuOptionBuilder()
+ .setDefault(true)
+ .setLabel('Charmander')
+ .setDescription('The Fire-type Lizard Pokémon.')
+ .setValue('charmander');
+```
+
## Receiving modal submissions
### Interaction collectors
@@ -165,7 +215,7 @@ client.on(Events.InteractionCreate, async interaction => {
```
::: tip
-If you're using typescript, you can use the typeguard, to make sure the received interaction was from a `MessageComponentInteraction`.
+If you're using typescript, you can use the type guard, to make sure the received interaction was from a .
:::
## Extracting data from modal submissions
@@ -179,6 +229,7 @@ client.on(Events.InteractionCreate, interaction => {
// Get the data entered by the user
const favoriteColor = interaction.fields.getTextInputValue('favoriteColorInput');
const hobbies = interaction.fields.getTextInputValue('hobbiesInput');
- console.log({ favoriteColor, hobbies });
+ const favoriteStarter = interaction.fields.getStringSelectMenuValues('starter');
+ console.log({ favoriteColor, hobbies, favoriteStarter });
});
```