@@ -4,76 +4,26 @@ A minimalist layout language inspired by elm-ui that compiles to static HTML.
44
55## Principles
66
7−- `@` means structure, bare lines mean content
8−- Layout is explicit and compositional — no CSS cascade
9−- Every element declares its own layout role
10−- Output is a self-contained HTML file with embedded CSS (flexbox)
7+- `@` means structure, bare lines mean content.
8+- Layout is explicit and compositional: every element declares its own layout
9+ role, and there is no CSS cascade to reason about.
10+- Output is one self-contained HTML file with embedded CSS (flexbox), and no
11+ JavaScript unless you write some.
1112
12−## Elements
13−
14−| Element | Output | Purpose |
15−|--------------|------------------|--------------------------------|
16−| `@row` | flex row | Horizontal layout |
17−| `@column` | flex column | Vertical layout |
18−| `@el` | div | Generic container |
19−| `@text` | span | Styled inline text |
20−| `@paragraph` | p with spans | Flowing/wrapping inline text |
21−| `@image` | img | Image |
22−| `@link` | a | Anchor wrapping children |
23−| `@raw` | verbatim HTML | Escape hatch |
24−| `@nav` | nav | Navigation landmark |
25−| `@header` | header | Page/section header |
26−| `@footer` | footer | Page/section footer |
27−| `@main` | main | Main content area |
28−| `@section` | section | Thematic section |
29−| `@article` | article | Self-contained content |
30−| `@aside` | aside | Sidebar/tangential content |
31−| `@list` | ul/ol | List (`[ordered]` for ol) |
32−| `@item` | li | List item |
33−| `@table` | table | Table |
34−| `@thead` | thead | Table head group |
35−| `@tbody` | tbody | Table body group |
36−| `@tr` | tr | Table row |
37−| `@td` | td | Table cell |
38−| `@th` | th | Table header cell |
39−| `@video` | video | Video element |
40−| `@audio` | audio | Audio element |
41−| `@form` | form | Form container |
42−| `@details` | details | Disclosure widget |
43−| `@summary` | summary | Summary for `@details` |
44−| `@blockquote`| blockquote | Block quotation |
45−| `@cite` | cite | Citation/source reference |
46−| `@code` | code | Inline code (monospace) |
47−| `@pre` | pre | Preformatted text |
48−| `@hr` | hr | Horizontal rule (self-closing) |
49−| `@figure` | figure | Figure with optional caption |
50−| `@figcaption`| figcaption | Caption for `@figure` |
51−| `@progress` | progress | Progress bar |
52−| `@meter` | meter | Meter/gauge element |
53−| `@iframe` | iframe | Embedded external page |
54−| `@output` | output | Form calculation result |
55−| `@canvas` | canvas | Drawing surface for scripts |
56−
57−| `@script` | script | JavaScript (inline or external) |
58−| `@noscript` | noscript | Fallback when JS is disabled |
59−| `@address` | address | Contact information |
60−| `@search` | search | Search landmark (HTML5) |
61−| `@breadcrumb` | nav>ol | Semantic breadcrumb navigation |
62−| `@in-front` | div (absolute) | Overlay layer painted on top of parent |
63−| `@behind` | div (absolute) | Layer painted behind the parent's content |
64−
65−Bare lines (not starting with `@` or `[`) are text nodes.
13+Every example block in this file is compiled by the test suite
14+(`tests/docs.rs`), so the examples stay correct.
6615
6716## Syntax
6817
69−### Basic structure
18+### Structure
7019
7120```
72−@element [attributes]
21+@element [attributes] argument
7322 children
7423```
7524
76−Children are indented under their parent. Attributes are optional, comma-separated inside `[...]`. Attribute lists can span multiple lines:
25+Children are indented under their parent. Attributes are comma-separated inside
26+`[...]` and may span several lines:
7727
7828```
7929@el [
@@ -85,272 +35,24 @@ Children are indented under their parent. Attributes are optional, comma-separat
8535 Content
8636```
8737
88−### Full example
89−
90−```
91−-- My Site
92−@page My Site
93−@let primary #3b82f6
94−@let gap 20
95−@define card [
96− padding 20,
97− background white,
98− rounded 8,
99− border 1 #e5e7eb,
100− shadow 0 2px 4px rgba(0,0,0,0.05)
101−]
102−
103−@include header.hl
104−
105−@column [max-width 800, center-x, padding 40, spacing $gap]
106− @row [spacing $gap]
107− @column [width fill, spacing 10]
108− @text [bold, size 32] Welcome
109− @paragraph [line-height 1.6]
110− This is a page built with {@text [bold, color $primary] htmlang}.
111− Read the {@link https://docs.example.com docs} to learn more.
112− @image [width 80, height 80, rounded 40] avatar.png
113−
114− -- cards
115− @row [wrap, spacing 10]
116− [$card]
117− First card
118− [$card, background #f9fafb]
119− Second card
120−
121− -- footer
122− @row [spacing 10]
123− @el [padding 16, background $primary, rounded 8] > @link https://example.com
124− @text [color white] Get Started
125− @el [width fill]
126− @text [color #888, text-align right] © 2026
127−
128− @raw """
129− <canvas id="chart"></canvas>
130− """
131−```
132−
133−## Directives
134−
135−### `@page`
136−
137−Sets the HTML `<title>` and generates boilerplate (`<!DOCTYPE>`, `<html>`, `<head>`, `<body>`).
138−
139−```
140−@page My Site
141−```
142−
143−### `@let`
144−
145−Defines a variable. Referenced with `$name`.
146−
147−```
148−@let primary #3b82f6
149−@let gap 20
150−
151−@el [background $primary, spacing $gap]
152−```
153−
154−Quoted values support string interpolation:
155−
156−```
157−@let name World
158−@let greeting "Hello $name" -- "Hello World"
159−@let base /api
160−@let url "$base/users" -- "/api/users"
161−```
162−
163−### `@include`
164−
165−Inlines another `.hl` file. The path is resolved relative to the current file. Variables defined with `@let`, attribute bundles from `@define`, and functions from `@fn` in the included file are available after the `@include` line.
166−
167−```
168−@include header.hl
169−@include components/card.hl
170−```
171−
172−Variables can be used in the filename:
173−
174−```
175−@let component card
176−@include $component.hl
177−```
178−
179−Circular includes are detected and reported as errors. Nested includes are supported.
180−
181−### `@import`
182−
183−Like `@include`, but only imports definitions (`@let`, `@define`, `@fn`) without emitting DOM nodes. Use this for shared theme/component libraries.
184−
185−```
186−@import theme.hl -- imports variables, defines, functions only
187−@include header.hl -- inlines everything including DOM nodes
188−```
189−
190−### `@data`
191−
192−Loads a JSON file and makes its values available as template variables. Useful for data-driven static sites.
193−
194−```
195−@data site.json -- top-level object keys become variables
196−@data $posts data/posts.json -- load with a prefix
197−```
198−
199−For a JSON file like:
200−```json
201−{
202− "title": "My Site",
203− "tags": ["rust", "html", "css"],
204− "links": [
205− {"label": "Home", "url": "/"},
206− {"label": "About", "url": "/about"}
207− ]
208−}
209−```
210−
211−Without prefix (`@data site.json`):
212−- `$title` → `"My Site"`
213−- `$tags` → iterable with `@each`
214−- `$links` → destructurable: `@each $label, $url in $links`
215−
216−With prefix (`@data $site site.json`):
217−- `$site.title` → `"My Site"`
218−- `$site.tags` → iterable
219−- `$site.links` → destructurable
220−
221−Arrays of objects are automatically formatted for `@each` destructuring. Indexed access is also available: `$links.0.label`, `$links.1.url`, etc. The count is available as `$links._count`.
222−
223−### `@meta`
224−
225−Adds a `<meta>` tag to the document `<head>`. Requires `@page`.
226−
227−```
228−@meta description A portfolio site
229−@meta og:image https://example.com/preview.png
230−```
231−
232−### `@head`
233−
234−Adds raw content to the document `<head>`. Use for external fonts, favicons, or custom CSS/JS.
235−
236−```
237−@head
238− <link rel="icon" href="favicon.ico">
239− <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter">
240−```
241−
242−### `@define`
243−
244−Creates a named attribute bundle. Referenced with `$name` inside attribute lists.
245−
246−```
247−@define card [padding 20, background white, rounded 8, border 1 #e5e7eb]
248−
249−@el [$card]
250− Content
251−@el [$card, background #f9fafb]
252− Overridden background
253−```
254−
255−Attributes listed after `$name` override those in the definition.
256−
257−### `@mixin`
258−
259−Creates a composable style group. Like `@define`, but intended for reusable style patterns. Referenced with `...$name` (spread syntax) or `$name` inside attribute lists.
260−
261−```
262−@mixin card-style [padding 20, rounded 8, background white]
263−@mixin interactive [cursor pointer, hover:background #e5e7eb, transition all 0.15s ease]
264−
265−@el [...$card-style, ...$interactive, border 1 #ccc]
266− Composed styles from two mixins
267−```
268−
269−### `@assert`
270−
271−Compile-time assertion. If the condition is false, a compile error is emitted. Useful for enforcing design system constraints.
272−
273−```
274−@let spacing 16
275−@let primary #3b82f6
276−
277−@assert $spacing == 16
278−@assert $primary != red
279−```
280−
281−### `@fn`
282−
283−Defines a pure function (reusable component). Parameters are prefixed with `$`. Default values can be specified with `=`.
284−
285−```
286−@fn card $title $variant=primary
287− @el [padding 20, background white, rounded 8, border 1 #e5e7eb]
288− @text [bold, size 18] $title
289− @children
290−```
291−
292−Call it like any element, passing parameters in `[...]`:
293−
294−```
295−@card [title Hello World]
296− This is the card body.
297− @text [italic] With styled text.
298−
299−@card [title Warning, variant danger]
300− Overridden variant.
301−```
302−
303−Parameters with defaults can be omitted in calls — the default value is used.
38+A comma inside `(...)` or `"..."` does not split attributes, so a font stack is
39+written `font "Inter, sans-serif"`.
30440
305−### `@children`
41+### Text
30642
307−A slot inside a function body that expands to the caller's indented children.
308−
309−```
310−@fn layout $title
311− @column [max-width 800, center-x, padding 40]
312− @text [bold, size 32] $title
313− @children
314−
315−@layout [title My Page]
316− @row [spacing 10]
317− Content goes here
318−```
319−
320−Functions can call other functions. `@let` and `@define` variables are available inside function bodies. Function parameters shadow `@let` variables of the same name within the body.
321−
322−## Text
323−
324−### Bare text
325−
326−Any line not starting with `@` or `[` is a text node.
327−
328−```
329−@column
330− Hello world
331− This is just text
332−```
333−
334−### Styled text
335−
336−Use `@text` when you need attributes.
337−
338−```
339−@text [bold, size 24, color #333] Hello world
340−```
341−
342−### Inline elements
343−
344−Use `{...}` to embed elements inside text lines. Essential for mixed-style paragraphs.
43+Any line that doesn't start with `@` or `[` is text. Use `@text` when text needs
44+attributes, and `{...}` to put elements inside a line of text:
34545
34646```
34747@paragraph
34848 This is {@text [bold] important} and this is a {@link https://example.com link}.
49+@text [bold, size 24, color #333] Hello world
34950```
35051
351−## Comments
52+### Comments
35253
353−`--` starts a comment. The rest of the line is ignored.
54+`--` at the start of a line begins a comment. Comments must be on their own
55+line: `--` later in a line is ordinary text.
35456
35557```
35658-- this is a comment
@@ -358,133 +60,85 @@ Use `{...}` to embed elements inside text lines. Essential for mixed-style parag
35860 -- todo: add nav items
35961```
36062
361−## Implicit `@el`
63+### Shorthands
36264
363−A line starting with `[` (attributes) that has indented children is an anonymous container (implicit `@el`).
65+A line starting with `[` is an anonymous `@el`. `>` chains single-child
66+elements on one line; the last element in the chain gets the indented children.
36467
36568```
36669[padding 20, background white]
36770 Hello
36871
369−-- equivalent to:
370−@el [padding 20, background white]
371− Hello
372−```
373−
374−## Single-child chaining
375−
376−Use `>` to chain single-child elements on one line, reducing nesting depth.
377−
378−```
37972@el [padding 16, background blue, rounded 8] > @link https://example.com
38073 @text [color white] Get Started
381−
382−-- equivalent to:
383−@el [padding 16, background blue, rounded 8]
384− @link https://example.com
385− @text [color white] Get Started
38674```
38775
388−The last element in the chain takes the indented children.
76+### Raw content
38977
390−## `@raw`
391−
392−Triple-quoted block pasted verbatim into output. Use for arbitrary HTML, CSS, or JS.
78+`@raw """ ... """` is pasted into the output verbatim.
39379
39480```
39581@raw """
396−<style>
397− @keyframes spin { to { transform: rotate(360deg); } }
398−</style>
39982<div class="custom-widget"></div>
40083"""
40184```
40285
403−## Units
404−
405−All numeric values default to pixels. You can use CSS units explicitly:
406−
407−```
408−@el [width 50%, height 100vh, padding 2rem, max-width 80ch, size 1.2em]
409−```
410−
411−Supported units: `%`, `rem`, `em`, `vh`, `vw`, `vmin`, `vmax`, `dvh`, `svh`, `ch`, `ex`, `cm`, `mm`, `in`, `pt`, `pc`, `fr`.
412−
413−## New elements
414−
415−### `@form`
416−
417−Form container. Argument is the `action` URL.
86+## Elements
41887
419−```
420−@form [method post] /submit
88+Layout elements set up flexbox; the rest map to the HTML element of the same
89+name.
90+
91+| Element | Output | Purpose |
92+|---|---|---|
93+| `@row` | div, flex row | Horizontal layout |
94+| `@column` | div, flex column | Vertical layout |
95+| `@el` | div, flex column | Generic container |
96+| `@grid` | div, grid | Grid container (use `grid-cols`) |
97+| `@stack` | div, position relative | Children layered on top of each other |
98+| `@in-front` / `@behind` | div, absolute | Overlay layers filling the parent (see below) |
99+| `@spacer` | div, flex 1 | Pushes siblings apart |
100+| `@text` | span | Styled inline text |
101+| `@paragraph` | p | Flowing text with inline elements |
102+| `@link url` | a | Link; text after the URL becomes its content |
103+| `@image src` | img | Image |
104+| `@h1` … `@h6` | h1 … h6 | Headings |
105+
106+Semantic containers, all laid out as columns: `@nav`, `@header`, `@footer`,
107+`@main`, `@section`, `@article`, `@aside`, `@address`, `@search`, `@form`,
108+`@details` / `@summary`, `@dialog`, `@figure` / `@figcaption`, `@blockquote` /
109+`@cite`, `@fieldset` / `@legend`, `@noscript`.
110+
111+Content: `@list` / `@item` (`@list [ordered]` for `<ol>`), `@dl` / `@dt` /
112+`@dd`, `@table` / `@thead` / `@tbody` / `@tr` / `@th` / `@td`, `@code`, `@pre`,
113+`@hr`, `@mark`, `@kbd`, `@abbr`, `@time`, `@progress`, `@meter`, `@output`,
114+`@canvas`, `@iframe src`, `@video`, `@audio`, `@picture` / `@source`,
115+`@script`, `@breadcrumb`, `@fragment` (children without a wrapper).
116+
117+Form controls: `@input`, `@button`, `@select` / `@option`, `@textarea`,
118+`@label`, `@datalist`.
119+
120+Styled components: `@badge`, `@chip`, `@tag`, `@avatar`, `@tooltip`,
121+`@carousel`.
122+
123+```
124+@list [ordered]
125+ @item First
126+ @item Second
127+@form [method post] /subscribe
421128 @label [for email] Email
422− @input [type email, name email, required]
129+ @input [type email, name email, id email, required]
423130 @button [type submit] Send
424−```
425−
426−### `@details` / `@summary`
427−
428−Native disclosure widget. Use `[open]` to expand by default.
429−
430−```
431131@details [open]
432− @summary FAQ Question
433− @text The answer is here.
434−```
435−
436−### `@blockquote` / `@cite`
437−
438−Semantic quotation with optional citation.
439−
440−```
441−@blockquote [padding 20, border-left 4 #ccc]
442− @text To be or not to be
443− @cite Shakespeare
444−```
445−
446−### `@code` / `@pre`
447−
448−Code and preformatted text. `@code` renders as `<code>` with monospace font. `@pre` preserves whitespace.
449−
450−```
451−@pre
452− @code console.log("hello")
453−```
454−
455−### `@hr`
456−
457−Horizontal rule / divider (self-closing). Alias: `@divider`.
458−
459−```
460−@hr [border-top 1 #e5e7eb]
461−```
462−
463−### `@figure` / `@figcaption`
464−
465−Figure with optional caption.
466−
467−```
468−@figure
469− @image [alt Sunset, width fill] sunset.jpg
470− @figcaption A beautiful sunset
471−```
472−
473−### `@progress` / `@meter`
474−
475−Progress bar and meter elements. Use `value`, `max`, `min` attributes.
476−
477−```
478−@progress [value 70, max 100]
479−@meter [value 6, min 0, max 10, low 3, high 8]
132+ @summary Question
133+ @text The answer.
480134```
481135
482136### `@in-front` / `@behind`
483137
484−Overlay layers, inspired by elm-ui's `inFront` and `behind`. Children of these
485−directives are absolutely positioned to fill the parent's bounds; the parent
486−automatically becomes a positioning context (`position: relative` and
487−`isolation: isolate`).
138+Overlay layers, like elm-ui's `inFront` and `behind`. Their children fill the
139+parent's bounds; the parent automatically becomes a positioning context
140+(`position: relative; isolation: isolate`). An explicit `position` on the parent
141+wins.
488142
489143```
490144@el [width 200, height 200, background blue]
@@ -495,819 +149,279 @@ automatically becomes a positioning context (`position: relative` and
495149 @el [background red, width fill, height fill]
496150```
497151
498−`@in-front` paints on top of the parent's content (DOM order ensures it
499−overlays static siblings). `@behind` paints behind (`z-index: -1` keeps it
500−under the siblings, while `isolation: isolate` on the parent prevents it from
501−slipping behind the parent's own background).
502−
503−If you set `position` explicitly on the parent (e.g., `position absolute` for a
504−popup), that wins over the auto-applied `position: relative`.
505−
506−## `@each` destructuring
507−
508−When `@each` has more than one variable and items contain spaces, values are destructured:
509−
510−```
511−@each $name, $url in Home /, About /about, Contact /contact
512− @link $url $name
513−```
514−
515−`var()` and `calc()` expressions are also passed through as-is.
152+## Attributes
516153
517−## `@each` with `@else`
154+### Layout and sizing
518155
519−When the list is empty, the `@else` block is rendered as a fallback:
156+| Attribute | Effect |
157+|---|---|
158+| `spacing N` | Gap between children (also `gap-x N`, `gap-y N`) |
159+| `padding N` / `padding Y X` / `padding T R B L` | Padding (also `padding-x`, `padding-y`, per side) |
160+| `margin ...` | Margin, same forms as padding |
161+| `width fill` / `width N` / `width shrink` | Take remaining space, exact size, or fit content |
162+| `height fill` / `height N` / `height shrink` | Same for height |
163+| `min-width`, `max-width`, `min-height`, `max-height` | Size limits |
164+| `center-x`, `center-y` | Center within the parent |
165+| `align-left`, `align-right`, `align-top`, `align-bottom` | Align within the parent |
166+| `wrap` | Let a row wrap |
167+| `grid-cols N`, `grid-rows N`, `col-span N`, `row-span N` | Grid layout |
168+| `hidden` | `display: none` |
520169
521−```
522−@each $item in $items
523− @text $item
524−@else
525− @text [color #888] No items found.
526−```
170+### Style
527171
528−## `@iframe`
172+| Attribute | Effect |
173+|---|---|
174+| `background COLOR`, `color COLOR` | Colors |
175+| `border N COLOR` (also `border-top` etc.) | Border |
176+| `rounded N` | Border radius |
177+| `shadow VALUE` | Box shadow |
178+| `bold`, `italic`, `underline` | Text style |
179+| `size N` | Font size |
180+| `font NAME` | Font family |
181+| `transition VALUE`, `animation VALUE` | Motion |
529182
530−Embedded external page. Argument is the `src` URL.
183+Most CSS properties can also be written directly as attributes with the same
184+name and value: `opacity 0.5`, `cursor pointer`, `z-index 10`,
185+`text-align center`, `position absolute`, `grid-template-areas "a b"`, and so on.
531186
532−```
533−@iframe [width fill, height 400, sandbox] https://example.com
534−```
187+**Units.** A bare number is pixels (`padding 20` is `20px`). Values with a unit,
188+keywords and CSS functions are passed through: `width 50%`, `margin 0 auto`,
189+`max-width min(100%, 800px)`.
535190
536−Attributes: `sandbox`, `allow`, `allowfullscreen`, `referrerpolicy`.
191+### HTML attributes
537192
538−## `@canvas`
193+`id`, `class`, and element attributes such as `href`, `type`, `name`, `value`,
194+`placeholder`, `alt`, `for`, `required`, `disabled`, `target`, `rel`, `role`,
195+`tabindex`, `title`, `data-*` and `aria-*` are emitted as HTML attributes.
539196
540−Drawing surface for JavaScript. Use with `@raw` for scripts.
197+### State and media prefixes
541198
542−```
543−@canvas [width 400, height 300, id chart]
544−```
199+Prefix a style attribute to apply it conditionally:
545200
546−## `@output`
201+| Prefix | Applies |
202+|---|---|
203+| `hover:`, `active:`, `focus:`, `focus-visible:`, `focus-within:`, `disabled:`, `checked:`, `visited:`, `target:`, `valid:`, `invalid:`, `empty:`, `placeholder:`, `selection:` | In that state |
204+| `first:`, `last:`, `odd:`, `even:`, `nth(EXPR):` | By position among siblings |
205+| `before:`, `after:` | On the `::before` / `::after` pseudo-element (with `content`) |
206+| `has(SELECTOR):` | When the element contains a match |
207+| `sm:`, `md:`, `lg:`, `xl:`, `2xl:` | From that viewport width up (640–1536px) |
208+| `cq-sm:` … `cq-2xl:` | Container queries |
209+| `dark:`, `print:`, `motion-safe:`, `motion-reduce:`, `landscape:`, `portrait:` | Media conditions |
547210
548−Form output element for displaying calculation results.
211+`@breakpoint name WIDTH` defines a custom responsive prefix.
549212
550213```
551−@output [for a b] Result
214+@el [padding 16, background #3b82f6, hover:background #2563eb, md:padding 32, dark:background #1e3a8a]
215+ @text [color white] Click me
216+@el [before:content "→ ", before:color red]
217+ Item with an arrow
552218```
553219
554−## Pseudo-elements
555−
556−Prefix any style attribute with `before:` or `after:` to apply it to the `::before` or `::after` pseudo-element. Use with `content` to set the generated content.
220+### Conditional attributes
557221
558222```
559−@el [before:content "→ ", before:color red, before:font-weight bold]
560− Item with arrow prefix
561−
562−@el [after:content " ✓", after:color green]
563− Completed item
223+@let active true
224+@el [background if($active, blue, gray), bold if $active]
225+ Conditionally styled
564226```
565227
566−Content values are automatically quoted unless they are CSS keywords (`none`, `normal`) or CSS functions (`attr()`, `counter()`).
567−
568−## Conditional attribute values
228+## Variables and functions
569229
570−Use `if(condition, true_value, false_value)` inside attribute values for conditional styling:
230+`@let` defines everything reusable. What it defines depends on its shape:
571231
572232```
573−@let active true
574−@let theme dark
575−
576−@el [background if($active, blue, gray)]
577− Conditionally styled
233+-- A value, used as $primary
234+@let primary #3b82f6
235+-- A computed value (= is optional)
236+@let gap = 8 * 2
237+-- A quoted string: interpolated, never computed
238+@let greeting "Hello $primary"
239+-- An attribute bundle, used as [$card]
240+@let card [padding 20, background white, rounded 8]
241+-- A function: a @let with an indented body
242+@let panel $title $tone=neutral
243+ @el [$card]
244+ @text [bold] $title
245+ @children
578246
579−@el [color if($theme == dark, white, black)]
580− Theme-aware text
247+@panel [title Welcome]
248+ Body text goes into @children.
249+@el [$card, background #f9fafb]
250+ Attributes after a bundle override it.
581251```
582252
583−Supports equality (`==`), inequality (`!=`), and truthy checks.
584−
585−## Attributes reference
586−
587−### Layout (set on parent)
588−
589−| Attribute | Effect |
590−|------------------------|----------------------------------|
591−| `spacing N` | Gap between children |
592−| `gap N` | Alias for `spacing` |
593−| `gap-x N` | Horizontal gap (column-gap) |
594−| `gap-y N` | Vertical gap (row-gap) |
595−| `padding N` | Uniform padding |
596−| `padding Y X` | Vertical + horizontal padding |
597−| `padding T H B` | Top + horizontal + bottom |
598−| `padding T R B L` | Per-side padding |
599−| `padding-x N` | Horizontal padding |
600−| `padding-y N` | Vertical padding |
601−
602−### Sizing (set on child)
603−
604−| Attribute | Effect |
605−|------------------------|----------------------------------|
606−| `width fill` | Take remaining space (flex: 1) |
607−| `width N` | Exact pixels |
608−| `width shrink` | Fit to content (default) |
609−| `height fill` | Take remaining space |
610−| `height N` | Exact pixels |
611−| `height shrink` | Fit to content (default) |
612−| `min-width N` | Minimum width |
613−| `max-width N` | Maximum width |
614−| `min-height N` | Minimum height |
615−| `max-height N` | Maximum height |
616−
617−### Alignment (set on child)
618−
619−| Attribute | Effect |
620−|------------------------|----------------------------------|
621−| `center-x` | Center horizontally |
622−| `center-y` | Center vertically |
623−| `align-left` | Align to left |
624−| `align-right` | Align to right |
625−| `align-top` | Align to top |
626−| `align-bottom` | Align to bottom |
627−
628−### Style
253+Function arguments are passed as attributes; parameters with `=default` may be
254+omitted. `@component name $params` defines a function whose indented `@style`
255+block is scoped to its output. Inside a function, `@children` is replaced by the caller's children,
256+and `@slot name` by the caller's `@slot name` block (the slot's own children are
257+the default).
629258
630−| Attribute | Effect |
631−|------------------------|----------------------------------|
632−| `background COLOR` | Background color |
633−| `color COLOR` | Text color |
634−| `border N COLOR` | Border width and color |
635−| `border-top N COLOR` | Top border |
636−| `border-bottom N COLOR`| Bottom border |
637−| `border-left N COLOR` | Left border |
638−| `border-right N COLOR` | Right border |
639−| `rounded N` | Border radius |
640−| `shadow VALUE` | Box shadow (CSS value) |
641−| `bold` | Bold text |
642−| `italic` | Italic text |
643−| `underline` | Underlined text |
644−| `size N` | Font size in px |
645−| `font NAME` | Font family |
646−| `text-align VALUE` | Text alignment (left/center/right/justify) |
647−| `line-height VALUE` | Line height (unitless or px) |
648−| `letter-spacing N` | Letter spacing |
649−| `text-transform VALUE` | Transform (uppercase/lowercase/capitalize) |
650−| `white-space VALUE` | White-space (nowrap/pre/normal) |
651−| `transition VALUE` | CSS transition |
652−| `cursor VALUE` | Cursor style |
653−| `opacity VALUE` | Opacity (0–1) |
654−| `overflow VALUE` | Overflow (hidden/scroll/auto/visible) |
655−| `position VALUE` | Position (relative/absolute/fixed/sticky) |
656−| `top N` | Top offset (positioned elements) |
657−| `right N` | Right offset |
658−| `bottom N` | Bottom offset |
659−| `left N` | Left offset |
660−| `z-index N` | Stack order |
661−| `display VALUE` | Display mode (none/block/flex/grid) |
662−| `visibility VALUE` | Visibility (visible/hidden) |
663−| `transform VALUE` | CSS transform (e.g., rotate(45deg)) |
664−| `backdrop-filter VALUE`| Backdrop filter (e.g., blur(10px)) |
665−
666−### Margin
667−
668−| Attribute | Effect |
669−|------------------------|----------------------------------|
670−| `margin N` | Uniform margin |
671−| `margin Y X` | Vertical + horizontal margin |
672−| `margin T R B L` | Per-side margin |
673−| `margin-x N` | Horizontal margin |
674−| `margin-y N` | Vertical margin |
675−
676−### Additional Style
677−
678−| Attribute | Effect |
679−|------------------------|----------------------------------|
680−| `filter VALUE` | CSS filter (blur, brightness) |
681−| `object-fit VALUE` | Object fit (cover/contain/fill) |
682−| `object-position VALUE`| Object position within container|
683−| `text-shadow VALUE` | Text shadow |
684−| `text-overflow VALUE` | Text overflow (ellipsis/clip) |
685−| `pointer-events VALUE` | Pointer events (none/auto) |
686−| `user-select VALUE` | User selection (none/text/all) |
687−| `justify-content VALUE`| Main axis alignment |
688−| `align-items VALUE` | Cross axis alignment |
689−| `order N` | Flex/grid item order |
690−| `background-size VALUE`| Background size |
691−| `background-position VALUE`| Background position |
692−| `background-repeat VALUE`| Background repeat |
693−| `word-break VALUE` | Word break (break-all/keep-all) |
694−| `overflow-wrap VALUE` | Overflow wrap (break-word) |
695−| `font-weight VALUE` | Font weight (100-900/bold/lighter)|
696−| `font-style VALUE` | Font style (normal/italic/oblique)|
697−| `text-wrap VALUE` | Text wrapping (balance/pretty) |
698−| `will-change VALUE` | Performance hint (transform) |
699−| `touch-action VALUE` | Touch behavior (none/pan-x) |
700−| `vertical-align VALUE` | Vertical alignment (middle/top) |
701−| `contain VALUE` | CSS containment (layout/paint) |
702−| `content-visibility VALUE` | Lazy rendering (auto) |
703−| `scroll-margin N` | Scroll margin (for anchors) |
704−| `scroll-padding N` | Scroll padding (scroll-snap) |
705−| `content VALUE` | Pseudo-element content (with before:/after:)|
706−
707−### Pseudo-states
708−
709−Prefix any style attribute with `hover:`, `active:`, or `focus:` to apply it on that state.
259+A multi-line string uses triple quotes; its indented lines are the value:
710260
711261```
712−@el [padding 16, background #3b82f6, hover:background #2563eb, active:background #1d4ed8, rounded 8, transition all 0.15s ease]
713− @text [color white] Click me
262+@let intro """
263+ First line
264+ Second line
265+ """
266+@text $intro
714267```
715268
716−All style attributes support state prefixes: `hover:color`, `active:rounded`, `focus:border`, etc.
717−
718−### Dark mode
719−
720−Prefix any style attribute with `dark:` to apply it when the user's system is in dark mode.
269+`@let --name value` also emits a CSS custom property. `@theme` declares a group
270+of design tokens at once; each becomes both `$name` and `--name`:
721271
722272```
723−@el [background white, dark:background #1a1a2e, color #333, dark:color #eee]
724− @text Theme-aware content
273+@theme
274+ brand #3b82f6
275+ radius 8
276+@el [background $brand, rounded $radius] Themed
725277```
726278
727−This generates a `@media (prefers-color-scheme: dark)` rule.
728−
729−### Print styles
279+### Filters
730280
731−Prefix any style attribute with `print:` to apply it when printing.
281+`$name|filter` transforms a value: `uppercase`, `lowercase`, `capitalize`,
282+`trim`, `length`, `reverse`, `truncate:N`, `replace:OLD:NEW`, `default:VALUE`,
283+and for colors `lighten:N`, `darken:N`, `alpha:N`, `mix:COLOR:N`.
732284
733285```
734−@nav [print:display none]
735− @text Navigation (hidden in print)
286+@let name htmlang
287+@let base #3b82f6
288+@text [color $base|darken:10] $name|uppercase
736289```
737290
738−This generates a `@media print` rule.
291+### Checks
739292
740−### Flow & Grid
293+`@assert CONDITION` fails the build when false. `@warn MESSAGE` emits a
294+warning; `@debug` and `@log` print values at compile time. `@deprecated MESSAGE`
295+before a function warns every caller.
741296
742−| Attribute | Effect |
743−|------------------------|----------------------------------|
744−| `wrap` | Enable flex-wrap (on `@row`) |
745−| `grid` | Enable CSS grid layout |
746−| `grid-cols N` | Grid template columns (N equal) |
747−| `grid-rows N` | Grid template rows (N equal) |
748−| `col-span N` | Span N columns in grid |
749−| `row-span N` | Span N rows in grid |
297+## Control flow
750298
751−### Identity
299+All control flow runs at compile time.
752300
753−| Attribute | Effect |
754−|------------------------|----------------------------------|
755−| `id NAME` | HTML id attribute |
756−| `class NAME` | HTML class attribute |
757−
758−### Modern CSS
759−
760−| Attribute | Effect |
761−|------------------------|----------------------------------|
762−| `color-scheme VALUE` | Color scheme (`light`, `dark`, `light dark`) |
763−| `appearance VALUE` | Form element appearance (`none`) |
764−
765−### Popover API
766−
767−Use HTML Popover API attributes for declarative popovers without JavaScript.
768−
769−```
770−@button [popovertarget info-panel] Show Info
771−@el [popover, id info-panel, padding 20, background white, rounded 8]
772− @text This is a popover
773301```
302+@let items apple, banana, cherry
303+@let count 3
774304
775−| Attribute | Effect |
776−|-------------------------------|---------------------------------|
777−| `popover` | Make element a popover |
778−| `popovertarget ID` | Open popover with given ID |
779−| `popovertargetaction VALUE` | Action (toggle/show/hide) |
780−
781−### Input hints
782−
783−| Attribute | Effect |
784−|------------------------|----------------------------------|
785−| `inputmode VALUE` | Virtual keyboard type (numeric/email/search/tel/url) |
786−| `enterkeyhint VALUE` | Enter key label (enter/done/go/next/previous/search/send) |
787−| `fetchpriority VALUE` | Resource priority (high/low/auto) |
788−| `translate VALUE` | Whether to translate (yes/no) |
789−| `spellcheck VALUE` | Spell check (true/false) |
790−
791−## Asset inlining
305+@if $count > 2
306+ @text Many
307+@else if $count == 0
308+ @text None
309+@else
310+ @text Few
792311
793−SVG images can be inlined directly into the HTML output using the `[inline]` attribute:
312+@if not $count
313+ @text Nothing to show
794314
795−```
796−@image [inline, width 24, height 24] icon.svg
797−```
315+@each $item in $items
316+ @text $_index: $item
317+@else
318+ @text The list is empty.
798319
799−This reads the SVG file and embeds its content directly, keeping the output self-contained.
320+@each $i in 1..10 step 3
321+ @text $i
800322
801−## CLI
323+@each $label, $url in Home /, About /about
324+ @link $url $label
802325
803−```
804−htmlang init # scaffold a new project
805−htmlang init my-site # scaffold in a new directory
806−htmlang new about-us # create about-us.hl from template
807−htmlang page.hl # compile page.hl → page.html
808−htmlang site/ # compile all .hl files in directory
809−htmlang --watch page.hl # compile and watch for changes
810−htmlang -w page.hl # short form
811−htmlang --serve site/ # serve a multi-page site with hot reload
812−htmlang -s --open site/ # serve and open browser
813−htmlang check page.hl # check for errors without writing output
814−htmlang check src/ # check all .hl files in a directory
815−htmlang convert page.html # convert HTML to .hl format (stdout)
816−htmlang build src/ -o dist/ # parallel compile + copy static assets
817−htmlang --compat page.hl # compile with vendor prefixes
818−htmlang --format json page.hl # output diagnostics as JSON
819−htmlang lint --format json src/ # lint with JSON output
820−htmlang dead-code --format json src/ # dead code report as JSON
821−htmlang lint page.hl # stricter lint checks (accessibility, nesting)
822−htmlang lint src/ # lint all .hl files in a directory
823−htmlang stats page.hl # show file statistics (elements, CSS, colors)
824−htmlang repl # interactive REPL (type .hl, get HTML)
825−htmlang feed src/ # generate RSS feed from @page metadata
826−htmlang feed src/ -b https://mysite.com # with custom base URL
827−htmlang components src/ # list all @fn definitions across project
828−htmlang deps src/ # show dependency graph (@include/@import)
829−htmlang dead-code src/ # find unused @fn, @define, @let across project
830−htmlang deploy src/ # build and deploy to GitHub Pages
831−htmlang playground # generate self-contained HTML playground
832−htmlang clean src/ # remove generated .html files
833−htmlang outline page.hl # show document structure tree
326+@match $count
327+ @case 3
328+ @text Three
329+ @default
330+ @text Other
834331```
835332
836−Watch mode recompiles automatically when the source file or any `@include`d/`@import`ed files change.
333+`@defer` keeps its children hidden until they scroll near the viewport (they
334+are shown immediately when JavaScript is off).
837335
838−The `build` command automatically extracts shared CSS rules across pages into a `shared.css` file when outputting to a directory.
336+Conditions support `==`, `!=`, `<`, `>`, `<=`, `>=`, `contains`, `starts-with`,
337+`ends-with`, `not`, and truthiness (empty, `false` and `0` are false). A range
338+counts down when its start is greater than its end.
839339
840−## Compilation target
340+## Files and data
841341
842−Each `.hl` file compiles to a single self-contained `.html` file:
342+| Directive | Effect |
343+|---|---|
344+| `@include file.hl` | Insert another file here, content and definitions |
345+| `@import file.hl` | Take only its definitions (`@let`, bundles, functions) |
346+| `@extends layout.hl` | Render this page inside a layout (below) |
347+| `@data file.json` / `@data $prefix file.json` | Load JSON values as variables |
348+| `@fetch $prefix http://...` | Like `@data`, fetched at build time (http only) |
349+| `@collection $name "glob"` | List files matching a pattern |
350+| `@translations` | Per-locale strings, used as `$t.key` |
351+| `@env NAME default` | Read an environment variable |
352+| `@markdown` / `@markdown file.md` | Markdown, converted to HTML |
353+| `@svg file.svg` | Inline an SVG file |
843354
844−- Elements map to `<div>`, `<span>`, `<p>`, `<a>`, `<img>` as appropriate
845−- Layout uses flexbox (`display: flex`, `flex-direction`, `gap`, etc.)
846−- Styles are scoped via generated class names in an embedded `<style>` block
847−- No external CSS, no JavaScript (unless injected via `@raw`)
848−- `@page` generates the HTML boilerplate; without it, output is an HTML fragment
355+With `@extends`, the layout marks where content goes with `@slot name` (named
356+blocks) and `@children` (everything in the page outside `@slot` blocks):
849357
850−## Template inheritance (`@extends`)
851−
852−A page can inherit a base layout and fill named `@slot` blocks:
853−
854−**layout.hl:**
855358```
359+-- layout.hl
856360@page My Site
857−@column [max-width 800, center-x, padding 40]
361+@column [max-width 800, center-x]
858362 @slot header
859− @text [bold, size 32] Default Header
860− @slot content
861− @slot footer
862− @text [color #888] Default Footer
363+ @text Default header
364+ @children
863365```
864366
865−**about.hl:**
866367```
368+-- page.hl
867369@extends layout.hl
868370@slot header
869− @text [bold, size 32] About Us
870−@slot content
871− @paragraph We are a team of...
872−```
873−
874−Slots not filled by the extending page use the default children from the layout.
875−
876−## Design tokens (`@theme`)
877−
878−Centralized design token definitions. Each token becomes both a `$variable` and a `--css-custom-property`:
879−
880−```
881−@theme
882− primary #3b82f6
883− secondary #10b981
884− spacing-sm 8
885− spacing-md 16
886− font-body system-ui, sans-serif
371+ @text About us
372+@paragraph This fills @children.
887373```
888374
889−Equivalent to:
890−```
891−@let primary #3b82f6
892−@let --primary #3b82f6
893−@let secondary #10b981
894−@let --secondary #10b981
895−...
896−```
897−
898−## Deprecation (`@deprecated`)
899−
900−Mark a function as deprecated. Callers receive a compile-time warning:
901−
902−```
903−@deprecated Use @card-v2 instead
904−@fn old-card $title
905− @el [padding 20]
906− @text $title
907− @children
908−```
375+## Page and head
909376
910−When `@old-card` is called, the compiler emits: `warning: @old-card is deprecated: Use @card-v2 instead`
911−
912−## Color functions
913−
914−Color manipulation via variable filters:
377+`@page TITLE` produces a full HTML document; without it the output is a fragment.
378+Head content comes from `@lang`, `@favicon`, `@meta NAME VALUE`,
379+`@og KEY VALUE`, `@canonical URL`, `@base URL`, `@manifest NAME` (with
380+indented settings), `@font-face NAME URL`, `@json-ld` (indented JSON) and `@head`
381+(indented raw HTML).
915382
916383```
917−@let primary #3b82f6
918−
919−@el [background $primary|lighten:20] -- 20% lighter
920−@el [background $primary|darken:10] -- 10% darker
921−@el [background $primary|alpha:0.5] -- 50% transparent (#3b82f67f)
922−@el [background $primary|mix:#ffffff:50] -- 50% mixed with white
923−```
924−
925−Filters: `lighten:N` (0-100), `darken:N` (0-100), `alpha:N` (0-1), `mix:COLOR:N` (0-100).
926−
927−## Enhanced `@keyframes`
928−
929−Keyframes support htmlang attribute syntax:
930−
931−```
932−@keyframes fade-in
933− from [opacity 0]
934− to [opacity 1]
935−
936−@keyframes slide
937− 0% [transform translateX(-100%)]
938− 50% [transform translateX(0), opacity 1]
939− 100% [transform translateX(100%), opacity 0]
940−```
941−
942−Raw CSS syntax is also supported for backwards compatibility.
943−
944−## Additional attributes
945−
946−| Attribute | Effect |
947−|-----------|--------|
948−| `autofocus` | Auto-focus element on page load (boolean) |
949−
950−## Named grid areas
951−
952−Use CSS Grid's named template areas with the `grid-template-areas` and `grid-area` attributes:
953−
954−```
955−@el [grid, grid-template-areas "header header" "sidebar main" "footer footer", gap 20]
956− @el [grid-area header, padding 10, background #3b82f6]
957− Header
958− @el [grid-area sidebar, padding 10]
959− Sidebar
960− @el [grid-area main, padding 10]
961− Main content
962− @el [grid-area footer, padding 10]
963− Footer
384+@page My Site
385+@lang en
386+@meta description A small site
387+@og title My Site
964388```
965389
966−## Animate shorthand
390+## CSS
967391
968−The `animate` attribute is shorthand for the CSS `animation` property:
392+- `@style` holds raw CSS, including at-rules such as `@scope`, `@property` and
393+ `@starting-style`.
394+- `@keyframes name` takes htmlang attribute syntax (`from [opacity 0]`) or raw
395+ CSS.
396+- Generated rules live in `@layer htmlang` (after a `hl-reset` layer), so any
397+ CSS in `@style` or `@raw` overrides them.
969398
970399```
971400@keyframes fade-in
972401 from [opacity 0]
973402 to [opacity 1]
974−
975−@el [animate fade-in 0.3s ease]
976− Fades in on load
977−```
978−
979−## View transitions
980−
981−The `view-transition-name` attribute enables the View Transitions API for smooth page transitions:
982−
983−```
984−@el [view-transition-name hero]
985− Hero content that transitions between pages
986−```
987−
988−## `:has()` pseudo-selector
989−
990−Style elements based on their children using the `has()` prefix:
991−
992−```
993−@el [padding 20, has(.active):background #dbeafe, has(img):padding 0]
994− Content
995−```
996−
997−Generates CSS `:has()` selectors: `.class:has(.active) { background:#dbeafe; }`
998−
999−## Computed `@let`
1000−
1001−Variables support arithmetic expressions. The `=` sign is optional:
1002−
1003−```
1004−@let base 16
1005−@let large = $base * 2 -- 32
1006−@let gap $base + 4 -- 20 (= is optional)
1007−```
1008−
1009−Supported operators: `+`, `-`, `*`, `/`.
1010−
1011−## Named slots in `@fn`
1012−
1013−Functions support named `@slot` blocks for multi-region components:
1014−
1015−```
1016−@fn layout $title
1017− @column [max-width 800, center-x, padding 40]
1018− @slot header
1019− @text [bold, size 32] $title
1020− @slot content
1021− @slot footer
1022− @text [color #888] Default Footer
1023−
1024−@layout [title My Page]
1025− @slot header
1026− @text [bold, size 32] Custom Header
1027− @slot content
1028− @paragraph Main content here
1029−```
1030−
1031−Slots not filled by the caller use the default children from the function definition.
1032−
1033−## New elements
1034−
1035−### `@script`
1036−
1037−Embeds JavaScript. Supports inline code and external files via `src`. Children are raw JS (not HTML-escaped).
1038−
1039−```
1040−@script [src app.js, defer]
1041−
1042−@script
1043− console.log("hello world");
1044− document.addEventListener("DOMContentLoaded", () => {
1045− init();
1046− });
1047−```
1048−
1049−Attributes: `src`, `type`, `defer`, `async`, `crossorigin`, `integrity`, `nomodule`.
1050−
1051−### `@noscript`
1052−
1053−Fallback content displayed when JavaScript is disabled.
1054−
1055−```
1056−@noscript
1057− @text This page requires JavaScript.
1058−```
1059−
1060−### `@address`
1061−
1062−Contact information, rendered as `<address>`.
1063−
1064−```
1065−@address
1066− @text [bold] John Doe
1067− @link mailto:john@example.com john@example.com
1068−```
1069−
1070−### `@search`
1071−
1072−HTML5 `<search>` landmark for search functionality.
1073−
1074−```
1075−@search
1076− @input [type search, placeholder Search...]
1077− @button [type submit] Search
1078−```
1079−
1080−### `@breadcrumb`
1081−
1082−Semantic breadcrumb navigation. Generates `<nav aria-label="breadcrumb">` with an `<ol>` list. Each child becomes an `<li>`.
1083−
1084−```
1085−@breadcrumb [spacing 10]
1086− @link / Home
1087− @link /docs Documentation
1088− @text Current Page
1089−```
1090−
1091−## New directives
1092−
1093−### `@canonical`
1094−
1095−Sets the canonical URL. Generates `<link rel="canonical">` in the document head.
1096−
1097−```
1098−@canonical https://example.com/my-page
1099−```
1100−
1101−### `@base`
1102−
1103−Sets the base URL for relative links. Generates `<base href="...">`.
1104−
1105−```
1106−@base https://example.com/
1107−```
1108−
1109−### `@font-face`
1110−
1111−Declares a custom font. Generates a CSS `@font-face` rule with `font-display: swap`. Format is auto-detected from the file extension.
1112−
1113−```
1114−@font-face Inter fonts/inter.woff2
1115−@font-face Mono fonts/jetbrains-mono.ttf
1116−```
1117−
1118−### `@json-ld`
1119−
1120−Structured data for SEO. Generates a `<script type="application/ld+json">` block. Indented body is raw JSON.
1121−
1122−```
1123−@json-ld
1124− {
1125− "@context": "https://schema.org",
1126− "@type": "WebPage",
1127− "name": "My Site"
1128− }
1129−```
1130−
1131−## Additional pseudo-states
1132−
1133−| Prefix | CSS Selector | Purpose |
1134−|--------|--------------|---------|
1135−| `visited:` | `:visited` | Visited links |
1136−| `empty:` | `:empty` | Empty elements |
1137−| `target:` | `:target` | URL fragment target |
1138−| `valid:` | `:valid` | Valid form inputs |
1139−| `invalid:` | `:invalid` | Invalid form inputs |
1140−
1141−```
1142−@link [visited:color purple] https://example.com
1143− Already visited
1144−
1145−@input [type email, valid:border 2 green]
1146−@el [target:background yellow, id section-1]
1147− Highlighted when navigated to
1148−```
1149−
1150−## Additional CSS properties
1151−
1152−| Attribute | Effect |
1153−|-----------|--------|
1154−| `text-underline-offset N` | Offset of text underline from text |
1155−| `column-width N` | Ideal column width for multi-column layout |
1156−| `column-rule VALUE` | Rule between columns (e.g., `1px solid #ccc`) |
1157−
1158−```
1159−@text [underline, text-underline-offset 4, text-decoration-thickness 2]
1160− Styled underline
1161−
1162−@el [column-count 3, column-width 200, column-rule 1px solid #ccc]
1163− Multi-column content
1164−```
1165−
1166−## CSS `@layer` wrapping
1167−
1168−Generated styles are wrapped in `@layer htmlang { ... }` for specificity management. This allows `@style` blocks and `@raw` CSS to easily override generated styles without specificity wars.
1169−
1170−## CLI commands
1171−
1172−### `htmlang deps`
1173−
1174−Show the file dependency graph (`@include`, `@import`, `@extends` relationships):
1175−
1176−```
1177−htmlang deps src/
1178−```
1179−
1180−### `htmlang dead-code`
1181−
1182−Find unused `@fn`, `@define`, and `@let` definitions across an entire project:
1183−
1184−```
1185−htmlang dead-code src/
1186−```
1187−
1188−### `htmlang deploy`
1189−
1190−Build and deploy to GitHub Pages:
1191−
1192−```
1193−htmlang deploy src/
1194−```
1195−
1196−Compiles all `.hl` files, copies static assets, and pushes to a `gh-pages` branch.
1197−
1198−### `htmlang clean`
1199−
1200−Remove generated `.html` files from a directory (matching `.hl` source files). Also removes `sitemap.xml` if present.
1201−
1202−```
1203−htmlang clean src/
1204−htmlang clean # clean current directory
1205−```
1206−
1207−### `htmlang outline`
1208−
1209−Show the document structure tree — a quick overview of elements and nesting without compiling.
1210−
1211−```
1212−htmlang outline page.hl
1213−```
1214−
1215−### `htmlang playground`
1216−
1217−Generate a self-contained HTML playground for experimenting with htmlang:
1218−
1219−```
1220−htmlang playground # writes playground.html
1221−htmlang playground my-play.html # custom output path
1222−htmlang clean src/ # remove generated .html files
1223−htmlang outline page.hl # show document structure tree
1224−```
1225−
1226−## Editor support
1227−
1228−A VS Code extension is available in `editors/vscode/` with:
1229−
1230−- Syntax highlighting via TextMate grammar
1231−- LSP integration via `htmlang-lsp` for:
1232− - Real-time diagnostics and error checking
1233− - Completions for elements, attributes, variables, and functions
1234− - Hover documentation for all elements and attributes
1235− - Go to definition for variables, defines, and functions
1236− - Find all references for variables and functions
1237− - Rename refactoring for variables and functions
1238− - Signature help for `@fn` parameters
1239− - Document formatting (format on save)
1240− - Code actions (quick-fixes for typos, unused variables, missing attributes)
1241− - Auto-import suggestions (scans project for `@fn` definitions)
1242− - Extract selection to `@fn` refactoring
1243− - Color picker for hex colors
1244− - Code folding and document symbols
1245− - Semantic tokens for syntax highlighting
1246− - VS Code snippets for common patterns (`@fn`, `@each`, `@grid`, `@form`, etc.)
1247−
1248−## Additional directives reference
1249−
1250−The compiler recognizes a number of directives that are documented only briefly
1251−above. A concise reference:
1252−
1253−### Content generation
1254−
1255−- `@markdown` — indented body is parsed as Markdown and emitted as HTML.
1256−- `@raw """..."""` — embed verbatim HTML / CSS / JS.
1257−- `@svg path/to/icon.svg` — inline an SVG file's contents.
1258−- `@fetch url -> $name` — fetch a JSON payload at build time and bind it.
1259−- `@data name path.json` — load a JSON file for iteration with `@each`.
1260−
1261−### Metadata & head
1262−
1263−- `@lang en` — set `<html lang>`.
1264−- `@favicon /favicon.png` — inject a favicon link.
1265−- `@meta name value` — add a `<meta>` tag.
1266−- `@og key value` — add an Open Graph `<meta property="og:key">` tag.
1267−- `@canonical https://…` — set `<link rel="canonical">`.
1268−- `@base /base/path/` — set `<base href>`.
1269−- `@manifest [Name]` — emit a PWA `manifest.json` reference and body.
1270−- `@breakpoint name width` — register a custom responsive prefix.
1271−
1272−### Styling
1273−
1274−- `@style` — indented body is raw CSS injected into `<style>`.
1275−- `@scope (selector)` — wrap body in CSS `@scope` for scoped styles.
1276−- `@starting-style` — CSS entry-animation starting values.
1277−- `@font-face name url` — register a web font.
1278−- `@theme` — design tokens emitted as CSS custom properties.
1279−- `@keyframes name` — define keyframe animations.
1280−- `@css-property name syntax value` — register a typed custom property.
1281−
1282−### Control flow
1283−
1284−- `@if cond` / `@unless cond` / `@else` / `@else if`.
1285−- `@match $value` / `@switch $value` — exhaustive branching.
1286−- `@each item in list` / `@each item, $_index in list` — iterate.
1287−- `@for $i in 0..10 [step 2]` — numeric ranges.
1288−- `@repeat N` — repeat a block N times.
1289−- `@defer` — defer block rendering below the fold.
1290−
1291−### Module system
1292−
1293−- `@include file.hl` — paste file contents here.
1294−- `@import file.hl` — import @fn / @let / @define (with optional `as` alias).
1295−- `@use "file.hl" fn1, fn2` — import a named subset.
1296−- `@extends template.hl` — template inheritance; override with `@slot`.
1297−- `@layout path.hl` — wrap output in a layout template.
1298−- `@collection path/ -> $items` — iterate over a directory.
1299−
1300−### Functions & composition
1301−
1302−- `@fn name $p1 $p2` — define a function; body uses `@children` / `@slot`.
1303−- `@component name $p` — like `@fn` but emits a scoped wrapper class.
1304−- `@mixin name [...attrs]` — attribute bundle you can merge elsewhere.
1305−- `@define name [...attrs]` — named attribute preset, used as `[$name]`.
1306−- `@with $source as $alias` — temporarily rebind a variable.
1307−
1308−### Debugging
1309−
1310−- `@assert cond [message]` — emit an error if a boolean check fails.
1311−- `@warn message` / `@debug expr` / `@log message` — compile-time output.
1312−- `@deprecated name [reason]` — mark a function as deprecated.
1313−- `@translations locale` — bind i18n strings available as `$t.key`.
403+@style
404+ .note { color: gray; }
405+@el [animation fade-in 0.3s ease, class note] Fades in
406+```
407+
408+## Upgrading older files
409+
410+`htmlang upgrade [dir|file]` rewrites removed syntax:
411+
412+| Removed | Use instead |
413+|---|---|
414+| `@fn`, `@define`, `@mixin` | `@let` (function, bundle) |
415+| `@unless COND` | `@if not COND` |
416+| `@for $i in A..B`, `@repeat N` | `@each $i in A..B`, `@each $_ in 1..N` |
417+| `@switch` | `@match` |
418+| `@use "file" names` | `@import file` |
419+| `@with $x as y` | `@let y $x` |
420+| `@layout file` | `@extends file` |
421+| `@scope`, `@starting-style`, `@css-property` | The CSS rule in `@style` |
422+| `@col`, `@p`, `@img`, `@li`, `@btn`, `@ul`, `@divider`, `@opt` | `@column`, `@paragraph`, `@image`, `@item`, `@button`, `@list`, `@hr`, `@option` |
423+| `...$bundle` | `$bundle` |
424+| `animate`, `inset-area` | `animation`, `position-area` |
425+| `\|upper`, `\|lower`, `\|cap`, `\|len` | `\|uppercase`, `\|lowercase`, `\|capitalize`, `\|length` |
426+
427+The compiler reports each removed form with its replacement.