CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-nextcord

A Python wrapper for the Discord API forked from discord.py

Pending
Overview
Eval results
Files

ui.mddocs/

Nextcord UI Framework

Interactive Discord UI components including views, buttons, select menus, modals, and text inputs for rich user interactions.

Views

Container classes for UI components with interaction handling and lifecycle management.

View Class { .api }

import nextcord
from nextcord.ui import View, Item, Button, Select, Modal
from typing import Optional, List, Any, Callable

class View:
    """Represents a UI view with interactive components.
    
    A view is a collection of UI components that can be attached to messages
    to create interactive interfaces.
    
    Attributes
    ----------
    timeout: Optional[float]
        The timeout duration in seconds. None means no timeout.
    children: List[Item]
        The list of UI components in this view.
    """
    
    def __init__(self, *, timeout: Optional[float] = 180.0):
        """Initialize a new view.
        
        Parameters
        ----------
        timeout: Optional[float]
            The timeout for this view in seconds. Defaults to 180 seconds (3 minutes).
            Set to None for no timeout.
        """
        ...
    
    def add_item(self, item: Item) -> Self:
        """Add an item to this view.
        
        Parameters
        ----------
        item: Item
            The UI component to add to this view.
        
        Returns
        -------
        Self
            The view instance for method chaining.
        """
        ...
    
    def remove_item(self, item: Item) -> Self:
        """Remove an item from this view.
        
        Parameters
        ----------
        item: Item
            The UI component to remove from this view.
        
        Returns
        -------
        Self
            The view instance for method chaining.
        """
        ...
    
    def clear_items(self) -> Self:
        """Remove all items from this view.
        
        Returns
        -------
        Self
            The view instance for method chaining.
        """
        ...
    
    def stop(self) -> None:
        """Stop listening for interactions on this view."""
        ...
    
    def is_finished(self) -> bool:
        """bool: Whether this view has finished running."""
        ...
    
    def is_persistent(self) -> bool:
        """bool: Whether this view is persistent across bot restarts."""
        ...
    
    async def wait(self) -> bool:
        """Wait until the view finishes running.
        
        Returns
        -------
        bool
            True if the view finished due to timeout, False otherwise.
        """
        ...
    
    # Callback methods that can be overridden
    async def on_timeout(self) -> None:
        """Called when the view times out."""
        ...
    
    async def on_error(
        self, 
        interaction: nextcord.Interaction, 
        error: Exception, 
        item: Item
    ) -> None:
        """Called when an error occurs in a view interaction.
        
        Parameters
        ----------
        interaction: nextcord.Interaction
            The interaction that caused the error.
        error: Exception
            The error that occurred.
        item: Item
            The UI component that caused the error.
        """
        ...
    
    async def interaction_check(self, interaction: nextcord.Interaction) -> bool:
        """Check if an interaction should be processed by this view.
        
        Parameters
        ----------
        interaction: nextcord.Interaction
            The interaction to check.
        
        Returns
        -------
        bool
            True if the interaction should be processed, False otherwise.
        """
        return True

# Basic view example
class MyView(View):
    def __init__(self):
        super().__init__(timeout=60.0)  # 1 minute timeout
    
    @nextcord.ui.button(label="Click Me!", style=nextcord.ButtonStyle.primary)
    async def click_me(self, button: Button, interaction: nextcord.Interaction):
        await interaction.response.send_message("Button clicked!", ephemeral=True)
    
    async def on_timeout(self):
        # Disable all components when the view times out
        for child in self.children:
            child.disabled = True
        
        # You would typically edit the message here to show disabled state
        # This requires keeping a reference to the message

# Usage
@bot.slash_command(description="Show a view with a button")
async def show_view(interaction: nextcord.Interaction):
    view = MyView()
    await interaction.response.send_message("Here's a button:", view=view)

Persistent Views { .api }

class PersistentView(View):
    """A persistent view that survives bot restarts.
    
    Persistent views must be added to the bot during startup
    and use custom_id for their components.
    """
    
    def __init__(self):
        super().__init__(timeout=None)  # Persistent views don't timeout
    
    @nextcord.ui.button(
        label="Persistent Button",
        style=nextcord.ButtonStyle.secondary,
        custom_id="persistent:button:1"  # Must have custom_id
    )
    async def persistent_button(self, button: Button, interaction: nextcord.Interaction):
        await interaction.response.send_message(
            "This button works even after bot restart!",
            ephemeral=True
        )

# Add persistent views during bot startup
@bot.event
async def on_ready():
    print(f'{bot.user} has connected to Discord!')
    
    # Add persistent views
    bot.add_view(PersistentView())

