Skip to content

Components

cordless has full support for Discord’s Components v2, plus the classic Button/ActionRow message components. For select menus and modals, see Modals & Select Menus.

from cordless import ActionRow, Button, ButtonStyle
@bot.command("greet", description="Say hello")
async def greet(ctx):
await ctx.send(
"Hello!",
components=[ActionRow([Button("Wave back", custom_id="wave", style=ButtonStyle.PRIMARY)])],
)
@bot.button("wave")
async def wave(ctx):
await ctx.edit(content="👋")

ButtonStyle has PRIMARY, SECONDARY, SUCCESS, DANGER, LINK (needs url= instead of custom_id=), and PREMIUM (needs sku_id= instead, no label/custom_id/url).

A button with custom_id="action:123" matches a handler registered as "action", and the suffix segments land on ctx.custom_id_args (here ["123"]). This works for buttons, selects, and modals, handy for embedding state like item ids or page numbers directly in the component.

@bot.button("shop")
async def shop(ctx):
item_id, page = ctx.custom_id_args
...

For buttons that take time to process, set defer=True. The message shows a loading state while the worker runs. See Deferred Interactions.

@bot.button("slow_button", defer=True)
async def slow_button(ctx):
result = await do_work()
await ctx.edit(components=[...])

Components v2 replaces plain message content with a tree of layout blocks: containers, sections, text, images, dividers. cordless auto-detects when a message uses any of them and sets Discord’s required flag for you; you never need to think about it.

from cordless import Container, Section, TextDisplay, Thumbnail, Separator, ActionRow, Button, ButtonStyle
components = [
Container([
Section(
TextDisplay("**Welcome to the server!**"),
TextDisplay("Read the rules below before posting."),
accessory=Thumbnail("https://example.com/icon.png"),
),
Separator(),
ActionRow([Button("I agree", custom_id="agree", style=ButtonStyle.SUCCESS)]),
])
]
await ctx.send(components=components)
Component Constructor Notes
Container Container(components, accent_color=None, spoiler=False) Groups components under a colored accent bar down the left edge
Section Section(*components, accessory=None) Up to 3 text blocks with a small accessory (Thumbnail or Button) beside them
TextDisplay TextDisplay(content) A block of markdown text
Thumbnail Thumbnail(url, description=None, spoiler=False) Small image, typically used as a Section accessory
MediaGallery MediaGallery(*items) Grid of images/videos; each item is a dict, e.g. {"url": "attachment://photo.png"}
File File(url, spoiler=False) Attaches a file inline; url must be an attachment://filename reference (see below)
Separator Separator(divider=True, spacing=1) Visual divider; spacing is 1 (small) or 2 (large)

Thumbnail and MediaGallery items accept any external image URL, same as an embed. File, specifically, only accepts an attachment://filename reference: it exists to display a file you’re uploading with the message itself, via files=:

from cordless import Container, File, MediaGallery
@bot.command("report", description="Send today's report")
async def report(ctx):
with open("report.pdf", "rb") as f:
pdf_bytes = f.read()
await ctx.send(
components=[Container([File("attachment://report.pdf")])],
files=[("report.pdf", pdf_bytes)],
)

files is a list of (filename, bytes) tuples and works on both ctx.send() and ctx.edit(), whether the handler is deferred or not.