> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Response formatting

> How ::: blocks in a reply become cards, buttons, carousels, files, and reactions, which blocks the model already knows, and where each one renders

A formatting component is a block in the agent's reply text that starts with `::: <name>` and ends with `:::`. The model writes the block as plain text; each [channel](/concepts/channels) then turns it into what that channel can show: cards in the web widget, an interactive list on WhatsApp, an Adaptive Card in Teams, an attachment in email. Components exist so one agent can answer every channel from one persona and one set of skills without your code knowing where a reply is displayed.

## From reply text to rendered message

The reply is ordinary Markdown with blocks embedded in it:

```text theme={null}
Here are two rooms for those dates.

::: list-item
![image](https://cdn.example.com/deluxe.jpg)
# Deluxe Ocean View
## $250 per night · 3 left
King bed, private balcony, breakfast included.
:::

::: actions
- Book the Deluxe Ocean View
- Show me cheaper rooms
:::
```

Before delivery, a messaging channel extracts every block it knows, sends each in its native form, and sends the remaining text as the message body. The same extraction runs for replies in a conversation and for messages you send with [`Channels.send`](/build/send-proactive-messages) to WhatsApp, Messenger, Instagram, and MessageBird. The web widget parses blocks in the browser and renders them while the reply streams. A channel that can't render a block drops it or sends it as raw text; the matrix under Rendering by channel says which.

## Taught blocks and instructed blocks

