diff --git a/apps/guide/content/docs/legacy/interactions/images/modal-example.png b/apps/guide/content/docs/legacy/interactions/images/modal-example.png
index 9cee3d67d630..6efaff6304f9 100644
Binary files a/apps/guide/content/docs/legacy/interactions/images/modal-example.png and b/apps/guide/content/docs/legacy/interactions/images/modal-example.png differ
diff --git a/apps/guide/content/docs/legacy/interactions/images/selectephem.png b/apps/guide/content/docs/legacy/interactions/images/selectephem.png
deleted file mode 100644
index 2f109b358862..000000000000
Binary files a/apps/guide/content/docs/legacy/interactions/images/selectephem.png and /dev/null differ
diff --git a/apps/guide/content/docs/legacy/interactions/modals.mdx b/apps/guide/content/docs/legacy/interactions/modals.mdx
index 4bdc32bc4a6f..dc9411404767 100644
--- a/apps/guide/content/docs/legacy/interactions/modals.mdx
+++ b/apps/guide/content/docs/legacy/interactions/modals.mdx
@@ -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 using discord.js!
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.
- 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` may only contain
+ one component.
To create a modal you construct a new `ModalBuilder`. You can then use the setters to add the custom id and title.
@@ -25,7 +25,6 @@ const { Events, ModalBuilder } = require('discord.js');
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
-
if (interaction.commandName === 'ping') {
const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
@@ -35,103 +34,504 @@ 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 `customId` is a developer-defined string of up to 100 characters. This field is the unique identifier of the
+ modal, it is used to differentiate incoming modal interactions. 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 components to the `ModalBuilder`.
+
+### Label
+
+Label components are used to add labels and descriptions to interactive modal components (text input, select menus, etc).
+
+```js
+const { LabelBuilder, ModalBuilder } = require('discord.js');
-At the end, we then call `ChatInputCommandInteraction#showModal` to display the modal to the user.
+client.on(Events.InteractionCreate, async (interaction) => {
+ if (!interaction.isChatInputCommand()) return;
+ if (interaction.commandName === 'ping') {
+ // Create the modal
+ const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
+
+ // [!code focus:12]
+ // TODO: Add more interactive components...
+
+ // [!code ++:5]
+ const hobbiesLabel = new LabelBuilder()
+ // The label is a large header text that identifies the interactive component for the user.
+ .setLabel("What's some of your favorite hobbies?")
+ // The description is optional small text beneath the label that provides additional information about the interactive component.
+ .setDescription('Activities you like to participate in');
+
+ // TODO: Add more components...
+
+ // [!code ++:2]
+ // Add label to the modal
+ modal.addLabelComponents(hobbiesLabel);
+
+ // TODO: Respond with modal...
+ }
+});
+```
-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`:
+ `label` has a max length of 45 characters.
+
+ `description` has a max length of 100 characters.
+
+
+
+### Text Input
+
+A text input is an interactive component used to receive free-form text.
+
+```js
+const { LabelBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
+
+client.on(Events.InteractionCreate, async (interaction) => {
+ if (!interaction.isChatInputCommand()) return;
+ if (interaction.commandName === 'ping') {
+ // Create the modal
+ const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
+
+ // [!code focus:6]
+ // [!code ++:6]
+ const hobbiesInput = new TextInputBuilder()
+ .setCustomId('hobbiesInput')
+ // Short means a single line of text.
+ .setStyle(TextInputStyle.Short)
+ // Placeholder text displayed inside the text input box
+ .setPlaceholder('card games, films, books, etc.');
+
+ // TODO: Add more interactive components
+
+ // [!code focus:8]
+ const hobbiesLabel = new LabelBuilder()
+ // The label is a large header that identifies the interactive component for the user.
+ .setLabel("What's some of your favorite hobbies?")
+ // The description is optional small text beneath the label that provides additional information about the interactive component.
+ .setDescription('Activities you like to participate in')
+ // [!code ++:2]
+ // Set text input as the component of the label
+ .setTextInputComponent(hobbiesInput);
+
+ // TODO: Add more components...
-```diff
-- new ActionRowBuilder()
-+ new ActionRowBuilder()
+ // Add the label to the modal
+ modal.addLabelComponents(hobbiesLabel);
+
+ // TODO: Respond with modal...
+ }
+});
+```
+
+#### Input styles
+
+Currently there are two different input styles available:
+
+- `Short`, a single-line text entry
+- `Paragraph`, a multi-line text entry
+
+#### Input properties
+
+In addition to the `customId` and `style`, a text input can be customized 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 component id (this is not the custom id)
+ .setId(0)
+ // Set the maximum number of characters 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);
```
+### Select Menu
+
+A select menu is an interactive component that allows you to limit user inputs to a preselected list of values.
+
+In addition to string select menus, menus for users, roles, mentionables (user and roles), and channels can be used in modals.
+
+
+ For more information on configuring select menus, see the [corresponding
+ page](../interactive-components/select-menus). interaction page.
```js
-const { ActionRowBuilder, Events, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
+const {
+ StringSelectMenuBuilder,
+ StringSelectMenuOptionBuilder,
+ LabelBuilder,
+ ModalBuilder,
+ TextInputBuilder,
+ TextInputStyle,
+} = require('discord.js');
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
+ if (interaction.commandName === 'ping') {
+ // Create the modal
+ const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
+
+ const hobbiesInput = new TextInputBuilder()
+ .setCustomId('hobbiesInput')
+ // Short means a single line of text.
+ .setStyle(TextInputStyle.Short)
+ // Placeholder text displayed inside the text input box
+ .setPlaceholder('card games, films, books, etc.');
+
+ // [!code focus:24]
+ // [!code ++:24]
+ const favoriteStarterSelect = new StringSelectMenuBuilder()
+ .setCustomId('starter')
+ .setPlaceholder('Make a selection!')
+ // Modal only property on select menus to prevent submission, defaults to true
+ .setRequired(true)
+ .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 you 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'),
+ );
+
+ const hobbiesLabel = new LabelBuilder()
+ // The label is a large header that identifies the interactive component for the user.
+ .setLabel("What's some of your favorite hobbies?")
+ // The description is optional small text beneath the label that provides additional information about the interactive component.
+ .setDescription('Activities you like to participate in')
+ // Set text input as component of the label
+ .setTextInputComponent(hobbiesInput);
+
+ // [!code focus:4]
+ // [!code ++:4]
+ const favoriteStarterLabel = new LabelBuilder()
+ .setLabel("What's your favorite Gen 1 Pokémon starter?")
+ // Set string select menu as component of the label
+ .setStringSelectMenuComponent(favoriteStarterSelect);
+
+ // TODO: Add more components...
+
+ // [!code focus:3]
+ // Add labels to modal
+ modal.addLabelComponents(hobbiesLabel); // [!code --]
+ modal.addLabelComponents(hobbiesLabel, favoriteStarterLabel); // [!code ++]
+
+ // TODO: Respond with modal...
+ }
+});
+```
+
+### Text Display
+Text display components are used to display text to the user which does not fit into labels or other interactive components.
+
+```js
+const {
+ StringSelectMenuBuilder,
+ StringSelectMenuOptionBuilder,
+ LabelBuilder,
+ ModalBuilder,
+ TextInputBuilder,
+ TextInputStyle,
+} = require('discord.js');
+
+client.on(Events.InteractionCreate, async (interaction) => {
+ if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
// Create the modal
const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
+ const hobbiesInput = new TextInputBuilder()
+ .setCustomId('hobbiesInput')
+ // Short means a single line of text.
+ .setStyle(TextInputStyle.Short)
+ // Placeholder text displayed inside the text input box
+ .setPlaceholder('card games, films, books, etc.');
+
+ const favoriteStarterSelect = new StringSelectMenuBuilder()
+ .setCustomId('starter')
+ .setPlaceholder('Make a selection!')
+ // Modal only property on select menus to prevent submission, defaults to true
+ .setRequired(true)
+ .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 you 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'),
+ );
+
+ const hobbiesLabel = new LabelBuilder()
+ // The label is a large header that identifies the interactive component for the user.
+ .setLabel("What's some of your favorite hobbies?")
+ // The description is optional small text beneath the label that provides additional information about the interactive component.
+ .setDescription('Activities you like to participate in')
+ // Set text input as component of the label
+ .setTextInputComponent(hobbiesInput);
+
+ const favoriteStarterLabel = new LabelBuilder()
+ .setLabel("What's your favorite Gen 1 Pokémon starter?")
+ // Set string select menu as component of the label
+ .setStringSelectMenuComponent(favoriteStarterSelect);
+
+ // [!code focus:3]
+ // [!code ++:2]
+ const text = new TextDisplayBuilder().setContent(
+ 'Text that could not fit in to a label or description\n-# Markdown can also be used',
+ );
+
+ // TODO: Add more components...
+
+ // [!code focus:4]
// Add components to modal
+ modal
+ .addLabelComponents(hobbiesLabel, favoriteStarterLabel)
+ // [!code ++:1]
+ .addTextDisplayComponents(text);
+
+ // TODO: Respond with modal...
+ }
+});
+```
+
+### File Upload
- // 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);
+File upload is an interactive component that allows a user to upload files to a modal.
+
+```js
+const {
+ StringSelectMenuBuilder,
+ StringSelectMenuOptionBuilder,
+ LabelBuilder,
+ ModalBuilder,
+ TextInputBuilder,
+ TextInputStyle,
+} = require('discord.js');
+
+client.on(Events.InteractionCreate, async (interaction) => {
+ if (!interaction.isChatInputCommand()) return;
+ if (interaction.commandName === 'ping') {
+ // Create the modal
+ const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
const hobbiesInput = new TextInputBuilder()
.setCustomId('hobbiesInput')
+ // Short means a single line of text.
+ .setStyle(TextInputStyle.Short)
+ // Placeholder text displayed inside the text input box
+ .setPlaceholder('card games, films, books, etc.');
+
+ const favoriteStarterSelect = new StringSelectMenuBuilder()
+ .setCustomId('starter')
+ .setPlaceholder('Make a selection!')
+ // Modal only property on select menus to prevent submission, defaults to true
+ .setRequired(true)
+ .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 you 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'),
+ );
+ // [!code focus:2]
+ // [!code ++:2]
+ const pictureOfTheWeekUpload = new FileUploadBuilder().setCustomId('picture');
+
+ const hobbiesLabel = new LabelBuilder()
+ // The label is a large header that identifies the interactive component for the user.
.setLabel("What's some of your favorite hobbies?")
- // Paragraph means multiple lines of text.
- .setStyle(TextInputStyle.Paragraph);
+ // The description is optional small text beneath the label that provides additional information about the interactive component.
+ .setDescription('Activities you like to participate in')
+ // set text input component of label
+ .setTextInputComponent(hobbiesInput);
+
+ const favoriteStarterLabel = new LabelBuilder()
+ .setLabel("What's your favorite Gen 1 Pokémon starter?")
+ .setStringSelectMenuComponent(favoriteStarterSelect);
+
+ const text = new TextDisplayBuilder().setContent(
+ 'Text that could not fit in to a label or description\n-# Markdown can also be used',
+ );
+
+ // [!code focus:11]
+ // [!code ++:5]
+ const pictureOfTheWeekLabel = new LabelBuilder()
+ .setLabel('Picture of the Week')
+ .setDescription('The best pictures you have taken this week')
+ // Set file upload as component of the label
+ .setFileUploadComponent(pictureOfTheWeekUpload);
- // 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);
-
- // Add inputs to the modal
- modal.addComponents(firstActionRow, secondActionRow);
+ // Add components to modal
+ modal
+ .addLabelComponents(hobbiesLabel, favoriteStarterLabel)
+ .addTextDisplayComponents(text)
+ // [!code ++:1]
+ .addLabelComponents(pictureOfTheWeekLabel);
- // Show the modal to the user
- await interaction.showModal(modal); // [!code word:showModal]
+ // TODO: Respond with modal...
}
});
```
-Restart your bot and invoke the `/ping` command again. You should see a popup form resembling the image below:
+#### File Upload properties
-
+In addition to the `customId`, a file upload component can be customized via the `FileUploadBuilder` methods:
+
+```js
+const pictureOfTheWeekUpload = new FileUploadBuilder()
+ // Set the optional identifier for component
+ .setId(0)
+ // Minimum number of items that must be uploaded (defaults to 1); min 0, max 10
+ .setMinValues(1)
+ // Maximum number of items that can be uploaded (defaults to 1); max 10
+ .setMaxValues(1)
+ // Require a value in this text input field (defaults to true)
+ .setRequired(true);
+```
+
+File upload component can not be customized to limit file size or file extension
+
+### Responding with modal
+
+With the modal built call `ChatInputCommandInteraction#showModal` to display the modal to the user.
- 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.
-### Input styles
+```js
+const {
+ StringSelectMenuBuilder,
+ StringSelectMenuOptionBuilder,
+ LabelBuilder,
+ ModalBuilder,
+ TextInputBuilder,
+ TextInputStyle,
+} = require('discord.js');
-Currently there are two different input styles available:
+client.on(Events.InteractionCreate, async (interaction) => {
+ if (!interaction.isChatInputCommand()) return;
+ if (interaction.commandName === 'ping') {
+ // Create the modal
+ const modal = new ModalBuilder().setCustomId('myModal').setTitle('My Modal');
-- `Short`, a single-line text entry;
-- `Paragraph`, a multi-line text entry similar to the HTML `
-### String select menu options
+## String select menu options
String select menu options are custom-defined by the user, as shown in the example above. At a minimum, each option must have it's `label` and `value` defined. The label is shown to the user, while the value is included in the interaction sent to the bot.
diff --git a/apps/guide/content/docs/legacy/popular-topics/display-components.mdx b/apps/guide/content/docs/legacy/popular-topics/display-components.mdx
index dc9b363ca39d..269c5934822f 100644
--- a/apps/guide/content/docs/legacy/popular-topics/display-components.mdx
+++ b/apps/guide/content/docs/legacy/popular-topics/display-components.mdx
@@ -32,6 +32,9 @@ Text Display components let you add markdown-formatted text to your message and
Sending user and role mentions in text display components **will notify users and roles**! You can and should control
mentions with the `allowedMentions` message option.
+
+ Text display components can be used in modals. See the [modal guide](../interactions/modals#text-display) for usage.
+
The example below shows how you can send a Text Display component in a channel.