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.
Buttons
Section titled “Buttons”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).
Prefix matching
Section titled “Prefix matching”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 ...Deferred buttons
Section titled “Deferred buttons”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
Section titled “Components v2”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)Reference
Section titled “Reference”| 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) |
Referencing uploaded files
Section titled “Referencing uploaded files”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.