# Example: Role assignment view
class RoleView(View):
    def __init__(self):
        super().__init__(timeout=None)
    
    @nextcord.ui.button(
        label="Get Member Role", 
        emoji="👤",
        style=nextcord.ButtonStyle.green,
        custom_id="role:member"
    )
    async def member_role(self, button: Button, interaction: nextcord.Interaction):
        role = nextcord.utils.get(interaction.guild.roles, name="Member")
        if role:
            if role in interaction.user.roles:
                await interaction.user.remove_roles(role)
                await interaction.response.send_message("Member role removed!", ephemeral=True)
            else:
                await interaction.user.add_roles(role)
                await interaction.response.send_message("Member role added!", ephemeral=True)
        else:
            await interaction.response.send_message("Member role not found!", ephemeral=True)
    
    @nextcord.ui.button(
        label="Get Notifications", 
        emoji="🔔",
        style=nextcord.ButtonStyle.blurple,
        custom_id="role:notifications"
    )
    async def notifications_role(self, button: Button, interaction: nextcord.Interaction):
        role = nextcord.utils.get(interaction.guild.roles, name="Notifications")
        if role:
            if role in interaction.user.roles:
                await interaction.user.remove_roles(role)
                await interaction.response.send_message("Notifications role removed!", ephemeral=True)
            else:
                await interaction.user.add_roles(role)
                await interaction.response.send_message("Notifications role added!", ephemeral=True)
        else:
            await interaction.response.send_message("Notifications role not found!", ephemeral=True)

Buttons

Clickable buttons with various styles and callback handling.

Button Class { .api }

class Button(Item):
    """Represents a button component.
    
    Attributes
    ----------
    style: ButtonStyle
        The style of the button.
    custom_id: Optional[str]
        The custom ID of the button.
    url: Optional[str]
        The URL this button links to (for link buttons).
    disabled: bool
        Whether the button is disabled.
    label: Optional[str]
        The label text displayed on the button.
    emoji: Optional[Union[str, Emoji, PartialEmoji]]
        The emoji displayed on the button.
    row: Optional[int]
        The row this button should be placed in (0-4).
    """
    
    def __init__(
        self,
        *,
        style: ButtonStyle = ButtonStyle.secondary,
        label: Optional[str] = None,
        disabled: bool = False,
        custom_id: Optional[str] = None,
        url: Optional[str] = None,
        emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
        row: Optional[int] = None,
    ):
        """Initialize a button.
        
        Parameters
        ----------
        style: ButtonStyle
            The visual style of the button.
        label: Optional[str]
            The text displayed on the button.
        disabled: bool
            Whether the button is disabled.
        custom_id: Optional[str]
            A custom ID for the button (required for non-link buttons).
        url: Optional[str]
            URL for link-style buttons.
        emoji: Optional[Union[str, Emoji, PartialEmoji]]
            Emoji to display on the button.
        row: Optional[int]
            Which row to place the button in (0-4).
        """
        ...
    
    async def callback(self, interaction: nextcord.Interaction) -> None:
        """The callback function called when the button is pressed.
        
        This method should be overridden in subclasses or set via decorator.
        
        Parameters
        ----------
        interaction: nextcord.Interaction
            The interaction that triggered this button press.
        """
        ...

# Button styles
class ButtonStyle(Enum):
    primary = 1      # Blurple
    secondary = 2    # Grey  
    success = 3      # Green
    danger = 4       # Red
    link = 5         # Grey with link

    # Aliases
    blurple = 1
    grey = 2
    gray = 2
    green = 3
    red = 4
    url = 5

# Button decorator usage
class ButtonView(View):
    @nextcord.ui.button(label="Primary", style=nextcord.ButtonStyle.primary)
    async def primary_button(self, button: Button, interaction: nextcord.Interaction):
        await interaction.response.send_message("Primary button pressed!", ephemeral=True)
    
    @nextcord.ui.button(label="Success", style=nextcord.ButtonStyle.success, emoji="✅")
    async def success_button(self, button: Button, interaction: nextcord.Interaction):
        await interaction.response.send_message("Success button pressed!", ephemeral=True)
    
    @nextcord.ui.button(label="Danger", style=nextcord.ButtonStyle.danger, emoji="❌")
    async def danger_button(self, button: Button, interaction: nextcord.Interaction):
        await interaction.response.send_message("Danger button pressed!", ephemeral=True)
    
    @nextcord.ui.button(label="Visit GitHub", style=nextcord.ButtonStyle.link, url="https://github.com")
    async def link_button(self, button: Button, interaction: nextcord.Interaction):
        # This callback will never be called for link buttons
        pass

# Dynamic button creation
class DynamicButtonView(View):
    def __init__(self):
        super().__init__()
        
        # Add buttons dynamically
        for i in range(3):
            button = Button(
                label=f"Button {i+1}",
                style=nextcord.ButtonStyle.secondary,
                custom_id=f"dynamic_button_{i}"
            )
            button.callback = self.dynamic_button_callback
            self.add_item(button)
    
    async def dynamic_button_callback(self, interaction: nextcord.Interaction):
        # Get which button was pressed from custom_id
        button_num = interaction.data["custom_id"].split("_")[-1]
        await interaction.response.send_message(f"Dynamic button {button_num} pressed!", ephemeral=True)

