Aero developer docs
Components: embeds, buttons and menus
Messages your Aero sends can carry rich cards, buttons and dropdown menus. You describe the shape; Ballastra validates and renders it.
The message shape
A message an Aero sends can carry an embed (a rich card), components (rows of buttons or a dropdown), and a layout (stacked blocks). A button press or menu choice comes back to your Aero as a component interaction. Every field, limit and style is checked by the same validators the API uses, so a shape that validates is accepted by i.reply(...).
{
"text": "Choose an action:",
"components": [{ "components": [
{ "type": "button", "style": "primary", "label": "Confirm", "custom_id": "confirm" },
{ "type": "button", "style": "danger", "label": "Cancel", "custom_id": "cancel" },
{ "type": "button", "style": "link", "label": "Learn more", "url": "https://www.ballastra.app/aero/docs" }
] }]
}{
"embeds": [{ "title": "Pick a role", "color": "#A78BFA" }],
"components": [{ "components": [{
"type": "select", "kind": "string", "custom_id": "role", "placeholder": "Select a role",
"options": [
{ "label": "Developer", "value": "dev", "description": "Ships code" },
{ "label": "Designer", "value": "design", "description": "Ships pixels" },
{ "label": "Community", "value": "community" }
]
}] }]
}{
"embeds": [{
"title": "Release 2.0 is live",
"description": "A rich card can hold a title, description, fields, an image, and an accent color.",
"color": "#22C55E",
"fields": [
{ "name": "Version", "value": "2.0.0", "inline": true },
{ "name": "Status", "value": "Stable", "inline": true }
],
"footer": { "text": "Posted by your Aero" }
}]
}If a shape breaks a rule — too many buttons, a missing label, a field over its limit — the API rejects it with a 400 and an error explaining what is wrong, so nothing malformed ever reaches members.
Replying to a component interaction
i.reply(...)posts a normal public message.i.replyEphemeral(...)replies privately; only the person who pressed sees it.i.update(...)edits the message the button is on, in place.i.deferReply()theni.editReply(id, ...)shows a placeholder, then the real answer.