CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-disnake

A modern, easy-to-use, feature-rich async-ready API wrapper for Discord written in Python

Pending
Overview
Eval results
Files

interactions-ui.mddocs/

Interactions and UI Components

Interactive Discord UI elements including buttons, select menus, modals, and views for creating rich user interfaces. These components enable persistent interactions and complex user workflows within Discord.

Capabilities

Views

Container classes that manage collections of interactive UI components with persistent state and callback handling.

class View:
    def __init__(
        self,
        *,
        timeout: Optional[float] = 180.0,
        auto_defer: bool = True
    ):
        """
        Create a view for managing UI components.
        
        Parameters:
        - timeout: View timeout in seconds (None for no timeout)
        - auto_defer: Whether to auto-defer interactions
        """

    timeout: Optional[float]
    children: List[Item]

    def add_item(self, item: Item) -> Self:
        """
        Add a UI component to this view.
        
        Parameters:
        - item: Component to add
        
        Returns:
        Self for method chaining
        """

    def remove_item(self, item: Item) -> Self:
        """
        Remove a UI component from this view.
        
        Parameters:
        - item: Component to remove
        
        Returns:
        Self for method chaining
        """

    def clear_items(self) -> Self:
        """
        Remove all items from this view.
        
        Returns:
        Self for method chaining
        """

    def get_item(self, custom_id: str) -> Optional[Item]:
        """
        Get an item by custom_id.
        
        Parameters:
        - custom_id: Custom ID to search for
        
        Returns:
        Item if found
        """

    async def interaction_check(self, interaction: Interaction) -> bool:
        """
        Check if interaction should be processed by this view.
        
        Parameters:
        - interaction: Interaction to check
        
        Returns:
        True if interaction should be processed
        """

    async def on_timeout(self) -> None:
        """Called when the view times out."""

    async def on_error(self, interaction: Interaction, error: Exception, item: Item) -> None:
        """
        Called when an error occurs in a component callback.
        
        Parameters:
        - interaction: Interaction that caused the error
        - error: Exception that occurred
        - item: Component that caused the error
        """

    def stop(self) -> None:
        """Stop the view and prevent further interactions."""

    def is_finished(self) -> bool:
        """Whether the view has finished (stopped or timed out)."""

    def is_persistent(self) -> bool:
        """Whether the view is persistent (all items have custom_id)."""

    @classmethod
    def from_message(cls, message: Message, *, timeout: Optional[float] = 180.0) -> View:
        """
        Create a view from existing message components.
        
        Parameters:
        - message: Message with components
        - timeout: View timeout
        
        Returns:
        View instance with message components
        """

Buttons

Clickable button components with various styles and callback handling.

class Button(Item):
    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
    ):
        """
        Create a button component.
        
        Parameters:
        - style: Button style (primary, secondary, success, danger, link)
        - label: Button text label
        - disabled: Whether button is disabled
        - custom_id: Custom ID for persistent buttons
        - url: URL for link buttons
        - emoji: Button emoji
        - row: Action row (0-4)
        """

    style: ButtonStyle
    label: Optional[str]
    disabled: bool
    custom_id: Optional[str]
    url: Optional[str]
    emoji: Optional[Union[str, Emoji, PartialEmoji]]

    async def callback(self, interaction: MessageInteraction) -> None:
        """
        Button click callback. Override this method.
        
        Parameters:
        - interaction: Button click interaction
        """

def button(
    *,
    label: Optional[str] = None,
    custom_id: Optional[str] = None,
    disabled: bool = False,
    style: ButtonStyle = ButtonStyle.secondary,
    emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
    row: Optional[int] = None
):
    """
    Decorator for button callbacks in views.
    
    Parameters:
    - label: Button text label
    - custom_id: Custom ID for persistent buttons
    - disabled: Whether button is disabled
    - style: Button style
    - emoji: Button emoji
    - row: Action row (0-4)
    
    Returns:
    Decorated method as a Button component
    """

Select Menus

Dropdown selection components with multiple variations for different data types.

class BaseSelect(Item):
    def __init__(
        self,
        *,
        custom_id: Optional[str] = None,
        placeholder: Optional[str] = None,
        min_values: int = 1,
        max_values: int = 1,
        disabled: bool = False,
        row: Optional[int] = None
    ):
        """
        Base class for select menu components.
        
        Parameters:
        - custom_id: Custom ID for persistent components
        - placeholder: Placeholder text
        - min_values: Minimum selections required
        - max_values: Maximum selections allowed
        - disabled: Whether component is disabled
        - row: Action row (0-4)
        """

    custom_id: Optional[str]
    placeholder: Optional[str]
    min_values: int
    max_values: int
    disabled: bool
    values: List[str]

    async def callback(self, interaction: MessageInteraction) -> None:
        """
        Selection callback. Override this method.
        
        Parameters:
        - interaction: Selection interaction
        """

