Add documentation for discord.ui components and usage examples

This commit is contained in:
Slipstream 2025-06-05 22:20:45 -06:00
parent 33c545de7b
commit 57a4e3fdb9
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD

305
docs/components.md Normal file
View File

@ -0,0 +1,305 @@
# `discord.ui` \-- User Interface Components
Discord\'s UI kit provides ways to build interactive interfaces using
components like buttons, select menus, and modals. The
`discord.ui`{.interpreted-text role="mod"} module in this library offers
high level abstractions to create these interfaces easily.
This page presents a basic overview of how to build views, lay out
components, and respond to user interaction.
## Creating a View
A `~discord.ui.View`{.interpreted-text role="class"} groups components
together and manages the interaction lifecycle. Views can be persistent
or temporary and support custom timeouts.
``` python3
from discord.ext import commands
import discord
bot = commands.Bot(command_prefix="!")
class Confirm(discord.ui.View):
def __init__(self):
super().__init__(timeout=30)
self.value = None
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.green)
async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button):
self.value = True
self.stop()
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.red)
async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button):
self.value = False
self.stop()
@bot.command()
async def ask(ctx: commands.Context):
view = Confirm()
await ctx.send("Do you wish to continue?", view=view)
await view.wait()
if view.value is None:
await ctx.send("Timed out...")
elif view.value:
await ctx.send("Confirmed!")
else:
await ctx.send("Cancelled.")
```
## Handling Select Menus
Select menus allow members to choose from a list of options. They are
represented by `~discord.ui.Select`{.interpreted-text role="class"} and
can be defined inside a view with the
`~discord.ui.select`{.interpreted-text role="func"} decorator.
``` python3
class FruitView(discord.ui.View):
@discord.ui.select(
placeholder="Choose your favourite fruit",
min_values=1,
max_values=1,
options=[
discord.SelectOption(label="Apple", value="apple"),
discord.SelectOption(label="Banana", value="banana"),
discord.SelectOption(label="Orange", value="orange"),
],
)
async def select_callback(self, interaction: discord.Interaction, select: discord.ui.Select):
await interaction.response.send_message(f"You chose {select.values[0]}!")
```
## Buttons and Modals
Buttons are created with `~discord.ui.Button`{.interpreted-text
role="class"} and can be declared using the
`~discord.ui.button`{.interpreted-text role="func"} decorator. For more
complex input, `~discord.ui.Modal`{.interpreted-text role="class"}
provides a dialog style interface with text inputs.
``` python3
class Feedback(discord.ui.Modal, title="Feedback"):
name = discord.ui.TextInput(label="Name")
message = discord.ui.TextInput(label="Feedback", style=discord.TextStyle.paragraph)
async def on_submit(self, interaction: discord.Interaction):
await interaction.response.send_message("Thanks for your feedback!", ephemeral=True)
class FeedbackView(discord.ui.View):
@discord.ui.button(label="Give Feedback", style=discord.ButtonStyle.primary)
async def feedback(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.send_modal(Feedback())
```
## Combining Everything
Views can contain multiple components arranged in rows. Each view keeps
track of its children and automatically disables components when timed
out.
More information about the available classes and their APIs can be found
in the `interactions/api`{.interpreted-text role="doc"} reference under
`discord_ui_kit`{.interpreted-text role="ref"}.
## Persistent Views
Sometimes a view should remain active even after the bot restarts. To
achieve this, register the view with
`discord.Client.add_view`{.interpreted-text role="meth"} and ensure all
components provide a `custom_id`. Persistent views cannot have a timeout
set.
``` python3
class Poll(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
@discord.ui.button(label="Vote", style=discord.ButtonStyle.primary,
custom_id="poll:vote")
async def vote(self, interaction: discord.Interaction,
button: discord.ui.Button):
await interaction.response.send_message(
"Thanks for voting!", ephemeral=True
)
bot.add_view(Poll())
```
## Timeouts and Disabling Components
When a view times out you can disable the components to prevent further
interaction. Override `~discord.ui.View.on_timeout`{.interpreted-text
role="meth"} and edit the message that contains the view to update it.
``` python3
class AutoDisable(discord.ui.View):
async def on_timeout(self) -> None:
for item in self.children:
item.disabled = True
await self.message.edit(view=self)
```
## Component Layout and Row Management
Each message can contain up to five rows of components with five
components in each row. By default, items are appended in the order they
are defined but you can explicitly control the layout using the `row`
keyword argument or by manually adding items with
`~discord.ui.View.add_item`{.interpreted-text role="meth"}.
``` python3
class RowExample(discord.ui.View):
def __init__(self):
super().__init__()
self.add_item(discord.ui.Button(label="Top", row=0))
self.add_item(discord.ui.Button(label="Bottom", row=4))
@discord.ui.button(label="Middle", row=2)
async def middle(self, interaction: discord.Interaction, _: discord.ui.Button):
await interaction.response.send_message("Middle row button")
```
## Handling Interaction Responses
Interactions need a response within three seconds. If your callback
requires long processing you should defer the response to buy yourself
more time.
``` python3
class DeferExample(discord.ui.View):
@discord.ui.button(label="Process", style=discord.ButtonStyle.blurple)
async def process(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.defer(thinking=True)
await lengthy_operation()
await interaction.followup.send("Done!", ephemeral=True)
```
## Pre-defined Select Menus
In addition to `~discord.ui.Select`{.interpreted-text role="class"},
there are convenience subclasses for common types of selections such as
channels, roles, users, and mentionables. These can be constructed
manually or by passing the `cls` keyword parameter to the
`~discord.ui.select`{.interpreted-text role="func"} decorator.
``` python3
class SelectView(discord.ui.View):
@discord.ui.select(cls=discord.ui.ChannelSelect, channel_types=[discord.ChannelType.voice])
async def channel(self, interaction: discord.Interaction, select: discord.ui.ChannelSelect):
await interaction.response.send_message(f"Chosen channel: {select.values[0]}")
```
## Pagination Example
The following example demonstrates how the building blocks come together
to create a paginated message. Clicking the buttons will edit the
message in-place to show the next or previous page.
``` python3
class Paginator(discord.ui.View):
def __init__(self, pages: list[str]):
super().__init__(timeout=60)
self.pages = pages
self.index = 0
async def update_message(self, interaction: discord.Interaction):
content = self.pages[self.index]
await interaction.response.edit_message(content=content, view=self)
@discord.ui.button(label="Prev", style=discord.ButtonStyle.secondary)
async def previous(self, interaction: discord.Interaction, _button: discord.ui.Button):
self.index = (self.index - 1) % len(self.pages)
await self.update_message(interaction)
@discord.ui.button(label="Next", style=discord.ButtonStyle.secondary)
async def next(self, interaction: discord.Interaction, _button: discord.ui.Button):
self.index = (self.index + 1) % len(self.pages)
await self.update_message(interaction)
@bot.command()
async def paginate(ctx: commands.Context):
view = Paginator(["Page 1", "Page 2", "Page 3"])
await ctx.send("Page 1", view=view)
```
## Advanced Components
The UI kit also provides a number of layout components that only work
inside of a `~discord.ui.LayoutView`{.interpreted-text role="class"}.
These components allow building complex message layouts with embedded
media and rich formatting.
### Action Rows and Layout Views
An `~discord.ui.ActionRow`{.interpreted-text role="class"} groups
interactive items within a layout. Use it inside a
`~discord.ui.LayoutView`{.interpreted-text role="class"} to position
buttons or selects.
``` python3
class ActionView(discord.ui.LayoutView):
row = discord.ui.ActionRow()
@row.button(label="Click Me")
async def click_me(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.send_message("You clicked the row button!")
```
### Containers
Containers group other layout components together and can be styled with
an accent colour.
``` python3
class ContainerView(discord.ui.LayoutView):
container = discord.ui.Container(
discord.ui.TextDisplay("Hello from a container!"),
discord.ui.ActionRow(discord.ui.Button(label="OK")),
)
```
### Files and Media Galleries
Use `~discord.ui.File`{.interpreted-text role="class"} to reference
attachments uploaded alongside the view.
`~discord.ui.MediaGallery`{.interpreted-text role="class"} displays a
collection of images or videos.
``` python3
class GalleryView(discord.ui.LayoutView):
file = discord.ui.File("attachment://local.png")
def __init__(self):
super().__init__()
self.gallery = discord.ui.MediaGallery()
self.gallery.add_item("https://example.com/image.png", description="Example")
```
Sections, Text Displays and Thumbnails
Sections organise text with an optional accessory like a thumbnail.
Additional static text can be shown with
`~discord.ui.TextDisplay`{.interpreted-text role="class"} while
`~discord.ui.Separator`{.interpreted-text role="class"} provides spacing
between elements.
``` python3
class InfoView(discord.ui.LayoutView):
section = discord.ui.Section(
"Important information",
accessory=discord.ui.Thumbnail("https://example.com/thumb.png"),
)
separator = discord.ui.Separator()
extra = discord.ui.TextDisplay("More details here")
```
## See Also
More examples can be found in the
`examples <examples>`{.interpreted-text role="resource"} directory.
Refer to the `interactions/api`{.interpreted-text role="doc"} page for
an exhaustive API reference.