# Button with state management
class CounterView(View):
    def __init__(self):
        super().__init__()
        self.counter = 0
        self.update_button_label()
    
    def update_button_label(self):
        # Find and update the counter button
        for child in self.children:
            if hasattr(child, 'custom_id') and child.custom_id == "counter":
                child.label = f"Count: {self.counter}"
                break
    
    @nextcord.ui.button(label="Count: 0", style=nextcord.ButtonStyle.primary, custom_id="counter")
    async def counter_button(self, button: Button, interaction: nextcord.Interaction):
        self.counter += 1
        self.update_button_label()
        
        # Update the message with the new view
        await interaction.response.edit_message(view=self)
    
    @nextcord.ui.button(label="Reset", style=nextcord.ButtonStyle.secondary)
    async def reset_button(self, button: Button, interaction: nextcord.Interaction):
        self.counter = 0
        self.update_button_label()
        await interaction.response.edit_message(view=self)

Select Menus

Dropdown select menus for choosing from predefined options.

Select Menu Types { .api }

from nextcord.ui import Select, UserSelect, RoleSelect, MentionableSelect, ChannelSelect
from nextcord import SelectOption, ChannelType

class Select(Item):
    """String-based select menu with custom options.
    
    Attributes
    ----------
    custom_id: Optional[str]
        The custom ID of the select menu.
    placeholder: Optional[str]
        Placeholder text shown when nothing is selected.
    min_values: int
        Minimum number of options that must be selected.
    max_values: int
        Maximum number of options that can be selected.
    options: List[SelectOption]
        The list of options available in this select menu.
    disabled: bool
        Whether the select menu is disabled.
    row: Optional[int]
        The row this select menu should be placed in (0-4).
    """
    
    def __init__(
        self,
        *,
        custom_id: Optional[str] = None,
        placeholder: Optional[str] = None,
        min_values: int = 1,
        max_values: int = 1,
        options: List[SelectOption] = None,
        disabled: bool = False,
        row: Optional[int] = None,
    ):
        """Initialize a select menu.
        
        Parameters
        ----------
        custom_id: Optional[str]
            A custom ID for the select menu.
        placeholder: Optional[str]
            Placeholder text to display.
        min_values: int
            Minimum number of selections required.
        max_values: int
            Maximum number of selections allowed.
        options: List[SelectOption]
            List of options for the select menu.
        disabled: bool
            Whether the select menu is disabled.
        row: Optional[int]
            Which row to place the select menu in (0-4).
        """
        ...
    
    def add_option(
        self,
        *,
        label: str,
        value: str,
        description: Optional[str] = None,
        emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
        default: bool = False,
    ) -> None:
        """Add an option to this select menu.
        
        Parameters
        ----------
        label: str
            The user-facing name of the option.
        value: str
            The developer-defined value of the option.
        description: Optional[str]
            An additional description of the option.
        emoji: Optional[Union[str, Emoji, PartialEmoji]]
            An emoji for the option.
        default: bool
            Whether this option is selected by default.
        """
        ...
    
    async def callback(self, interaction: nextcord.Interaction) -> None:
        """Called when an option is selected.
        
        Parameters
        ----------
        interaction: nextcord.Interaction
            The interaction containing the selected values.
        """
        ...

class SelectOption:
    """Represents an option in a select menu.
    
    Attributes
    ----------
    label: str
        The user-facing name of the option.
    value: str
        The developer-defined value of the option.
    description: Optional[str]
        An additional description of the option.
    emoji: Optional[Union[str, Emoji, PartialEmoji]]
        An emoji that appears on the option.
    default: bool
        Whether this option is selected by default.
    """
    
    def __init__(
        self,
        *,
        label: str,
        value: str,
        description: Optional[str] = None,
        emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
        default: bool = False,
    ):
        """Create a select option.
        
        Parameters
        ----------
        label: str
            The display name for this option.
        value: str
            The value returned when this option is selected.
        description: Optional[str]
            Additional description text for this option.
        emoji: Optional[Union[str, Emoji, PartialEmoji]]
            Emoji to display with this option.
        default: bool
            Whether this option is selected by default.
        """
        ...

# Basic select menu example
class ColorSelectView(View):
    @nextcord.ui.select(
        placeholder="Choose your favorite color...",
        min_values=1,
        max_values=1,
        options=[
            nextcord.SelectOption(
                label="Red",
                description="The color of fire",
                emoji="🔴",
                value="red"
            ),
            nextcord.SelectOption(
                label="Blue", 
                description="The color of water",
                emoji="🔵",
                value="blue"
            ),
            nextcord.SelectOption(
                label="Green",
                description="The color of nature",
                emoji="🟢", 
                value="green"
            ),
        ]
    )
    async def select_callback(self, select: Select, interaction: nextcord.Interaction):
        selected_color = select.values[0]
        await interaction.response.send_message(
            f"You selected {selected_color}!",
            ephemeral=True
        )

# Multi-select example
class HobbiesSelectView(View):
    @nextcord.ui.select(
        placeholder="Select your hobbies (up to 3)...",
        min_values=0,
        max_values=3,
        options=[
            nextcord.SelectOption(label="Gaming", value="gaming", emoji="🎮"),
            nextcord.SelectOption(label="Reading", value="reading", emoji="📚"),
            nextcord.SelectOption(label="Music", value="music", emoji="🎵"),
            nextcord.SelectOption(label="Sports", value="sports", emoji="⚽"),
            nextcord.SelectOption(label="Art", value="art", emoji="🎨"),
            nextcord.SelectOption(label="Cooking", value="cooking", emoji="👨‍🍳"),
        ]
    )
    async def hobbies_callback(self, select: Select, interaction: nextcord.Interaction):
        if select.values:
            hobbies = ", ".join(select.values)
            await interaction.response.send_message(
                f"Your selected hobbies: {hobbies}",
                ephemeral=True
            )
        else:
            await interaction.response.send_message(
                "You didn't select any hobbies.",
                ephemeral=True
            )