On every text channel the platform prompt teaches the model the syntax for `list-item`, `horizontal-list-item`, `actions`, `links`, `images`, and `payment`; on every channel except `web` it also teaches `flow` (WhatsApp only) and `reaction`. The model decides when to use them, so for predictable output say when and how in the [persona](/concepts/persona) or a [skill's context](/build/write-skill-context). Two blocks are never taught: `documents` and `navigate` appear only when your instructions include their syntax.

Two cases change the rules. On the `web` channel (Lua Desktop and the admin dashboard's chat) the model is told to prefer plain Markdown, to use cards only for options the end user picks from, and never to emit `flow`; the web widget is not on that channel (it reports `pop`) and gets the same rulebook as the messaging channels. A client that declares the `plain-markdown` capability gets plain Markdown and no blocks at all: `lua chat` declares it, and the [HTTP chat API](/reference/rest/chat) accepts it as `clientCapabilities: ["plain-markdown"]`. Voice turns get no blocks either; the same data goes through tools that show it on screen.

## Rendered previews before release

Nothing shows a sandbox version with its blocks rendered. `lua chat` declares `plain-markdown` in both environments, so it prints Markdown and never a card, and `lua chat --agent-version` runs against production. Two clients render blocks, and both talk to the agent's production version: the web widget, and the chat panel in the admin dashboard's agent workspace, which is on the `web` channel and so gets the plain-Markdown-first rulebook; a block your instructions ask for still renders there. To see a reply on a real messaging app, [test on Lua's channels](/channels/quick-testing) over WhatsApp, Messenger, Instagram, or Slack, which also deliver production. The HTTP chat API returns blocks as raw markers for your own client to render.

## Rendering by channel

Three terms recur in the matrix. The messaging channels are WhatsApp, Messenger, Instagram, Slack, Teams, RCS, MessageBird, and iMessage; they share one extractor. Email has two forms: a generated inbox is the address Lua issues, and your own address is a mailbox you connected. A block a channel can't render is either dropped, meaning the block is removed and the surrounding text is still sent, or reaches the reader as raw text, meaning the `:::` lines are sent as written.

| Component                                                           | Native                                                                                               | Degraded                                                                                                                        | Dropped or raw text                                                                                                        |
| ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| [`list-item`](/channels/formatting/list-item)                       | Web widget, WhatsApp, Messenger, Instagram, Slack, Teams, RCS, MessageBird, email (your own address) | iMessage: text and image                                                                                                        | Dropped: email (generated inbox). Raw text: SMS, Front                                                                     |
| [`horizontal-list-item`](/channels/formatting/horizontal-list-item) | Web widget, WhatsApp, RCS                                                                            | Messenger, Instagram: as `list-item`; iMessage: text and image                                                                  | Dropped: Slack, Teams, MessageBird, email (generated inbox). Raw text: email (your own address), SMS, Front                |
| [`actions`](/channels/formatting/actions)                           | Web widget, WhatsApp, Messenger, Instagram, Slack, Teams, RCS, MessageBird                           | iMessage: numbered text                                                                                                         | Dropped: email (both forms). Raw text: SMS, Front                                                                          |
| [`links`](/channels/formatting/links)                               | Web widget, WhatsApp, Slack, Teams, RCS, iMessage, email (your own address)                          | —                                                                                                                               | Dropped: Messenger, Instagram, MessageBird, email (generated inbox). Raw text: SMS, Front                                  |
| [`images`](/channels/formatting/images)                             | Web widget, Messenger, Instagram, Teams, RCS, iMessage, email (both forms)                           | WhatsApp: one collage; Slack: first image; MessageBird: first three                                                             | Raw text: SMS, Front                                                                                                       |
| [`documents`](/channels/formatting/documents)                       | Web widget, WhatsApp, Teams, RCS, iMessage, email (generated inbox)                                  | MessageBird: label and URL as text                                                                                              | Dropped: Messenger, Instagram, Slack. Raw text: email (your own address), SMS, Front                                       |
| [`payment`](/channels/formatting/payment)                           | Web widget, WhatsApp, Messenger, Instagram, Slack, Teams, email (your own address)                   | iMessage, MessageBird: URL as text                                                                                              | Dropped: RCS, email (generated inbox). Raw text: SMS, Front                                                                |
| [`flow`](/channels/formatting/flow)                                 | WhatsApp                                                                                             | —                                                                                                                               | Dropped: the other messaging channels, email (generated inbox). Raw text: web widget, email (your own address), SMS, Front |
| [`navigate`](/channels/formatting/navigate)                         | Web widget with an `onNavigate` handler                                                              | —                                                                                                                               | Dropped: web widget without `onNavigate`. Raw text: every other channel                                                    |
| [`reaction`](/channels/formatting/reaction)                         | WhatsApp, Instagram, Messenger, Slack, iMessage, web widget                                          | Teams, RCS, MessageBird, SMS, Front, email (generated inbox): emoji as text; Messenger, Slack, iMessage: unmapped emoji as text | Dropped: email (your own address)                                                                                          |

SMS and Front have no extractor: a `reaction` block becomes its emoji, and every other block reaches the reader as raw text, so keep instructions for those channels to plain text. Your own address renders blocks into HTML; a generated inbox runs the messaging channels' extraction, sends `images` and `documents` as attachments, and drops the rest.

## Channel-scoped instructions

A block that works on one channel can leak on another, so scope the instruction: "On WhatsApp, send the verification flow …", or in a tool read `Lua.request.channel` and return the block only where it renders (see [`Lua`](/reference/sdk/lua)). When in doubt, `list-item`, `actions`, `images`, and `payment` travel furthest; every other block has a channel list to check first.

## Limits

| Limit                                                                  | Value                                                                        |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Items per `list-item` or `horizontal-list-item` reply                  | 10; WhatsApp sends lists in chunks of 9 rows                                 |
| A reply whose only block is one `list-item`, on the messaging channels | Sent as text with the title in bold, not as a card; send 2 or more for cards |
| Carousel on WhatsApp                                                   | 2 to 10 cards; card text capped at 135 characters                            |
| Entries per `actions` block                                            | 10; WhatsApp shows 1 or 2 as buttons, more as a list                         |
| `links`, `documents`, and `payment` blocks on messaging channels       | Only the first block of each kind in a reply is rendered                     |
| Reactions per reply                                                    | 1                                                                            |

## Next steps

<Columns cols={2}>
  <Card title="list-item" href="/channels/formatting/list-item">The component most replies start with.</Card>
  <Card title="Write skill context" href="/build/write-skill-context">Where component instructions belong.</Card>
  <Card title="Send proactive messages" href="/build/send-proactive-messages">Blocks in messages the agent starts.</Card>
  <Card title="Channels" href="/concepts/channels">Every channel and its inbound and outbound names.</Card>
</Columns>