class StringSelect(BaseSelect):
    def __init__(
        self,
        *,
        options: List[SelectOption],
        custom_id: Optional[str] = None,
        placeholder: Optional[str] = None,
        min_values: int = 1,
        max_values: int = 1,
        disabled: bool = False,
        row: Optional[int] = None
    ):
        """
        String selection menu with predefined options.
        
        Parameters:
        - options: List of SelectOption objects
        - custom_id: Custom ID for persistent components
        - placeholder: Placeholder text
        - min_values: Minimum selections required
        - max_values: Maximum selections allowed
        - disabled: Whether component is disabled
        - row: Action row (0-4)
        """

    options: List[SelectOption]

class UserSelect(BaseSelect):
    def __init__(
        self,
        *,
        custom_id: Optional[str] = None,
        placeholder: Optional[str] = None,
        min_values: int = 1,
        max_values: int = 1,
        disabled: bool = False,
        row: Optional[int] = None,
        default_values: Optional[List[SelectDefaultValue]] = None
    ):
        """
        User selection menu.
        
        Parameters:
        - custom_id: Custom ID for persistent components
        - placeholder: Placeholder text
        - min_values: Minimum selections required
        - max_values: Maximum selections allowed
        - disabled: Whether component is disabled
        - row: Action row (0-4)
        - default_values: Default selected users
        """

    default_values: Optional[List[SelectDefaultValue]]

class RoleSelect(BaseSelect):
    def __init__(
        self,
        *,
        custom_id: Optional[str] = None,
        placeholder: Optional[str] = None,
        min_values: int = 1,
        max_values: int = 1,
        disabled: bool = False,
        row: Optional[int] = None,
        default_values: Optional[List[SelectDefaultValue]] = None
    ):
        """
        Role selection menu.
        
        Parameters:
        - custom_id: Custom ID for persistent components
        - placeholder: Placeholder text
        - min_values: Minimum selections required
        - max_values: Maximum selections allowed
        - disabled: Whether component is disabled
        - row: Action row (0-4)
        - default_values: Default selected roles
        """

    default_values: Optional[List[SelectDefaultValue]]

class ChannelSelect(BaseSelect):
    def __init__(
        self,
        *,
        channel_types: Optional[List[ChannelType]] = None,
        custom_id: Optional[str] = None,
        placeholder: Optional[str] = None,
        min_values: int = 1,
        max_values: int = 1,
        disabled: bool = False,
        row: Optional[int] = None,
        default_values: Optional[List[SelectDefaultValue]] = None
    ):
        """
        Channel selection menu.
        
        Parameters:
        - channel_types: Allowed channel types
        - custom_id: Custom ID for persistent components
        - placeholder: Placeholder text
        - min_values: Minimum selections required
        - max_values: Maximum selections allowed
        - disabled: Whether component is disabled
        - row: Action row (0-4)
        - default_values: Default selected channels
        """

    channel_types: Optional[List[ChannelType]]
    default_values: Optional[List[SelectDefaultValue]]

class MentionableSelect(BaseSelect):
    def __init__(
        self,
        *,
        custom_id: Optional[str] = None,
        placeholder: Optional[str] = None,
        min_values: int = 1,
        max_values: int = 1,
        disabled: bool = False,
        row: Optional[int] = None,
        default_values: Optional[List[SelectDefaultValue]] = None
    ):
        """
        Mentionable entity (users and roles) selection menu.
        
        Parameters:
        - custom_id: Custom ID for persistent components
        - placeholder: Placeholder text
        - min_values: Minimum selections required
        - max_values: Maximum selections allowed
        - disabled: Whether component is disabled
        - row: Action row (0-4)
        - default_values: Default selected entities
        """

    default_values: Optional[List[SelectDefaultValue]]

class SelectOption:
    def __init__(
        self,
        *,
        label: str,
        value: str,
        description: Optional[str] = None,
        emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
        default: bool = False
    ):
        """
        Option for string select menus.
        
        Parameters:
        - label: Option display label
        - value: Option value
        - description: Option description
        - emoji: Option emoji
        - default: Whether option is selected by default
        """

    label: str
    value: str
    description: Optional[str]
    emoji: Optional[Union[str, Emoji, PartialEmoji]]
    default: bool