Discord Entity Select Menus { .api }

# User select menu
class UserSelect(Item):
    """A select menu for choosing users/members.
    
    Attributes
    ----------
    custom_id: Optional[str]
        The custom ID of the select menu.
    placeholder: Optional[str]
        Placeholder text shown when nothing is selected.
    min_values: int
        Minimum number of users that must be selected.
    max_values: int
        Maximum number of users that can be selected.
    disabled: bool
        Whether the select menu is disabled.
    """
    pass

class RoleSelect(Item):
    """A select menu for choosing roles.
    
    Attributes
    ----------
    custom_id: Optional[str]
        The custom ID of the select menu.
    placeholder: Optional[str]
        Placeholder text shown when nothing is selected.
    min_values: int
        Minimum number of roles that must be selected.
    max_values: int
        Maximum number of roles that can be selected.
    disabled: bool
        Whether the select menu is disabled.
    """
    pass

class MentionableSelect(Item):
    """A select menu for choosing mentionable entities (users and roles).
    
    Attributes
    ----------
    custom_id: Optional[str]
        The custom ID of the select menu.
    placeholder: Optional[str]
        Placeholder text shown when nothing is selected.
    min_values: int
        Minimum number of entities that must be selected.
    max_values: int
        Maximum number of entities that can be selected.
    disabled: bool
        Whether the select menu is disabled.
    """
    pass

class ChannelSelect(Item):
    """A select menu for choosing channels.
    
    Attributes
    ----------
    custom_id: Optional[str]
        The custom ID of the select menu.
    placeholder: Optional[str]
        Placeholder text shown when nothing is selected.
    min_values: int
        Minimum number of channels that must be selected.
    max_values: int
        Maximum number of channels that can be selected.
    channel_types: Optional[List[ChannelType]]
        The types of channels that can be selected.
    disabled: bool
        Whether the select menu is disabled.
    """
    pass

# Entity select examples
class EntitySelectView(View):
    @nextcord.ui.user_select(placeholder="Select users...", min_values=1, max_values=3)
    async def user_select_callback(self, select: UserSelect, interaction: nextcord.Interaction):
        users = [user.mention for user in select.values]
        await interaction.response.send_message(
            f"Selected users: {', '.join(users)}",
            ephemeral=True
        )
    
    @nextcord.ui.role_select(placeholder="Select roles...", min_values=1, max_values=2)
    async def role_select_callback(self, select: RoleSelect, interaction: nextcord.Interaction):
        roles = [role.mention for role in select.values]
        await interaction.response.send_message(
            f"Selected roles: {', '.join(roles)}",
            ephemeral=True
        )
    
    @nextcord.ui.channel_select(
        placeholder="Select channels...",
        channel_types=[nextcord.ChannelType.text, nextcord.ChannelType.voice]
    )
    async def channel_select_callback(self, select: ChannelSelect, interaction: nextcord.Interaction):
        channels = [channel.mention for channel in select.values]
        await interaction.response.send_message(
            f"Selected channels: {', '.join(channels)}",
            ephemeral=True
        )

# Moderation panel example
class ModerationView(View):
    def __init__(self):
        super().__init__(timeout=300.0)  # 5 minute timeout
    
    @nextcord.ui.user_select(
        placeholder="Select user to moderate...",
        min_values=1,
        max_values=1
    )
    async def select_user(self, select: UserSelect, interaction: nextcord.Interaction):
        user = select.values[0]
        
        # Create a follow-up view with moderation actions
        mod_view = UserModerationView(user)
        
        embed = nextcord.Embed(
            title="User Moderation",
            description=f"Selected user: {user.mention}",
            color=nextcord.Color.orange()
        )
        
        await interaction.response.send_message(
            embed=embed,
            view=mod_view,
            ephemeral=True
        )

class UserModerationView(View):
    def __init__(self, user: nextcord.Member):
        super().__init__(timeout=180.0)
        self.user = user
    
    @nextcord.ui.button(label="Timeout", style=nextcord.ButtonStyle.secondary, emoji="⏰")
    async def timeout_user(self, button: Button, interaction: nextcord.Interaction):
        # Open modal for timeout duration
        modal = TimeoutModal(self.user)
        await interaction.response.send_modal(modal)
    
    @nextcord.ui.button(label="Kick", style=nextcord.ButtonStyle.danger, emoji="👢")
    async def kick_user(self, button: Button, interaction: nextcord.Interaction):
        try:
            await self.user.kick(reason=f"Kicked by {interaction.user}")
            await interaction.response.send_message(
                f"✅ {self.user.mention} has been kicked.",
                ephemeral=True
            )
        except nextcord.Forbidden:
            await interaction.response.send_message(
                "❌ I don't have permission to kick this user.",
                ephemeral=True
            )
    
    @nextcord.ui.button(label="Ban", style=nextcord.ButtonStyle.danger, emoji="🔨")
    async def ban_user(self, button: Button, interaction: nextcord.Interaction):
        try:
            await self.user.ban(reason=f"Banned by {interaction.user}")
            await interaction.response.send_message(
                f"✅ {self.user.mention} has been banned.",
                ephemeral=True
            )
        except nextcord.Forbidden:
            await interaction.response.send_message(
                "❌ I don't have permission to ban this user.",
                ephemeral=True
            )

