# How the Astryx CLI `layout expand` Command Works and What Grammar It Uses

> Learn how the Astryx CLI layout expand command works. Discover its XLE and XLO grammar, which validates and expands layouts into typed TSX components.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: how-to-guide
- Published: 2026-08-04

---

**The `astryx layout expand` command validates a layout expression written in Astryx's compact (XLE) or outline (XLO) syntax, expands it to a fully-typed TSX component, and optionally writes the result to a file.**

In the `facebook/astryx` repository, the `layout expand` CLI command bridges high-level layout declarations with production-ready React components. Understanding this pipeline—including its dual-surface grammar and block-module system—helps developers generate maintainable UI scaffolding from concise expressions.

## CLI Architecture and Execution Flow

The command is implemented as a seven-stage pipeline. Each stage is traceable to specific source files in `packages/cli/`.

### Stage 1: Command Registration

`registerLayout()` in `packages/cli/clients/cli/commands/layout.mjs` defines the `layout expand` subcommand. It accepts flags including `--file`, `--form`, `--name`, and `--loose`. This file also handles the global `--json` flag for machine-readable output.

### Stage 2: Expression Input

`readExpression()` reads the layout string from three possible sources, with a **5 MiB size limit**:

- Positional argument
- `--file` path
- Stdin (via `-`)

Source: `packages/cli/clients/cli/commands/layout.mjs` (lines 48-95)

### Stage 3: Analysis and Validation

`layoutExpand()` calls `analyze()` from `packages/cli/api/layout/_adapter.mjs`. This parses the expression, binds component references via `validate.mjs`, and collects **errors** and **warnings** before expansion proceeds.

Source: `packages/cli/api/layout/expand/expand.mjs` (lines 98-102)

### Stage 4: Block Module Resolution

All `{block}` hints in the AST are collected via `collectHintNames()`. Blocks operate in two modes:

- **Import-mode**: References application components
- **Splice-mode**: Reads template blocks, strips asset references, and inlines the result

Source: `packages/cli/api/layout/expand/expand.mjs` (lines 31-84)

### Stage 5: AST to TSX Expansion

`expand(doc, registry, options)` in `packages/cli/foundation/xle/expand.mjs` performs the core transformation:

- Walks the validated AST
- Generates JSX tags
- Creates `useState` scaffolding
- Resolves slot props
- Emits complete TSX plus metadata (components used, state count, TODOs)

### Stage 6: Optional File Write

When a target path is provided, `assertWithin()` guarantees the write stays within the current working directory. The file is written via `fs.writeFileSync`.

Source: `packages/cli/api/layout/expand/expand.mjs` (lines 21-38)

### Stage 7: Result Return

The command returns a typed envelope `{type:'layout.expand', data:{…}}`. Human mode prints formatted results; `--json` mode emits raw JSON.

Source: `packages/cli/clients/cli/commands/layout.mjs` (lines 122-150)

## The XLE/XLO Grammar System

Astryx supports **two surface syntaxes** that compile to the same internal AST. The grammar implementation lives in `packages/cli/foundation/xle/parse.mjs`.

### Compact Syntax (XLE)

XLE is **one-line, whitespace-insensitive, and Emmet-inspired**. Tokens separate at depth-0 whitespace; groups use `()`; repeats use `*N`.

```

V[g6] > C{card-callout}*4

```

This expands to: a `VStack` with `gap=6` containing four `Card` components, each with the `card-callout` block hint.

### Outline Syntax (XLO)

XLO is **multi-line and indentation-based**. It supports explicit constructs for chains (`>`), slots (`@slot:`), repeats (`repeat N:`), and an `overlays:` section for open-state wiring.

```

Layout > LayoutHeader pad=0
  @title "Dashboard"
LayoutContent
  Card

```

### Core Grammar Tokens

The parser recognizes these token types in order of precedence:

| Token | Syntax | Purpose |
|-------|--------|---------|
| ID | `#identifier` | Element ID |
| Modifier | `.modifier` | Enum/class modifier |
| Payload | `"text"` or `'text'` | Content payload |
| Attributes | `[key=value ...]` | Attribute block |
| Hint | `{block-name}` | Block/module reference |
| Repeat | `*N` | Repeat count |
| Selected | `!` | Selected state flag |

### Attribute Token Grammar

Attribute tokens support:

- `key=value` pairs
- Flags: `+flag`
- Slots: `@slot=value`
- Shorthands: `p6` (padding), `g{min:2}` (gap with constraints)
- Escaped counters: `$`

### Lexical Rules

- Identifiers start with a letter; subsequent characters may be letters, digits, `-`, or `_`
- Whitespace is ignored outside quoted strings
- Brackets `()[]{}<>` increase parsing depth; parsing terminates at depth-0 whitespace

### Parser Dispatch

`detectForm()` determines whether input is compact or outline. The appropriate parser—`parseCompact()` or `parseOutline()`—builds the **XLE AST** using these node types:

- `XLENode`: Single element
- `XLEGroup`: Parenthesized group
- `XLEItem`: Repeatable unit

Both surfaces share the **same attribute-token grammar**, so `validate.mjs` and `expand.mjs` operate independently of the original dialect.

## Practical Usage Examples

### Basic Compact Expression

```bash
astryx layout expand "V[g6] > C{card-callout}*4"

```

Human-mode output shows warnings, the written path, and component list.

### File-Based Outline with Custom Name

```bash
astryx layout expand --file my-layout.txt --name DashboardLayout ./src/DashboardLayout.tsx

```

Contents of [`my-layout.txt`](https://github.com/facebook/astryx/blob/main/my-layout.txt):

```

Layout > LayoutHeader pad=0
  @title "Dashboard"
LayoutContent
  Card

```

### JSON Output for CI Integration

```bash
astryx --json layout expand "V[g6] > C{card-callout}*4"

```

```json
{
  "type": "layout.expand",
  "data": {
    "form": "compact",
    "code": "// Generated by `astryx layout expand` …",
    "componentsUsed": ["VStack", "Card"],
    "states": 2,
    "todos": [],
    "warnings": [],
    "written": null
  }
}

```

### Grammar Verification with `check`

```bash
astryx layout check "V[g6] > C{card-callout}*4"

```

Output:

```

✓ Valid (parsed as compact)

compact:
  V[g6] > C{card-callout}*4

outline:
  VStack gap=6
    Card {card-callout}

```

## Source File Reference

| File | Purpose |
|------|---------|
| `packages/cli/clients/cli/commands/layout.mjs` | CLI registration and I/O handling |
| `packages/cli/api/layout/expand/expand.mjs` | Core expansion orchestration |
| `packages/cli/foundation/xle/parse.mjs` | Dual-syntax grammar parser |
| `packages/cli/foundation/xle/expand.mjs` | AST-to-TSX code generator |
| `packages/cli/foundation/xle/validate.mjs` | Component binding and attribute validation |
| `packages/cli/api/layout/_adapter.mjs` | Shared analysis layer |

## Summary

- `astryx layout expand` is a seven-stage pipeline from raw expression to TSX output
- **XLE** (compact) and **XLO** (outline) are surface syntaxes with identical semantics
- The grammar supports IDs, modifiers, payloads, attributes, block hints, repeats, and selection flags
- Block modules resolve as either imports or inline splices depending on hint context
- The `expand()` function in `foundation/xle/expand.mjs` generates `useState` scaffolding and JSX

## Frequently Asked Questions

### What is the difference between XLE and XLO syntax?

**XLE** is compact and line-oriented, ideal for quick CLI usage and embedding in scripts. **XLO** is verbose and indentation-based, better for version control and complex layouts with overlays. Both parse to the same AST, so validation and expansion behavior is identical.

### How does the `layout expand` command handle block references?

Blocks in `{braces}` are collected via `collectHintNames()` and resolved as either **import-mode** (application components) or **splice-mode** (template blocks read and inlined without assets). This happens in `packages/cli/api/layout/expand/expand.mjs` before TSX generation.

### Can I use `layout expand` in CI/CD pipelines?

Yes. Pass `--json` for machine-parseable output. The command exits with non-zero status on validation errors, and the JSON envelope includes `warnings`, `todos`, and `componentsUsed` for automated checks.

### Where is the grammar formally defined?

The grammar implementation is in `packages/cli/foundation/xle/parse.mjs`. It uses recursive descent with depth tracking for bracket matching. No separate grammar file exists; the parser is hand-written with explicit token precedence for component names, `#id`, `.mod`, payloads, attributes, hints, repeats, and flags.