class SelectDefaultValue:
    def __init__(self, id: int, type: SelectDefaultValueType):
        """
        Default value for entity select menus.
        
        Parameters:
        - id: Entity ID
        - type: Entity type (user, role, channel)
        """

    id: int
    type: SelectDefaultValueType

Modals

Dialog forms for collecting structured user input with multiple text fields.

class Modal:
    def __init__(
        self,
        *,
        title: str,
        custom_id: Optional[str] = None,
        timeout: Optional[float] = None
    ):
        """
        Create a modal dialog form.
        
        Parameters:
        - title: Modal title
        - custom_id: Custom ID for persistent modals
        - timeout: Modal timeout in seconds
        """

    title: str
    custom_id: Optional[str]
    timeout: Optional[float]
    children: List[TextInput]

    def add_item(self, item: TextInput) -> Self:
        """
        Add a text input to this modal.
        
        Parameters:
        - item: TextInput to add
        
        Returns:
        Self for method chaining
        """

    def remove_item(self, item: TextInput) -> Self:
        """
        Remove a text input from this modal.
        
        Parameters:
        - item: TextInput to remove
        
        Returns:
        Self for method chaining
        """

    async def on_submit(self, interaction: ModalInteraction) -> None:
        """
        Called when modal is submitted. Override this method.
        
        Parameters:
        - interaction: Modal submission interaction
        """

    async def on_error(self, interaction: ModalInteraction, error: Exception) -> None:
        """
        Called when an error occurs during modal submission.
        
        Parameters:
        - interaction: Modal interaction
        - error: Exception that occurred
        """

    async def on_timeout(self) -> None:
        """Called when the modal times out."""

    def stop(self) -> None:
        """Stop the modal and prevent further interactions."""

class TextInput(Item):
    def __init__(
        self,
        *,
        label: str,
        custom_id: Optional[str] = None,
        style: TextInputStyle = TextInputStyle.short,
        placeholder: Optional[str] = None,
        value: Optional[str] = None,
        required: bool = True,
        min_length: Optional[int] = None,
        max_length: Optional[int] = None,
        row: Optional[int] = None
    ):
        """
        Text input field for modals.
        
        Parameters:
        - label: Input field label
        - custom_id: Custom ID for the input
        - style: Input style (short or paragraph)
        - placeholder: Placeholder text
        - value: Pre-filled value
        - required: Whether input is required
        - min_length: Minimum text length
        - max_length: Maximum text length
        - row: Action row (0-4)
        """

    label: str
    custom_id: Optional[str]
    style: TextInputStyle
    placeholder: Optional[str]
    value: Optional[str]
    required: bool
    min_length: Optional[int]
    max_length: Optional[int]

def text_input(
    *,
    label: str,
    custom_id: Optional[str] = None,
    style: TextInputStyle = TextInputStyle.short,
    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
):
    """
    Decorator for text input fields in modals.
    
    Parameters:
    - label: Input field label
    - custom_id: Custom ID for the input
    - style: Input style (short or paragraph)
    - placeholder: Placeholder text
    - default: Pre-filled value
    - required: Whether input is required
    - min_length: Minimum text length
    - max_length: Maximum text length
    - row: Action row (0-4)
    
    Returns:
    Decorated attribute as a TextInput component
    """

Message Interactions

Interaction objects for UI component interactions like button clicks and select menu selections.

class MessageInteraction:
    def __init__(self): ...

    id: int
    type: InteractionType
    data: MessageInteractionData
    guild: Optional[Guild]
    guild_id: Optional[int]
    channel: Optional[Union[GuildChannel, PartialMessageable]]
    channel_id: Optional[int]
    user: Union[User, Member]
    token: str
    version: int
    message: Message
    locale: Optional[Locale]
    guild_locale: Optional[Locale]
    created_at: datetime
    expires_at: datetime

    @property
    def response(self) -> InteractionResponse:
        """Interaction response handler."""

    @property
    def followup(self) -> Webhook:
        """Webhook for followup messages."""

    @property
    def component(self) -> Component:
        """The component that triggered this interaction."""