Modals

Dialog forms for collecting text input from users.

Modal Class { .api }

from nextcord.ui import Modal, TextInput
from nextcord import TextInputStyle

class Modal:
    """Represents a modal dialog form.
    
    Modals are popup forms that can collect text input from users.
    They can contain up to 5 TextInput components.
    
    Attributes
    ----------
    title: str
        The title of the modal.
    timeout: Optional[float]
        The timeout for this modal in seconds.
    children: List[TextInput]
        The text inputs in this modal.
    """
    
    def __init__(self, *, title: str, timeout: Optional[float] = None):
        """Initialize a modal.
        
        Parameters
        ----------
        title: str
            The title displayed at the top of the modal.
        timeout: Optional[float]
            The timeout for this modal in seconds.
        """
        ...
    
    def add_item(self, item: TextInput) -> None:
        """Add a text input to this modal.
        
        Parameters
        ----------
        item: TextInput
            The text input to add.
        """
        ...
    
    def remove_item(self, item: TextInput) -> None:
        """Remove a text input from this modal.
        
        Parameters
        ----------
        item: TextInput
            The text input to remove.
        """
        ...
    
    def clear_items(self) -> None:
        """Remove all text inputs from this modal."""
        ...
    
    async def on_submit(self, interaction: nextcord.Interaction) -> None:
        """Called when the modal is submitted.
        
        This method should be overridden to handle modal submissions.
        
        Parameters
        ----------
        interaction: nextcord.Interaction
            The interaction containing the submitted data.
        """
        ...
    
    async def on_error(
        self, 
        interaction: nextcord.Interaction, 
        error: Exception
    ) -> None:
        """Called when an error occurs in modal handling.
        
        Parameters
        ----------
        interaction: nextcord.Interaction
            The interaction that caused the error.
        error: Exception
            The error that occurred.
        """
        ...
    
    async def on_timeout(self) -> None:
        """Called when the modal times out."""
        ...

class TextInput(Item):
    """A text input field for modals.
    
    Attributes
    ----------
    label: str
        The label for this text input.
    style: TextInputStyle
        The style of the text input (short or paragraph).
    custom_id: Optional[str]
        The custom ID of this text input.
    placeholder: Optional[str]
        Placeholder text shown when the input is empty.
    default: Optional[str]
        Default text to pre-fill the input with.
    required: bool
        Whether this input is required.
    min_length: Optional[int]
        Minimum length of the input.
    max_length: Optional[int]
        Maximum length of the input.
    value: Optional[str]
        The current value of the text input (available after submission).
    """
    
    def __init__(
        self,
        *,
        label: str,
        style: TextInputStyle = TextInputStyle.short,
        custom_id: Optional[str] = None,
        placeholder: Optional[str] = None,
        default: Optional[str] = None,
        required: bool = True,
        min_length: Optional[int] = None,
        max_length: Optional[int] = None,
        row: Optional[int] = None,
    ):
        """Initialize a text input.
        
        Parameters
        ----------
        label: str
            The label displayed above the text input.
        style: TextInputStyle
            Whether this is a short (single-line) or paragraph (multi-line) input.
        custom_id: Optional[str]
            A custom ID for this text input.
        placeholder: Optional[str]
            Placeholder text to show when empty.
        default: Optional[str]
            Default text to pre-fill the input.
        required: bool
            Whether this input must be filled out.
        min_length: Optional[int]
            Minimum character length.
        max_length: Optional[int]
            Maximum character length.
        row: Optional[int]
            Which row to place this input in.
        """
        ...

class TextInputStyle(Enum):
    """Text input styles for modals."""
    short = 1      # Single line input
    paragraph = 2  # Multi-line input

# Basic modal example
class FeedbackModal(Modal):
    def __init__(self):
        super().__init__(title="Feedback Form", timeout=300.0)
    
    name = nextcord.ui.TextInput(
        label="Name",
        placeholder="Enter your name here...",
        required=True,
        max_length=50
    )
    
    feedback = nextcord.ui.TextInput(
        label="Feedback",
        style=nextcord.TextInputStyle.paragraph,
        placeholder="Tell us what you think...",
        required=True,
        max_length=1000
    )
    
    rating = nextcord.ui.TextInput(
        label="Rating (1-10)",
        placeholder="Rate from 1 to 10",
        required=False,
        max_length=2
    )
    
    async def on_submit(self, interaction: nextcord.Interaction):
        embed = nextcord.Embed(
            title="Feedback Received",
            color=nextcord.Color.green()
        )
        embed.add_field(name="Name", value=self.name.value, inline=True)
        embed.add_field(name="Rating", value=self.rating.value or "Not provided", inline=True)
        embed.add_field(name="Feedback", value=self.feedback.value, inline=False)
        
        await interaction.response.send_message(
            "Thank you for your feedback!",
            embed=embed,
            ephemeral=True
        )

