@@ -35,18 +35,22 @@ Children are indented under their parent. Attributes are comma-separated inside
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
38−A comma inside `(...)` or `"..."` does not split attributes, so a font stack is
41+(`required`, `disabled`, `open`). A comma inside `(...)` or `"..."` does not
39−written `font "Inter, sans-serif"`.
42+split attributes, so a font stack is written `font "Inter, sans-serif"`.
43−Any line that doesn't start with `@` or `[` is text. Use `@text` when text needs
46+Any line that doesn't start with `@` or `[` is text. Text after an element's
44−attributes, and `{...}` to put elements inside a line of text:
47+attributes is its content, and `{...}` puts elements inside a line of text:
48 This is {@text [bold] important} and this is a {@link https://example.com link}.
51 This is {@text [bold] important} and this is a {@link https://example.com link}.
49@text [bold, size 24, color #333] Hello world
52@text [bold, size 24, color #333] Hello world
53+@section [padding 8] Text after the attributes is content too.
52### Comments
56### Comments
@@ -96,7 +100,6 @@ name.
96| `@grid` | div, grid | Grid container (use `grid-cols`) |
100| `@grid` | div, grid | Grid container (use `grid-cols`) |
97| `@stack` | div, position relative | Children layered on top of each other |
101| `@stack` | div, position relative | Children layered on top of each other |
98| `@in-front` / `@behind` | div, absolute | Overlay layers filling the parent (see below) |
102| `@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 |
103| `@text` | span | Styled inline text |
101| `@paragraph` | p | Flowing text with inline elements |
104| `@paragraph` | p | Flowing text with inline elements |
102| `@link url` | a | Link; text after the URL becomes its content |
105| `@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`,
111Content: `@list` / `@item` (`@list [ordered]` for `<ol>`), `@dl` / `@dt` /
114Content: `@list` / `@item` (`@list [ordered]` for `<ol>`), `@dl` / `@dt` /
112`@dd`, `@table` / `@thead` / `@tbody` / `@tr` / `@th` / `@td`, `@code`, `@pre`,
115`@dd`, `@table` / `@thead` / `@tbody` / `@tr` / `@th` / `@td`, `@code`, `@pre`,
113`@hr`, `@mark`, `@kbd`, `@abbr`, `@time`, `@progress`, `@meter`, `@output`,
116`@hr`, `@mark`, `@kbd`, `@abbr`, `@time`, `@progress`, `@meter`, `@output`,
114−`@canvas`, `@iframe src`, `@video`, `@audio`, `@picture` / `@source`,
117+`@canvas`, `@iframe src`, `@video src`, `@audio src`, `@picture` / `@source`,
115−`@script`, `@breadcrumb`, `@fragment` (children without a wrapper).
118+`@script`, `@fragment` (children without a wrapper).
117Form controls: `@input`, `@button`, `@select` / `@option`, `@textarea`,
120Form controls: `@input`, `@button`, `@select` / `@option`, `@textarea`,
118−`@label`, `@datalist`.
121+`@label`, `@datalist`. `@form URL` sets the form's `action`.
120−Styled components: `@badge`, `@chip`, `@tag`, `@avatar`, `@tooltip`,
124@list [ordered]
124@list [ordered]
125 @item First
125 @item First
126 @item Second
126 @item Second
127−@form [method post] /subscribe
127+@form [method=post] /subscribe
128− @label [for email] Email
128+ @label [for=email] Email
129− @input [type email, name email, id email, required]
129+ @input [type=email, name=email, id=email, required]
130− @button [type submit] Send
130+ @button [type=submit] Send
131@details [open]
131@details [open]
132 @summary Question
132 @summary Question
133 @text The answer.
133 @text The answer.
@@ -149,6 +149,35 @@ wins.
149 @el [background red, width fill, height fill]
149 @el [background red, width fill, height fill]
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.
157+| Component | Purpose |
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 |
170+@row [spacing 8, align-items center]
171+ @badge [background #ef4444, color white] 3
174+ @tooltip [tip Opens in a new tab] Help
176+ @item > @link / Home
178+@el [$skeleton, height 20, width fill]
152## Attributes
181## Attributes
154### Layout and sizing
183### Layout and sizing
@@ -156,11 +185,10 @@ wins.
156| Attribute | Effect |
185| Attribute | Effect |
158| `spacing N` | Gap between children (also `gap-x N`, `gap-y N`) |
187| `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) |
188+| `padding N` / `padding Y X` / `padding T R B L` | Padding (also `padding-x`, `padding-y`) |
160−| `margin ...` | Margin, same forms as padding |
189+| `margin ...` | Margin, same forms as padding (also `margin-x`, `margin-y`) |
161| `width fill` / `width N` / `width shrink` | Take remaining space, exact size, or fit content |
190| `width fill` / `width N` / `width shrink` | Take remaining space, exact size, or fit content |
162| `height fill` / `height N` / `height shrink` | Same for height |
191| `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 |
192| `center-x`, `center-y` | Center within the parent |
165| `align-left`, `align-right`, `align-top`, `align-bottom` | Align within the parent |
193| `align-left`, `align-right`, `align-top`, `align-bottom` | Align within the parent |
166| `wrap` | Let a row wrap |
194| `wrap` | Let a row wrap |
@@ -171,28 +199,36 @@ wins.
172| Attribute | Effect |
200| Attribute | Effect |
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 |
176| `rounded N` | Border radius |
203| `rounded N` | Border radius |
177| `shadow VALUE` | Box shadow |
204| `shadow VALUE` | Box shadow |
178| `bold`, `italic`, `underline` | Text style |
205| `bold`, `italic`, `underline` | Text style |
179| `size N` | Font size |
206| `size N` | Font size |
180| `font NAME` | Font family |
207| `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 |
183−Most CSS properties can also be written directly as attributes with the same
211+**Any standard CSS property** can also be used as an attribute, with the same
184−name and value: `opacity 0.5`, `cursor pointer`, `z-index 10`,
212+name and value: `background red`, `opacity 0.5`, `cursor pointer`,
185−`text-align center`, `position absolute`, `grid-template-areas "a b"`, and so on.
213+`z-index 10`, `margin-top 16`, `grid-template-areas "a b"`, and so on.
187−**Units.** A bare number is pixels (`padding 20` is `20px`). Values with a unit,
215+**Units.** For lengths, a bare number is pixels (`padding 20` is `20px`).
188−keywords and CSS functions are passed through: `width 50%`, `margin 0 auto`,
216+Values with a unit, keywords and CSS functions are passed through: `width 50%`,
189−`max-width min(100%, 800px)`.
217+`margin 0 auto`, `max-width min(100%, 800px)`.
191### HTML attributes
219### HTML attributes
193−`id`, `class`, and element attributes such as `href`, `type`, `name`, `value`,
221+Write HTML attributes as `key=value`: `id=main`, `class=note`, `href=/about`,
194−`placeholder`, `alt`, `for`, `required`, `disabled`, `target`, `rel`, `role`,
222+`type=email`, `alt=Logo`, `target=_blank`, `aria-label=Close menu`,
195−`tabindex`, `title`, `data-*` and `aria-*` are emitted as HTML attributes.
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:
228+@image [width=800, width 200, alt=A photo] photo.jpg
229+@select [size=4, size 18]
197### State and media prefixes
233### State and media prefixes
@@ -201,7 +237,8 @@ Prefix a style attribute to apply it conditionally:
201| Prefix | Applies |
237| Prefix | Applies |
203| `hover:`, `active:`, `focus:`, `focus-visible:`, `focus-within:`, `disabled:`, `checked:`, `visited:`, `target:`, `valid:`, `invalid:`, `empty:`, `placeholder:`, `selection:` | In that state |
239| `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 |
205| `before:`, `after:` | On the `::before` / `::after` pseudo-element (with `content`) |
242| `before:`, `after:` | On the `::before` / `::after` pseudo-element (with `content`) |
206| `has(SELECTOR):` | When the element contains a match |
243| `has(SELECTOR):` | When the element contains a match |
207| `sm:`, `md:`, `lg:`, `xl:`, `2xl:` | From that viewport width up (640–1536px) |
244| `sm:`, `md:`, `lg:`, `xl:`, `2xl:` | From that viewport width up (640–1536px) |
@@ -213,12 +250,18 @@ Prefix a style attribute to apply it conditionally:
214@el [padding 16, background #3b82f6, hover:background #2563eb, md:padding 32, dark:background #1e3a8a]
251@el [padding 16, background #3b82f6, hover:background #2563eb, md:padding 32, dark:background #1e3a8a]
215 @text [color white] Click me
252 @text [color white] Click me
253+@row [spacing 4, children:flex 1, nth:2n:background #f3f4f6]
216@el [before:content "→ ", before:color red]
256@el [before:content "→ ", before:color red]
217 Item with an arrow
257 Item with an arrow
220### Conditional attributes
260### Conditional attributes
262+`key if CONDITION` includes an attribute only when the condition holds;
263+`if(CONDITION, A, B)` picks a value.
223@let active true
266@let active true
224@el [background if($active, blue, gray), bold if $active]
267@el [background if($active, blue, gray), bold if $active]
@@ -232,9 +275,9 @@ Prefix a style attribute to apply it conditionally:
233-- A value, used as $primary
276-- A value, used as $primary
234@let primary #3b82f6
277@let primary #3b82f6
235−-- A computed value (= is optional)
278+-- A computed value: `=` makes it an expression
236@let gap = 8 * 2
279@let gap = 8 * 2
237−-- A quoted string: interpolated, never computed
280+-- A quoted string, with $variables interpolated
238@let greeting "Hello $primary"
281@let greeting "Hello $primary"
239-- An attribute bundle, used as [$card]
282-- An attribute bundle, used as [$card]
240@let card [padding 20, background white, rounded 8]
283@let card [padding 20, background white, rounded 8]
@@ -250,11 +293,28 @@ Prefix a style attribute to apply it conditionally:
250 Attributes after a bundle override it.
293 Attributes after a bundle override it.
296+A function is called like an element:
253−Function arguments are passed as attributes; parameters with `=default` may be
298+- Its parameters are passed as attributes; parameters with `=default` may be
254−omitted. `@component name $params` defines a function whose indented `@style`
299+ omitted.
255−block is scoped to its output. Inside a function, `@children` is replaced by the caller's children,
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
256−and `@slot name` by the caller's `@slot name` block (the slot's own children are
303+ caller's `@slot name` block replaces `@slot name` (the slot's own children are
257−the default).
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.
311+ .title { font-weight: bold; }
312+ @el [padding 12, rounded 6, background #eff6ff]
313+ @text [class=title] $kind
316+@note [kind Tip, padding 20] Scoped styles and forwarded attributes.
259A multi-line string uses triple quotes; its indented lines are the value:
319A multi-line string uses triple quotes; its indented lines are the value:
@@ -291,8 +351,23 @@ and for colors `lighten:N`, `darken:N`, `alpha:N`, `mix:COLOR:N`.
291### Checks
351### Checks
293`@assert CONDITION` fails the build when false. `@warn MESSAGE` emits a
353`@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.
358+Conditions and computed values (`@let x = ...`) are expressions:
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)` |
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.
297## Control flow
372## Control flow
@@ -302,16 +377,13 @@ All control flow runs at compile time.
302@let items apple, banana, cherry
377@let items apple, banana, cherry
303@let count 3
378@let count 3
305−@if $count > 2
380+@if $count > 2 and not $hidden
306 @text Many
381 @text Many
307@else if $count == 0
382@else if $count == 0
308 @text None
383 @text None
310 @text Few
385 @text Few
313− @text Nothing to show
315@each $item in $items
387@each $item in $items
316 @text $_index: $item
388 @text $_index: $item
@@ -330,12 +402,7 @@ All control flow runs at compile time.
330 @text Other
402 @text Other
333−`@defer` keeps its children hidden until they scroll near the viewport (they
334−are shown immediately when JavaScript is off).
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.
340## Files and data
407## Files and data
@@ -401,16 +468,17 @@ adds a meta tag (`og:` names become Open Graph `property` tags), and
401 to [opacity 1]
468 to [opacity 1]
403 .note { color: gray; }
470 .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
407## Upgrading older files
474## Upgrading older files
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.
411| Removed | Use instead |
479| Removed | Use instead |
413−| `@fn`, `@define`, `@mixin` | `@let` (function, bundle) |
481+| `@fn`, `@define`, `@mixin`, `@component` | `@let` (function, bundle; `@style` in a function body is scoped) |
414| `@unless COND` | `@if not COND` |
482| `@unless COND` | `@if not COND` |
415| `@for $i in A..B`, `@repeat N` | `@each $i in A..B`, `@each $_ in 1..N` |
483| `@for $i in A..B`, `@repeat N` | `@each $i in A..B`, `@each $_ in 1..N` |
416| `@switch` | `@match` |
484| `@switch` | `@match` |
@@ -418,9 +486,16 @@ adds a meta tag (`og:` names become Open Graph `property` tags), and
418| `@with $x as y` | `@let y $x` |
486| `@with $x as y` | `@let y $x` |
419| `@layout file` | `@extends file` |
487| `@layout file` | `@extends file` |
420| `@scope`, `@starting-style`, `@css-property` | The CSS rule in `@style` |
488| `@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 |
421| `@col`, `@p`, `@img`, `@li`, `@btn`, `@ul`, `@divider`, `@opt` | `@column`, `@paragraph`, `@image`, `@item`, `@button`, `@list`, `@hr`, `@option` |
493| `@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)` |
422| `...$bundle` | `$bundle` |
499| `...$bundle` | `$bundle` |
423| `animate`, `inset-area` | `animation`, `position-area` |
500| `animate`, `inset-area` | `animation`, `position-area` |
424| `\|upper`, `\|lower`, `\|cap`, `\|len` | `\|uppercase`, `\|lowercase`, `\|capitalize`, `\|length` |
501| `\|upper`, `\|lower`, `\|cap`, `\|len` | `\|uppercase`, `\|lowercase`, `\|capitalize`, `\|length` |
426−The compiler reports each removed form with its replacement.