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 -![Modal Example](./images/modal-example.png) +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 `