# Button that opens a modal
class FeedbackView(View):
    @nextcord.ui.button(label="Give Feedback", style=nextcord.ButtonStyle.primary, emoji="📝")
    async def feedback_button(self, button: Button, interaction: nextcord.Interaction):
        modal = FeedbackModal()
        await interaction.response.send_modal(modal)

# Advanced modal with validation
class UserRegistrationModal(Modal):
    def __init__(self):
        super().__init__(title="User Registration", timeout=600.0)
    
    username = nextcord.ui.TextInput(
        label="Username",
        placeholder="Choose a username...",
        required=True,
        min_length=3,
        max_length=20
    )
    
    email = nextcord.ui.TextInput(
        label="Email",
        placeholder="your.email@example.com",
        required=True,
        max_length=100
    )
    
    age = nextcord.ui.TextInput(
        label="Age",
        placeholder="Enter your age",
        required=True,
        max_length=3
    )
    
    bio = nextcord.ui.TextInput(
        label="Bio (Optional)",
        style=nextcord.TextInputStyle.paragraph,
        placeholder="Tell us about yourself...",
        required=False,
        max_length=500
    )
    
    async def on_submit(self, interaction: nextcord.Interaction):
        # Validate age
        try:
            age = int(self.age.value)
            if age < 13 or age > 120:
                await interaction.response.send_message(
                    "❌ Age must be between 13 and 120.",
                    ephemeral=True
                )
                return
        except ValueError:
            await interaction.response.send_message(
                "❌ Please enter a valid age (numbers only).",
                ephemeral=True
            )
            return
        
        # Validate email (basic check)
        import re
        email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(email_pattern, self.email.value):
            await interaction.response.send_message(
                "❌ Please enter a valid email address.",
                ephemeral=True
            )
            return
        
        # Registration successful
        embed = nextcord.Embed(
            title="Registration Successful!",
            description="Welcome to our community!",
            color=nextcord.Color.green()
        )
        embed.add_field(name="Username", value=self.username.value, inline=True)
        embed.add_field(name="Age", value=self.age.value, inline=True)
        embed.add_field(name="Email", value=self.email.value, inline=False)
        
        if self.bio.value:
            embed.add_field(name="Bio", value=self.bio.value, inline=False)
        
        await interaction.response.send_message(embed=embed, ephemeral=True)
    
    async def on_error(self, interaction: nextcord.Interaction, error: Exception):
        await interaction.response.send_message(
            "❌ An error occurred during registration. Please try again.",
            ephemeral=True
        )
        print(f"Modal error: {error}")

# Timeout modal example
class TimeoutModal(Modal):
    def __init__(self, user: nextcord.Member):
        super().__init__(title=f"Timeout {user.display_name}")
        self.user = user
    
    duration = nextcord.ui.TextInput(
        label="Duration (minutes)",
        placeholder="Enter timeout duration in minutes (1-40320)",
        required=True,
        max_length=5
    )
    
    reason = nextcord.ui.TextInput(
        label="Reason",
        style=nextcord.TextInputStyle.paragraph,
        placeholder="Reason for timeout (optional)",
        required=False,
        max_length=200
    )
    
    async def on_submit(self, interaction: nextcord.Interaction):
        try:
            duration_minutes = int(self.duration.value)
            
            if duration_minutes < 1 or duration_minutes > 40320:  # Max 4 weeks
                await interaction.response.send_message(
                    "❌ Duration must be between 1 and 40320 minutes (4 weeks).",
                    ephemeral=True
                )
                return
            
            from datetime import datetime, timedelta
            until = datetime.utcnow() + timedelta(minutes=duration_minutes)
            reason = self.reason.value or f"Timed out by {interaction.user}"
            
            await self.user.timeout(until=until, reason=reason)
            
            embed = nextcord.Embed(
                title="User Timed Out",
                description=f"{self.user.mention} has been timed out for {duration_minutes} minutes.",
                color=nextcord.Color.orange()
            )
            embed.add_field(name="Reason", value=reason, inline=False)
            embed.add_field(
                name="Until", 
                value=until.strftime("%Y-%m-%d %H:%M UTC"), 
                inline=True
            )
            
            await interaction.response.send_message(embed=embed, ephemeral=True)
            
        except ValueError:
            await interaction.response.send_message(
                "❌ Please enter a valid number for duration.",
                ephemeral=True
            )
        except nextcord.Forbidden:
            await interaction.response.send_message(
                "❌ I don't have permission to timeout this user.",
                ephemeral=True
            )
        except Exception as e:
            await interaction.response.send_message(
                "❌ An error occurred while timing out the user.",
                ephemeral=True
            )

Advanced UI Examples

Complex UI implementations combining multiple components.

Multi-Page Interface { .api }

