DEPRECATED - Use components-v2-modals instead
This skill has been replaced by the new Components v2 skills:
components-v2-modals - Modals with Labelscomponents-v2-buttons - Button componentscomponents-v2-selects - Select menuscomponents-v2-action-rows - Action row layoutsPlease use the new skills for updated, concise guidance on Discord Components v2.
Provides guidance on creating Discord modals with labels, text inputs, select menus, and file uploads using the Serenity library in the Open Guard bot.
Use this skill when you need to:
A modal is an interactive popup form that can contain various input components. Modals are triggered in response to interactions and allow collecting multiple pieces of information from users in a structured way.
A Label is a top-level layout component that wraps modal input components with descriptive text, providing context for what the user should enter.
use poise::serenity_prelude as serenity;
// Create a modal response
let modal = serenity::CreateModal::new()
.custom_id("my_modal")
.title("Modal Title")
.components(vec![
// Label components go here
]);
Labels wrap input components and provide better organization:
use poise::serenity_prelude as serenity;
let label = serenity::CreateLabel::input_text(
"Field Label",
serenity::CreateInputText::new(
serenity::InputTextStyle::Short,
"input_field_custom_id"
)
.placeholder("Enter text here...")
.required(true)
);
serenity::CreateLabel::input_text(
"Email Address",
serenity::CreateInputText::new(
serenity::InputTextStyle::Short,
"user_email"
)
.placeholder("user@example.com")
.min_length(5)
.max_length(100)
.required(true)
)
Text input styles:
InputTextStyle::Short - Single lineInputTextStyle::Paragraph - Multi-lineserenity::CreateLabel::select_menu(
"Choose an option",
serenity::CreateSelectMenu::new(
"select_custom_id",
serenity::CreateSelectMenuKind::String {
options: Cow::Borrowed(&[
serenity::CreateSelectMenuOption::new("Option 1", "opt1"),
serenity::CreateSelectMenuOption::new("Option 2", "opt2"),
])
}
)
.placeholder("Select an option")
.required(true)
)
serenity::CreateLabel::select_menu(
"Select Users",
serenity::CreateSelectMenu::new(
"user_select_id",
serenity::CreateSelectMenuKind::User {
default_users: None
}
)
.min_values(1)
.max_values(3)
.required(true)
)
serenity::CreateLabel::select_menu(
"Select Roles",
serenity::CreateSelectMenu::new(
"role_select_id",
serenity::CreateSelectMenuKind::Role {
default_roles: None
}
)
.required(true)
)
serenity::CreateLabel::select_menu(
"Select Channels",
serenity::CreateSelectMenu::new(
"channel_select_id",
serenity::CreateSelectMenuKind::Channel {
channel_types: Some(Cow::Borrowed(&[
serenity::ChannelType::Text,
serenity::ChannelType::Voice
])),
default_channels: None
}
)
)
serenity::CreateLabel::file_upload(
"Upload Files",
serenity::CreateFileUpload::new("file_upload_id")
.min_values(1)
.max_values(5)
.required(true)
)
serenity::CreateLabel::select_menu(
"Select Users or Roles",
serenity::CreateSelectMenu::new(
"mentionable_select_id",
serenity::CreateSelectMenuKind::Mentionable {
default_users: None,
default_roles: None
}
)
)
use poise::serenity_prelude as serenity;
use std::borrow::Cow;
async fn show_feedback_modal(ctx: &serenity::Context) -> Result<(), Box<dyn std::error::Error>> {
let modal = serenity::CreateModal::new()
.custom_id("feedback_modal")
.title("Feedback Form")
.components(vec![
serenity::CreateLabel::input_text(
"Your Feedback",
serenity::CreateInputText::new(
serenity::InputTextStyle::Paragraph,
"feedback_text"
)
.placeholder("Write your feedback here...")
.required(true)
),
serenity::CreateLabel::select_menu(
"Rating",
serenity::CreateSelectMenu::new(
"rating",
serenity::CreateSelectMenuKind::String {
options: Cow::Borrowed(&[
serenity::CreateSelectMenuOption::new("Excellent", "5"),
serenity::CreateSelectMenuOption::new("Good", "4"),
serenity::CreateSelectMenuOption::new("Average", "3"),
])
}
)
.placeholder("Select a rating")
.required(true)
)
]);
// In a command handler, show the modal
interaction.create_response(
ctx,
serenity::CreateInteractionResponse::Modal(modal)
).await?;
Ok(())
}
Modal submissions arrive as interactions with InteractionType::MODAL_SUBMIT (type 5).
async fn handle_modal_submit(
ctx: &serenity::Context,
interaction: &serenity::ModalSubmitInteraction,
data: &Data
) -> Result<(), Error> {
let custom_id = &interaction.data.custom_id;
if custom_id == "feedback_modal" {
// Extract values from labels
for component in &interaction.data.components {
if let serenity::ActionRowComponent::InputText(input) = component {
let custom_id = &input.custom_id;
let value = &input.value;
match custom_id.as_str() {
"feedback_text" => {
println!("Feedback: {}", value);
}
_ => {}
}
}
}
// Send confirmation
interaction.create_response(
ctx,
serenity::CreateInteractionResponse::UpdateMessage(
serenity::CreateInteractionResponseMessage::new()
.content("Thank you for your feedback!")
)
).await?;
}
Ok(())
}
required field defaults to true for most componentsSee docs/MODALS.md for comprehensive documentation and the Serenity implementation examples.