class ModalInteraction:
    def __init__(self): ...

    id: int
    type: InteractionType
    data: ModalInteractionData
    guild: Optional[Guild]
    guild_id: Optional[int]
    channel: Optional[Union[GuildChannel, PartialMessageable]]
    channel_id: Optional[int]
    user: Union[User, Member]
    token: str
    version: int
    message: Optional[Message]
    locale: Optional[Locale]
    guild_locale: Optional[Locale]
    created_at: datetime
    expires_at: datetime

    @property
    def response(self) -> InteractionResponse:
        """Interaction response handler."""

    @property
    def followup(self) -> Webhook:
        """Webhook for followup messages."""

    @property
    def text_values(self) -> Dict[str, str]:
        """Dictionary mapping text input custom_ids to their values."""

Usage Examples

Basic Button View

import disnake

class BasicView(disnake.ui.View):
    def __init__(self):
        super().__init__(timeout=60)
        self.value = None

    @disnake.ui.button(label="Yes", style=disnake.ButtonStyle.green)
    async def yes_button(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
        self.value = True
        self.stop()
        await interaction.response.send_message("You clicked Yes!", ephemeral=True)

    @disnake.ui.button(label="No", style=disnake.ButtonStyle.red)
    async def no_button(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
        self.value = False
        self.stop()
        await interaction.response.send_message("You clicked No!", ephemeral=True)

# Usage
@bot.slash_command(description="Ask a yes/no question")
async def question(inter: disnake.ApplicationCommandInteraction):
    view = BasicView()
    await inter.response.send_message("Do you like pizza?", view=view)
    
    await view.wait()
    if view.value is None:
        await inter.followup.send("You didn't respond in time!")
    elif view.value:
        await inter.followup.send("Great choice!")
    else:
        await inter.followup.send("That's okay!")

Select Menu Example

class GameSelect(disnake.ui.View):
    @disnake.ui.string_select(
        placeholder="Choose your favorite game...",
        min_values=1,
        max_values=3,
        options=[
            disnake.SelectOption(
                label="Minecraft",
                value="minecraft",
                description="Block building game",
                emoji="🟫"
            ),
            disnake.SelectOption(
                label="Among Us",
                value="among_us",
                description="Social deduction game",
                emoji="🔴"
            ),
            disnake.SelectOption(
                label="Discord",
                value="discord",
                description="Chat application",
                emoji="💬"
            ),
        ]
    )
    async def select_callback(self, select: disnake.ui.StringSelect, interaction: disnake.MessageInteraction):
        games = ", ".join(select.values)
        await interaction.response.send_message(f"You selected: {games}")

@bot.slash_command(description="Choose your favorite games")
async def games(inter: disnake.ApplicationCommandInteraction):
    view = GameSelect()
    await inter.response.send_message("Select your favorites:", view=view)

User/Role Select Menus

class ModeratorPanel(disnake.ui.View):
    @disnake.ui.user_select(placeholder="Select users to moderate...")
    async def select_users(self, select: disnake.ui.UserSelect, interaction: disnake.MessageInteraction):
        users = [f"{user.mention}" for user in select.values]
        await interaction.response.send_message(
            f"Selected users: {', '.join(users)}",
            ephemeral=True
        )

    @disnake.ui.role_select(placeholder="Select roles to manage...")
    async def select_roles(self, select: disnake.ui.RoleSelect, interaction: disnake.MessageInteraction):
        roles = [f"{role.mention}" for role in select.values]
        await interaction.response.send_message(
            f"Selected roles: {', '.join(roles)}",
            ephemeral=True
        )

@bot.slash_command(description="Moderator panel")
async def mod_panel(inter: disnake.ApplicationCommandInteraction):
    view = ModeratorPanel()
    await inter.response.send_message("Moderator Controls:", view=view)

Modal Forms

class FeedbackModal(disnake.ui.Modal):
    def __init__(self):
        super().__init__(title="Submit Feedback", timeout=300)

    name = disnake.ui.TextInput(
        label="Your Name",
        placeholder="Enter your name here...",
        required=True,
        max_length=100
    )

    feedback = disnake.ui.TextInput(
        label="Feedback",
        style=disnake.TextInputStyle.paragraph,
        placeholder="Tell us what you think...",
        required=True,
        min_length=10,
        max_length=1000
    )

    async def on_submit(self, interaction: disnake.ModalInteraction):
        embed = disnake.Embed(
            title="New Feedback",
            color=disnake.Color.blue()
        )
        embed.add_field(name="From", value=self.name.value, inline=True)
        embed.add_field(name="User", value=interaction.author.mention, inline=True)
        embed.add_field(name="Feedback", value=self.feedback.value, inline=False)
        
        # Send to feedback channel
        feedback_channel = bot.get_channel(FEEDBACK_CHANNEL_ID)
        await feedback_channel.send(embed=embed)
        
        await interaction.response.send_message(
            "Thank you for your feedback!",
            ephemeral=True
        )

@bot.slash_command(description="Submit feedback")
async def feedback(inter: disnake.ApplicationCommandInteraction):
    modal = FeedbackModal()
    await inter.response.send_modal(modal)

Persistent Views

class PersistentView(disnake.ui.View):
    def __init__(self):
        super().__init__(timeout=None)  # No timeout for persistent views

    @disnake.ui.button(
        label="Get Support",
        style=disnake.ButtonStyle.primary,
        custom_id="support_button"  # Custom ID for persistence
    )
    async def support_button(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
        # This button will work even after bot restarts
        await interaction.response.send_message(
            "Support ticket created! A moderator will be with you shortly.",
            ephemeral=True
        )

# Add persistent view on bot startup
@bot.event
async def on_ready():
    bot.add_view(PersistentView())
    print(f'{bot.user} is ready!')

@bot.slash_command(description="Send support panel")
async def support_panel(inter: disnake.ApplicationCommandInteraction):
    embed = disnake.Embed(
        title="Support Center",
        description="Click the button below to get help."
    )
    view = PersistentView()
    await inter.response.send_message(embed=embed, view=view)

Complex Interactive Workflow

class ConfigurationWizard(disnake.ui.View):
    def __init__(self):
        super().__init__(timeout=300)
        self.config = {}

    @disnake.ui.button(label="Step 1: Choose Channel", style=disnake.ButtonStyle.primary)
    async def step1(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
        view = ChannelSelectView(self)
        await interaction.response.edit_message(
            content="Step 1: Select a channel for notifications:",
            view=view
        )

    @disnake.ui.button(label="Step 2: Set Permissions", style=disnake.ButtonStyle.secondary, disabled=True)
    async def step2(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
        view = RoleSelectView(self)
        await interaction.response.edit_message(
            content="Step 2: Select roles that can use this feature:",
            view=view
        )

    @disnake.ui.button(label="Finish", style=disnake.ButtonStyle.green, disabled=True)
    async def finish(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction):
        embed = disnake.Embed(title="Configuration Complete!")
        embed.add_field(name="Channel", value=self.config.get('channel', 'None'))
        embed.add_field(name="Roles", value=', '.join(self.config.get('roles', [])))
        
        await interaction.response.edit_message(
            content="Configuration saved!",
            embed=embed,
            view=None
        )

class ChannelSelectView(disnake.ui.View):
    def __init__(self, parent):
        super().__init__(timeout=300)
        self.parent = parent

    @disnake.ui.channel_select(
        placeholder="Select notification channel...",
        channel_types=[disnake.ChannelType.text]
    )
    async def select_channel(self, select: disnake.ui.ChannelSelect, interaction: disnake.MessageInteraction):
        self.parent.config['channel'] = select.values[0].mention
        
        # Enable step 2
        self.parent.children[1].disabled = False
        
        await interaction.response.edit_message(
            content="✅ Channel selected! Now proceed to step 2:",
            view=self.parent
        )

class RoleSelectView(disnake.ui.View):
    def __init__(self, parent):
        super().__init__(timeout=300)
        self.parent = parent

    @disnake.ui.role_select(
        placeholder="Select authorized roles...",
        min_values=1,
        max_values=5
    )
    async def select_roles(self, select: disnake.ui.RoleSelect, interaction: disnake.MessageInteraction):
        self.parent.config['roles'] = [role.mention for role in select.values]
        
        # Enable finish button
        self.parent.children[2].disabled = False
        
        await interaction.response.edit_message(
            content="✅ Roles selected! Click Finish to complete setup:",
            view=self.parent
        )

@bot.slash_command(description="Configure bot settings")
async def configure(inter: disnake.ApplicationCommandInteraction):
    view = ConfigurationWizard()
    await inter.response.send_message("Welcome to the configuration wizard!", view=view)

Install with Tessl CLI

npx tessl i tessl/pypi-disnake

docs

application-commands.md

automod.md

channels-messaging.md

client-bot.md

command-framework.md

error-handling.md

events-gateway.md

guild-management.md

index.md

interactions-ui.md

localization-i18n.md

permissions-security.md

polls.md

users-members.md

voice-audio.md

tile.json