class PaginatorView(View):
    """A paginated interface for displaying multiple pages of content."""
    
    def __init__(self, pages: List[nextcord.Embed]):
        super().__init__(timeout=300.0)
        self.pages = pages
        self.current_page = 0
        self.update_buttons()
    
    def update_buttons(self):
        """Update button states based on current page."""
        self.first_page.disabled = self.current_page == 0
        self.previous_page.disabled = self.current_page == 0
        self.next_page.disabled = self.current_page == len(self.pages) - 1
        self.last_page.disabled = self.current_page == len(self.pages) - 1
    
    @nextcord.ui.button(label="<<", style=nextcord.ButtonStyle.secondary)
    async def first_page(self, button: Button, interaction: nextcord.Interaction):
        self.current_page = 0
        self.update_buttons()
        
        embed = self.pages[self.current_page]
        embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
        
        await interaction.response.edit_message(embed=embed, view=self)
    
    @nextcord.ui.button(label="<", style=nextcord.ButtonStyle.primary)
    async def previous_page(self, button: Button, interaction: nextcord.Interaction):
        self.current_page -= 1
        self.update_buttons()
        
        embed = self.pages[self.current_page]
        embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
        
        await interaction.response.edit_message(embed=embed, view=self)
    
    @nextcord.ui.button(label=">", style=nextcord.ButtonStyle.primary)
    async def next_page(self, button: Button, interaction: nextcord.Interaction):
        self.current_page += 1
        self.update_buttons()
        
        embed = self.pages[self.current_page]
        embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
        
        await interaction.response.edit_message(embed=embed, view=self)
    
    @nextcord.ui.button(label=">>", style=nextcord.ButtonStyle.secondary)
    async def last_page(self, button: Button, interaction: nextcord.Interaction):
        self.current_page = len(self.pages) - 1
        self.update_buttons()
        
        embed = self.pages[self.current_page]
        embed.set_footer(text=f"Page {self.current_page + 1} of {len(self.pages)}")
        
        await interaction.response.edit_message(embed=embed, view=self)
    
    @nextcord.ui.button(label="Jump to Page", style=nextcord.ButtonStyle.secondary, emoji="🔢")
    async def jump_to_page(self, button: Button, interaction: nextcord.Interaction):
        modal = JumpToPageModal(self)
        await interaction.response.send_modal(modal)
    
    async def on_timeout(self):
        # Disable all buttons when view times out
        for child in self.children:
            child.disabled = True

class JumpToPageModal(Modal):
    def __init__(self, paginator: PaginatorView):
        super().__init__(title="Jump to Page")
        self.paginator = paginator
    
    page_number = nextcord.ui.TextInput(
        label=f"Page Number (1-{len(paginator.pages)})",
        placeholder="Enter page number...",
        required=True,
        max_length=3
    )
    
    async def on_submit(self, interaction: nextcord.Interaction):
        try:
            page_num = int(self.page_number.value)
            if 1 <= page_num <= len(self.paginator.pages):
                self.paginator.current_page = page_num - 1
                self.paginator.update_buttons()
                
                embed = self.paginator.pages[self.paginator.current_page]
                embed.set_footer(text=f"Page {self.paginator.current_page + 1} of {len(self.paginator.pages)}")
                
                await interaction.response.edit_message(embed=embed, view=self.paginator)
            else:
                await interaction.response.send_message(
                    f"❌ Page number must be between 1 and {len(self.paginator.pages)}.",
                    ephemeral=True
                )
        except ValueError:
            await interaction.response.send_message(
                "❌ Please enter a valid page number.",
                ephemeral=True
            )

# Usage example
@bot.slash_command(description="Show paginated help")
async def help_pages(interaction: nextcord.Interaction):
    # Create multiple embed pages
    pages = []
    
    # Page 1 - Commands
    embed1 = nextcord.Embed(title="Bot Help - Commands", color=nextcord.Color.blue())
    embed1.add_field(name="/help", value="Show this help message", inline=False)
    embed1.add_field(name="/info", value="Get bot information", inline=False)
    embed1.add_field(name="/ping", value="Check bot latency", inline=False)
    pages.append(embed1)
    
    # Page 2 - Features
    embed2 = nextcord.Embed(title="Bot Help - Features", color=nextcord.Color.green())
    embed2.add_field(name="Moderation", value="Timeout, kick, ban users", inline=False)
    embed2.add_field(name="Utility", value="Server info, user info", inline=False)
    embed2.add_field(name="Fun", value="Games and entertainment", inline=False)
    pages.append(embed2)
    
    # Page 3 - Support
    embed3 = nextcord.Embed(title="Bot Help - Support", color=nextcord.Color.red())
    embed3.add_field(name="Support Server", value="[Join Here](https://discord.gg/example)", inline=False)
    embed3.add_field(name="Bug Reports", value="Use `/report` command", inline=False)
    embed3.add_field(name="Feature Requests", value="Use `/suggest` command", inline=False)
    pages.append(embed3)
    
    # Set initial page footer
    pages[0].set_footer(text=f"Page 1 of {len(pages)}")
    
    view = PaginatorView(pages)
    await interaction.response.send_message(embed=pages[0], view=view)

