> ## 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.

# Block syntax

> The rules every Lua app and channel uses to find ::: blocks in a reply: openers, closers, the one-line form, unclosed blocks, code, and what happens to text around a block

Every surface that shows an agent's reply reads its `:::` blocks with the same rules: the web widget, Lua Desktop, the mobile app, Frontline, the dashboard chat, the messaging channels, email, and voice. A reply that renders as cards in one place is read as the same blocks everywhere else; what each channel then does with a block is in the [channel matrix](/channels/formatting/overview#rendering-by-channel). A message you send from code with [`Channels.send`](/build/send-proactive-messages) to SMS, Front, or a generated inbox is not read for blocks: it arrives as written. The canonical form is the one the platform teaches the model:

```text theme={null}
::: actions
- Book the Deluxe Ocean View
- Show me cheaper rooms
:::
```

The rules below say which other spellings count, so a block the model writes slightly differently still renders instead of reaching the end user as raw text.

## Openers

A block opens at the start of a line with `:::` followed by the block name. These all open the same `actions` block:

| Written | Reads as |
| - | - |
| `::: actions` | The canonical form |
| `:::actions` | No space after the colons |
| `:::   actions` or a tab | Extra spaces or a tab |
| `::: Actions` | Names are not case-sensitive |
| `  ::: actions` | An indented opener |
| `Sure!::: actions` | An opener glued to the end of a line of text: the text stays, the block opens |

Text after the name on the opener line is the block's first body line, so `::: actions - Yes` followed by `- No` on the next line gives two entries.

An opener glued to text counts only when it is a known block name at the end of the line. `:::` anywhere else in a line is text, never a marker: `ratio 3:::2`, an ARN such as `arn:aws:s3:::bucket`, and `` `a:::b` `` in inline code all show as written.

## Closers

A block closes at a line that holds only colons:

| Written | Reads as |
| - | - |
| `:::` | The canonical closer |
| `  :::` or `:::` with trailing spaces | Indented, or followed by spaces |
| `::` or `::::` | Any line of two or more colons |
| `- No:::` | A closer glued to the end of the last body line |
| `::: Thanks for waiting!` | A closer followed by text that isn't a block name: the block closes and the text shows after it |

A closer with no block open, such as a stray `:::` line, is removed.

## One-line blocks

A whole block can sit on one line. It ends at the first run of two or more colons after a space, and the rest of the line is read again as text that can hold more blocks:

```text theme={null}
::: reaction emoji=👍 :::
```

Text before a one-line block stays on its line, and text after it continues the same line.

## Missing closers

The model sometimes forgets a closer. Two rules keep the reply readable:

* **A new opener closes the open block.** In `::: images` … `::: actions` … `:::`, the images block ends where `::: actions` starts.
* **A block still open when the reply ends renders** as if it had been closed. For `actions`, `images`, `links`, `payment`, `documents`, `reaction`, and `navigate`, the block keeps only its own lines, such as entries, images, or links, and any prose after them shows as normal text. A `list-item` or `horizontal-list-item` card keeps its free text as the description.

While a reply streams, a block appears once its closer arrives, and a half-written marker line is held back, so the end user never sees `::: act` flash on screen.

## Code

Nothing inside code is a block. A block written inside a fenced code block (` ``` ` or `~~~`) or inside inline code shows as code, so you can quote the syntax in a reply:

````text theme={null}
To show buttons, write:

```
::: actions
- Yes
:::
```
````

A line that opens and closes its own triple backticks, such as ` ```::: actions``` `, is inline code, not a fence.

## Text around and inside blocks

* **Line breaks:** a literal `\n` (a backslash followed by `n`) in the reply becomes a line break, except inside inline code or a code block, where it stays as written.
* **Several blocks of one kind** are all read. The apps show each one where it sits; the messaging channels and email merge them, so two `actions` blocks become one set of choices with repeats removed. See each block's page for the channels that send only the first payment link.
* **Prose inside a block** whose body is a list, such as `Pick one:` inside `::: actions`, shows as text just before the block.
* **Unknown names:** the marker lines of a block the surface doesn't know, such as `::: summary`, are removed and its body shows as normal text.
* **Each part of a message is read on its own.** A reply made of several text parts never has a block that starts in one part and ends in the next.

## Body rules

Each block's page lists its fields. A few rules apply across them:

| Rule | Detail |
| - | - |
| Card headings | `# Title` and `#Title` are the same, as are `## Subheading` and `##Subheading`. The first `##` line is the subheading; later ones stay in the description. |
| Entries | `actions` entries start with `-`, `*`, `•`, or `+`. Empty and repeated entries are removed. |
| Links in a card | A `[label](url)` in a card's description stays a link. |
| Image URLs | `images`, card images, `payment`, and `documents` accept `http` and `https` URLs only. |
| Link URLs | `links` accepts `http`, `https`, `mailto:`, and `tel:`. |
| `navigate` | A relative path on your site, such as `/pricing`. |
| Other schemes | Anything else, including `javascript:` and `data:`, is removed. |
| Broken URLs | A URL split across two lines inside `(…)` is joined back together. |

## See also

* [Response formatting](/channels/formatting/overview) — which blocks exist and where each one renders
* [list-item](/channels/formatting/list-item) — the card most replies start with
* [form](/channels/formatting/form) — the one block whose body is YAML


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.