@@ -35,18 +35,22 @@ Children are indented under their parent. Attributes are comma-separated inside
3535 Content
3636```
3737
38−A comma inside `(...)` or `"..."` does not split attributes, so a font stack is
39−written `font "Inter, sans-serif"`.
38+An attribute is either a **style**, written `key value` (`padding 20`,
39+`color red`), or an **HTML attribute**, written `key=value` (`id=main`,
40+`type=email`, `aria-label=Close`). Boolean HTML attributes are written bare
41+(`required`, `disabled`, `open`). A comma inside `(...)` or `"..."` does not
42+split attributes, so a font stack is written `font "Inter, sans-serif"`.
4043
4144### Text
4245
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:
46+Any line that doesn't start with `@` or `[` is text. Text after an element's
47+attributes is its content, and `{...}` puts elements inside a line of text:
4548
4649```
4750@paragraph
4851 This is {@text [bold] important} and this is a {@link https://example.com link}.
4952@text [bold, size 24, color #333] Hello world
53+@section [padding 8] Text after the attributes is content too.
5054```
5155
5256### Comments
@@ -96,7 +100,6 @@ name.
96100| `@grid` | div, grid | Grid container (use `grid-cols`) |
97101| `@stack` | div, position relative | Children layered on top of each other |
98102| `@in-front` / `@behind` | div, absolute | Overlay layers filling the parent (see below) |
99−| `@spacer` | div, flex 1 | Pushes siblings apart |
100103| `@text` | span | Styled inline text |
101104| `@paragraph` | p | Flowing text with inline elements |
102105| `@link url` | a | Link; text after the URL becomes its content |
@@ -111,23 +114,20 @@ Semantic containers, all laid out as columns: `@nav`, `@header`, `@footer`,
111114Content: `@list` / `@item` (`@list [ordered]` for `<ol>`), `@dl` / `@dt` /
112115`@dd`, `@table` / `@thead` / `@tbody` / `@tr` / `@th` / `@td`, `@code`, `@pre`,
113116`@hr`, `@mark`, `@kbd`, `@abbr`, `@time`, `@progress`, `@meter`, `@output`,
114−`@canvas`, `@iframe src`, `@video`, `@audio`, `@picture` / `@source`,
115−`@script`, `@breadcrumb`, `@fragment` (children without a wrapper).
117+`@canvas`, `@iframe src`, `@video src`, `@audio src`, `@picture` / `@source`,
118+`@script`, `@fragment` (children without a wrapper).
116119
117120Form controls: `@input`, `@button`, `@select` / `@option`, `@textarea`,
118−`@label`, `@datalist`.
119−
120−Styled components: `@badge`, `@chip`, `@tag`, `@avatar`, `@tooltip`,
121−`@carousel`.
121+`@label`, `@datalist`. `@form URL` sets the form's `action`.
122122
123123```
124124@list [ordered]
125125 @item First
126126 @item Second
127−@form [method post] /subscribe
128− @label [for email] Email
129− @input [type email, name email, id email, required]
130− @button [type submit] Send
127+@form [method=post] /subscribe
128+ @label [for=email] Email
129+ @input [type=email, name=email, id=email, required]
130+ @button [type=submit] Send
131131@details [open]
132132 @summary Question
133133 @text The answer.
@@ -149,6 +149,35 @@ wins.
149149 @el [background red, width fill, height fill]
150150```
151151
152+### Standard library
153+
154+These components are written in htmlang (`std.hl`) and available in every file.
155+Use them like elements; your own `@let` with the same name takes precedence.
156+
157+| Component | Purpose |
158+|---|---|
159+| `@badge` | Small pill for counts and statuses |
160+| `@tag` | Label with slightly rounded corners |
161+| `@chip` | Outlined, fully rounded label |
162+| `@avatar` | Circular frame for an image or initials |
163+| `@spacer` | Takes up the remaining space in a row or column |
164+| `@tooltip [tip TEXT]` | Text that shows `TEXT` when hovered |
165+| `@carousel` | Horizontally scrolling row that snaps to each child |
166+| `@breadcrumb` | Breadcrumb trail of `@item`s |
167+| `$skeleton`, `$no-scrollbar` | Attribute bundles: loading placeholder, hidden scrollbars |
168+
169+```
170+@row [spacing 8, align-items center]
171+ @badge [background #ef4444, color white] 3
172+ @chip Rust
173+ @spacer
174+ @tooltip [tip Opens in a new tab] Help
175+@breadcrumb
176+ @item > @link / Home
177+ @item Docs
178+@el [$skeleton, height 20, width fill]
179+```
180+
152181## Attributes
153182
154183### Layout and sizing
@@ -156,11 +185,10 @@ wins.
156185| Attribute | Effect |
157186|---|---|
158187| `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 |
188+| `padding N` / `padding Y X` / `padding T R B L` | Padding (also `padding-x`, `padding-y`) |
189+| `margin ...` | Margin, same forms as padding (also `margin-x`, `margin-y`) |
161190| `width fill` / `width N` / `width shrink` | Take remaining space, exact size, or fit content |
162191| `height fill` / `height N` / `height shrink` | Same for height |
163−| `min-width`, `max-width`, `min-height`, `max-height` | Size limits |
164192| `center-x`, `center-y` | Center within the parent |
165193| `align-left`, `align-right`, `align-top`, `align-bottom` | Align within the parent |
166194| `wrap` | Let a row wrap |
@@ -171,28 +199,36 @@ wins.
171199
172200| Attribute | Effect |
173201|---|---|
174−| `background COLOR`, `color COLOR` | Colors |
175−| `border N COLOR` (also `border-top` etc.) | Border |
202+| `border N COLOR` (also `border-top` etc.) | Solid border |
176203| `rounded N` | Border radius |
177204| `shadow VALUE` | Box shadow |
178205| `bold`, `italic`, `underline` | Text style |
179206| `size N` | Font size |
180207| `font NAME` | Font family |
181−| `transition VALUE`, `animation VALUE` | Motion |
208+| `truncate`, `line-clamp N` | Cut text off with an ellipsis |
209+| `blur N`, `backdrop-blur N` | Blur filters |
182210
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.
211+**Any standard CSS property** can also be used as an attribute, with the same
212+name and value: `background red`, `opacity 0.5`, `cursor pointer`,
213+`z-index 10`, `margin-top 16`, `grid-template-areas "a b"`, and so on.
186214
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)`.
215+**Units.** For lengths, a bare number is pixels (`padding 20` is `20px`).
216+Values with a unit, keywords and CSS functions are passed through: `width 50%`,
217+`margin 0 auto`, `max-width min(100%, 800px)`.
190218
191219### HTML attributes
192220
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.
221+Write HTML attributes as `key=value`: `id=main`, `class=note`, `href=/about`,
222+`type=email`, `alt=Logo`, `target=_blank`, `aria-label=Close menu`,
223+`data-id=42`. Any name works. Booleans are written bare: `required`,
224+`disabled`, `checked`, `open`, `sandbox`. A style and an HTML attribute may
225+share a name without clashing:
226+
227+```
228+@image [width=800, width 200, alt=A photo] photo.jpg
229+@select [size=4, size 18]
230+ @option One
231+```
196232
197233### State and media prefixes
198234
@@ -201,7 +237,8 @@ Prefix a style attribute to apply it conditionally:
201237| Prefix | Applies |
202238|---|---|
203239| `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 |
240+| `first:`, `last:`, `odd:`, `even:`, `nth:EXPR:` | By position among siblings |
241+| `children:` | To each direct child |
205242| `before:`, `after:` | On the `::before` / `::after` pseudo-element (with `content`) |
206243| `has(SELECTOR):` | When the element contains a match |
207244| `sm:`, `md:`, `lg:`, `xl:`, `2xl:` | From that viewport width up (640–1536px) |
@@ -213,12 +250,18 @@ Prefix a style attribute to apply it conditionally:
213250```
214251@el [padding 16, background #3b82f6, hover:background #2563eb, md:padding 32, dark:background #1e3a8a]
215252 @text [color white] Click me
253+@row [spacing 4, children:flex 1, nth:2n:background #f3f4f6]
254+ @el A
255+ @el B
216256@el [before:content "→ ", before:color red]
217257 Item with an arrow
218258```
219259
220260### Conditional attributes
221261
262+`key if CONDITION` includes an attribute only when the condition holds;
263+`if(CONDITION, A, B)` picks a value.
264+
222265```
223266@let active true
224267@el [background if($active, blue, gray), bold if $active]
@@ -232,9 +275,9 @@ Prefix a style attribute to apply it conditionally:
232275```
233276-- A value, used as $primary
234277@let primary #3b82f6
235−-- A computed value (= is optional)
278+-- A computed value: `=` makes it an expression
236279@let gap = 8 * 2
237−-- A quoted string: interpolated, never computed
280+-- A quoted string, with $variables interpolated
238281@let greeting "Hello $primary"
239282-- An attribute bundle, used as [$card]
240283@let card [padding 20, background white, rounded 8]
@@ -250,11 +293,28 @@ Prefix a style attribute to apply it conditionally:
250293 Attributes after a bundle override it.
251294```
252295
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).
296+A function is called like an element:
297+
298+- Its parameters are passed as attributes; parameters with `=default` may be
299+ omitted.
300+- Other attributes style its root element, so `@panel [title Hi, padding 40]`
301+ works like styling a built-in element.
302+- Text after the attributes and indented children replace `@children`, and a
303+ caller's `@slot name` block replaces `@slot name` (the slot's own children are
304+ the default).
305+- An `@style` block at the top of the body is scoped to the function: its rules
306+ only apply inside the function's output.
307+
308+```
309+@let note $kind=info
310+ @style
311+ .title { font-weight: bold; }
312+ @el [padding 12, rounded 6, background #eff6ff]
313+ @text [class=title] $kind
314+ @children
315+
316+@note [kind Tip, padding 20] Scoped styles and forwarded attributes.
317+```
258318
259319A multi-line string uses triple quotes; its indented lines are the value:
260320
@@ -291,8 +351,23 @@ and for colors `lighten:N`, `darken:N`, `alpha:N`, `mix:COLOR:N`.
291351### Checks
292352
293353`@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.
354+warning. `@deprecated MESSAGE` before a function warns every caller.
355+
356+## Expressions
357+
358+Conditions and computed values (`@let x = ...`) are expressions:
359+
360+| | |
361+|---|---|
362+| Values | numbers, `"strings"` (with `$var` interpolation), `$variables` (with filters), `true`, `false`, and bare words (`dark`, `#fff`) as strings |
363+| Arithmetic | `+ - * / %` with the usual precedence, unary `-`, `( )` |
364+| Text | `~` joins two values: `$first ~ " " ~ $last` |
365+| Comparison | `== != < > <= >=` (numeric when both sides are numbers), `contains`, `starts-with`, `ends-with` |
366+| Logic | `and`, `or`, `not`; empty, `false` and `0` are false |
367+| Choice | `if(CONDITION, A, B)` |
368+
369+Variables are looked up while evaluating, so a value containing `==` or spaces
370+is still one value. An invalid expression is a compile error.
296371
297372## Control flow
298373
@@ -302,16 +377,13 @@ All control flow runs at compile time.
302377@let items apple, banana, cherry
303378@let count 3
304379
305−@if $count > 2
380+@if $count > 2 and not $hidden
306381 @text Many
307382@else if $count == 0
308383 @text None
309384@else
310385 @text Few
311386
312−@if not $count
313− @text Nothing to show
314−
315387@each $item in $items
316388 @text $_index: $item
317389@else
@@ -330,12 +402,7 @@ All control flow runs at compile time.
330402 @text Other
331403```
332404
333−`@defer` keeps its children hidden until they scroll near the viewport (they
334−are shown immediately when JavaScript is off).
335−
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.
405+A range counts down when its start is greater than its end.
339406
340407## Files and data
341408
@@ -401,16 +468,17 @@ adds a meta tag (`og:` names become Open Graph `property` tags), and
401468 to [opacity 1]
402469@style
403470 .note { color: gray; }
404−@el [animation fade-in 0.3s ease, class note] Fades in
471+@el [animation fade-in 0.3s ease, class=note] Fades in
405472```
406473
407474## Upgrading older files
408475
409−`htmlang upgrade [dir|file]` rewrites removed syntax:
476+`htmlang upgrade [dir|file]` rewrites removed syntax; the compiler reports each
477+removed form with its replacement.
410478
411479| Removed | Use instead |
412480|---|---|
413−| `@fn`, `@define`, `@mixin` | `@let` (function, bundle) |
481+| `@fn`, `@define`, `@mixin`, `@component` | `@let` (function, bundle; `@style` in a function body is scoped) |
414482| `@unless COND` | `@if not COND` |
415483| `@for $i in A..B`, `@repeat N` | `@each $i in A..B`, `@each $_ in 1..N` |
416484| `@switch` | `@match` |
@@ -418,9 +486,16 @@ adds a meta tag (`og:` names become Open Graph `property` tags), and
418486| `@with $x as y` | `@let y $x` |
419487| `@layout file` | `@extends file` |
420488| `@scope`, `@starting-style`, `@css-property` | The CSS rule in `@style` |
489+| `@lang`, `@favicon`, `@canonical`, `@base` | `@page [lang ..., favicon ..., canonical ..., base ...] Title` |
490+| `@og KEY VALUE` | `@meta og:KEY VALUE` |
491+| `@debug`, `@log` | `@warn` |
492+| `@defer` | Its content, directly |
421493| `@col`, `@p`, `@img`, `@li`, `@btn`, `@ul`, `@divider`, `@opt` | `@column`, `@paragraph`, `@image`, `@item`, `@button`, `@list`, `@hr`, `@option` |
494+| `type email`, `id main` (HTML attributes written as styles) | `type=email`, `id=main` |
495+| `skeleton`, `no-scrollbar`, `gradient A B` | `$skeleton`, `$no-scrollbar`, `background linear-gradient(A, B)` |
496+| `@tooltip TEXT` | `@tooltip [tip TEXT] TEXT` |
497+| `@let x $a + 1` (computed without `=`) | `@let x = $a + 1` |
498+| `COND ? A : B` | `if(COND, A, B)` |
422499| `...$bundle` | `$bundle` |
423500| `animate`, `inset-area` | `animation`, `position-area` |
424501| `\|upper`, `\|lower`, `\|cap`, `\|len` | `\|uppercase`, `\|lowercase`, `\|capitalize`, `\|length` |
425−
426−The compiler reports each removed form with its replacement.