Complex Configuration Panel { .api }

class ServerConfigView(View):
    """A comprehensive server configuration panel."""
    
    def __init__(self, guild: nextcord.Guild):
        super().__init__(timeout=600.0)  # 10 minute timeout
        self.guild = guild
        self.config = self.load_config(guild.id)  # Load from database/file
    
    def load_config(self, guild_id: int) -> dict:
        """Load server configuration (implement your storage here)."""
        return {
            "welcome_channel": None,
            "log_channel": None,
            "auto_role": None,
            "prefix": "!",
            "moderation": True,
            "auto_mod": False
        }
    
    def save_config(self) -> None:
        """Save server configuration (implement your storage here)."""
        # Save self.config to database/file
        pass
    
    @nextcord.ui.select(
        placeholder="Configure server settings...",
        options=[
            nextcord.SelectOption(
                label="Welcome System",
                description="Set up welcome messages and channel",
                emoji="👋",
                value="welcome"
            ),
            nextcord.SelectOption(
                label="Logging",
                description="Configure audit log channel",
                emoji="📝",
                value="logging"
            ),
            nextcord.SelectOption(
                label="Auto Role",
                description="Set role to assign to new members",
                emoji="👤",
                value="auto_role"
            ),
            nextcord.SelectOption(
                label="Moderation",
                description="Toggle moderation features",
                emoji="🔨",
                value="moderation"
            ),
        ]
    )
    async def config_select(self, select: Select, interaction: nextcord.Interaction):
        selection = select.values[0]
        
        if selection == "welcome":
            view = WelcomeConfigView(self.guild, self.config)
        elif selection == "logging":
            view = LoggingConfigView(self.guild, self.config)
        elif selection == "auto_role":
            view = AutoRoleConfigView(self.guild, self.config)
        elif selection == "moderation":
            view = ModerationConfigView(self.guild, self.config)
        
        embed = nextcord.Embed(
            title=f"{selection.replace('_', ' ').title()} Configuration",
            description="Use the buttons below to configure this feature.",
            color=nextcord.Color.blue()
        )
        
        await interaction.response.edit_message(embed=embed, view=view)
    
    @nextcord.ui.button(label="Save & Exit", style=nextcord.ButtonStyle.success, emoji="💾")
    async def save_and_exit(self, button: Button, interaction: nextcord.Interaction):
        self.save_config()
        
        embed = nextcord.Embed(
            title="Configuration Saved",
            description="All changes have been saved successfully!",
            color=nextcord.Color.green()
        )
        
        await interaction.response.edit_message(embed=embed, view=None)
        self.stop()

class WelcomeConfigView(View):
    def __init__(self, guild: nextcord.Guild, config: dict):
        super().__init__(timeout=300.0)
        self.guild = guild
        self.config = config
    
    @nextcord.ui.channel_select(
        placeholder="Select welcome channel...",
        channel_types=[nextcord.ChannelType.text]
    )
    async def welcome_channel_select(self, select: ChannelSelect, interaction: nextcord.Interaction):
        channel = select.values[0]
        self.config["welcome_channel"] = channel.id
        
        await interaction.response.send_message(
            f"✅ Welcome channel set to {channel.mention}",
            ephemeral=True
        )
    
    @nextcord.ui.button(label="Set Welcome Message", style=nextcord.ButtonStyle.primary)
    async def set_welcome_message(self, button: Button, interaction: nextcord.Interaction):
        modal = WelcomeMessageModal(self.config)
        await interaction.response.send_modal(modal)
    
    @nextcord.ui.button(label="Back to Main", style=nextcord.ButtonStyle.secondary)
    async def back_to_main(self, button: Button, interaction: nextcord.Interaction):
        main_view = ServerConfigView(self.guild)
        main_view.config = self.config  # Preserve changes
        
        embed = nextcord.Embed(
            title="Server Configuration",
            description="Choose a category to configure:",
            color=nextcord.Color.blue()
        )
        
        await interaction.response.edit_message(embed=embed, view=main_view)

class WelcomeMessageModal(Modal):
    def __init__(self, config: dict):
        super().__init__(title="Set Welcome Message")
        self.config = config
    
    message = nextcord.ui.TextInput(
        label="Welcome Message",
        style=nextcord.TextInputStyle.paragraph,
        placeholder="Welcome {user} to {server}! Available: {user}, {server}, {mention}",
        default=config.get("welcome_message", ""),
        max_length=1000
    )
    
    async def on_submit(self, interaction: nextcord.Interaction):
        self.config["welcome_message"] = self.message.value
        
        await interaction.response.send_message(
            "✅ Welcome message updated!",
            ephemeral=True
        )

This comprehensive documentation covers all major aspects of nextcord's UI framework, providing developers with the tools to create rich, interactive Discord bot interfaces using views, buttons, select menus, and modals.

Install with Tessl CLI

npx tessl i tessl/pypi-nextcord

docs

application-commands.md

channels.md

client.md

commands.md

errors.md

events.md

guild.md

index.md

messages.md

permissions.md

tasks.md

ui.md

users.md

utilities.md

voice.md

webhooks.md

tile.json