# htmlang (.hl) A minimalist layout language inspired by elm-ui that compiles to static HTML. ## Principles - `@` means structure, bare lines mean content - Layout is explicit and compositional — no CSS cascade - Every element declares its own layout role - Output is a self-contained HTML file with embedded CSS (flexbox) ## Elements | Element | Output | Purpose | |--------------|------------------|--------------------------------| | `@row` | flex row | Horizontal layout | | `@column` | flex column | Vertical layout | | `@el` | div | Generic container | | `@text` | span | Styled inline text | | `@paragraph` | p with spans | Flowing/wrapping inline text | | `@image` | img | Image | | `@link` | a | Anchor wrapping children | | `@raw` | verbatim HTML | Escape hatch | | `@nav` | nav | Navigation landmark | | `@header` | header | Page/section header | | `@footer` | footer | Page/section footer | | `@main` | main | Main content area | | `@section` | section | Thematic section | | `@article` | article | Self-contained content | | `@aside` | aside | Sidebar/tangential content | | `@list` | ul/ol | List (`[ordered]` for ol) | | `@item` | li | List item | | `@table` | table | Table | | `@thead` | thead | Table head group | | `@tbody` | tbody | Table body group | | `@tr` | tr | Table row | | `@td` | td | Table cell | | `@th` | th | Table header cell | | `@video` | video | Video element | | `@audio` | audio | Audio element | | `@form` | form | Form container | | `@details` | details | Disclosure widget | | `@summary` | summary | Summary for `@details` | | `@blockquote`| blockquote | Block quotation | | `@cite` | cite | Citation/source reference | | `@code` | code | Inline code (monospace) | | `@pre` | pre | Preformatted text | | `@hr` | hr | Horizontal rule (self-closing) | | `@figure` | figure | Figure with optional caption | | `@figcaption`| figcaption | Caption for `@figure` | | `@progress` | progress | Progress bar | | `@meter` | meter | Meter/gauge element | | `@iframe` | iframe | Embedded external page | | `@output` | output | Form calculation result | | `@canvas` | canvas | Drawing surface for scripts | | `@script` | script | JavaScript (inline or external) | | `@noscript` | noscript | Fallback when JS is disabled | | `@address` | address | Contact information | | `@search` | search | Search landmark (HTML5) | | `@breadcrumb` | nav>ol | Semantic breadcrumb navigation | Bare lines (not starting with `@` or `[`) are text nodes. ## Syntax ### Basic structure ``` @element [attributes] children ``` Children are indented under their parent. Attributes are optional, comma-separated inside `[...]`. Attribute lists can span multiple lines: ``` @el [ padding 20, background white, rounded 8, shadow 0 2px 4px rgba(0,0,0,0.1) ] Content ``` ### Full example ``` -- My Site @page My Site @let primary #3b82f6 @let gap 20 @define card [ padding 20, background white, rounded 8, border 1 #e5e7eb, shadow 0 2px 4px rgba(0,0,0,0.05) ] @include header.hl @column [max-width 800, center-x, padding 40, spacing $gap] @row [spacing $gap] @column [width fill, spacing 10] @text [bold, size 32] Welcome @paragraph [line-height 1.6] This is a page built with {@text [bold, color $primary] htmlang}. Read the {@link https://docs.example.com docs} to learn more. @image [width 80, height 80, rounded 40] avatar.png -- cards @row [wrap, spacing 10] [$card] First card [$card, background #f9fafb] Second card -- footer @row [spacing 10] @el [padding 16, background $primary, rounded 8] > @link https://example.com @text [color white] Get Started @el [width fill] @text [color #888, text-align right] © 2026 @raw """ """ ``` ## Directives ### `@page` Sets the HTML `
` with monospace font. `@pre` preserves whitespace.
```
@pre
@code console.log("hello")
```
### `@hr`
Horizontal rule / divider (self-closing). Alias: `@divider`.
```
@hr [border-top 1 #e5e7eb]
```
### `@figure` / `@figcaption`
Figure with optional caption.
```
@figure
@image [alt Sunset, width fill] sunset.jpg
@figcaption A beautiful sunset
```
### `@progress` / `@meter`
Progress bar and meter elements. Use `value`, `max`, `min` attributes.
```
@progress [value 70, max 100]
@meter [value 6, min 0, max 10, low 3, high 8]
```
## `@each` destructuring
When `@each` has more than one variable and items contain spaces, values are destructured:
```
@each $name, $url in Home /, About /about, Contact /contact
@link $url $name
```
`var()` and `calc()` expressions are also passed through as-is.
## `@each` with `@else`
When the list is empty, the `@else` block is rendered as a fallback:
```
@each $item in $items
@text $item
@else
@text [color #888] No items found.
```
## `@iframe`
Embedded external page. Argument is the `src` URL.
```
@iframe [width fill, height 400, sandbox] https://example.com
```
Attributes: `sandbox`, `allow`, `allowfullscreen`, `referrerpolicy`.
## `@canvas`
Drawing surface for JavaScript. Use with `@raw` for scripts.
```
@canvas [width 400, height 300, id chart]
```
## `@output`
Form output element for displaying calculation results.
```
@output [for a b] Result
```
## Pseudo-elements
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.
```
@el [before:content "→ ", before:color red, before:font-weight bold]
Item with arrow prefix
@el [after:content " ✓", after:color green]
Completed item
```
Content values are automatically quoted unless they are CSS keywords (`none`, `normal`) or CSS functions (`attr()`, `counter()`).
## Conditional attribute values
Use `if(condition, true_value, false_value)` inside attribute values for conditional styling:
```
@let active true
@let theme dark
@el [background if($active, blue, gray)]
Conditionally styled
@el [color if($theme == dark, white, black)]
Theme-aware text
```
Supports equality (`==`), inequality (`!=`), and truthy checks.
## Attributes reference
### Layout (set on parent)
| Attribute | Effect |
|------------------------|----------------------------------|
| `spacing N` | Gap between children |
| `gap N` | Alias for `spacing` |
| `gap-x N` | Horizontal gap (column-gap) |
| `gap-y N` | Vertical gap (row-gap) |
| `padding N` | Uniform padding |
| `padding Y X` | Vertical + horizontal padding |
| `padding T H B` | Top + horizontal + bottom |
| `padding T R B L` | Per-side padding |
| `padding-x N` | Horizontal padding |
| `padding-y N` | Vertical padding |
### Sizing (set on child)
| Attribute | Effect |
|------------------------|----------------------------------|
| `width fill` | Take remaining space (flex: 1) |
| `width N` | Exact pixels |
| `width shrink` | Fit to content (default) |
| `height fill` | Take remaining space |
| `height N` | Exact pixels |
| `height shrink` | Fit to content (default) |
| `min-width N` | Minimum width |
| `max-width N` | Maximum width |
| `min-height N` | Minimum height |
| `max-height N` | Maximum height |
### Alignment (set on child)
| Attribute | Effect |
|------------------------|----------------------------------|
| `center-x` | Center horizontally |
| `center-y` | Center vertically |
| `align-left` | Align to left |
| `align-right` | Align to right |
| `align-top` | Align to top |
| `align-bottom` | Align to bottom |
### Style
| Attribute | Effect |
|------------------------|----------------------------------|
| `background COLOR` | Background color |
| `color COLOR` | Text color |
| `border N COLOR` | Border width and color |
| `border-top N COLOR` | Top border |
| `border-bottom N COLOR`| Bottom border |
| `border-left N COLOR` | Left border |
| `border-right N COLOR` | Right border |
| `rounded N` | Border radius |
| `shadow VALUE` | Box shadow (CSS value) |
| `bold` | Bold text |
| `italic` | Italic text |
| `underline` | Underlined text |
| `size N` | Font size in px |
| `font NAME` | Font family |
| `text-align VALUE` | Text alignment (left/center/right/justify) |
| `line-height VALUE` | Line height (unitless or px) |
| `letter-spacing N` | Letter spacing |
| `text-transform VALUE` | Transform (uppercase/lowercase/capitalize) |
| `white-space VALUE` | White-space (nowrap/pre/normal) |
| `transition VALUE` | CSS transition |
| `cursor VALUE` | Cursor style |
| `opacity VALUE` | Opacity (0–1) |
| `overflow VALUE` | Overflow (hidden/scroll/auto/visible) |
| `position VALUE` | Position (relative/absolute/fixed/sticky) |
| `top N` | Top offset (positioned elements) |
| `right N` | Right offset |
| `bottom N` | Bottom offset |
| `left N` | Left offset |
| `z-index N` | Stack order |
| `display VALUE` | Display mode (none/block/flex/grid) |
| `visibility VALUE` | Visibility (visible/hidden) |
| `transform VALUE` | CSS transform (e.g., rotate(45deg)) |
| `backdrop-filter VALUE`| Backdrop filter (e.g., blur(10px)) |
### Margin
| Attribute | Effect |
|------------------------|----------------------------------|
| `margin N` | Uniform margin |
| `margin Y X` | Vertical + horizontal margin |
| `margin T R B L` | Per-side margin |
| `margin-x N` | Horizontal margin |
| `margin-y N` | Vertical margin |
### Additional Style
| Attribute | Effect |
|------------------------|----------------------------------|
| `filter VALUE` | CSS filter (blur, brightness) |
| `object-fit VALUE` | Object fit (cover/contain/fill) |
| `object-position VALUE`| Object position within container|
| `text-shadow VALUE` | Text shadow |
| `text-overflow VALUE` | Text overflow (ellipsis/clip) |
| `pointer-events VALUE` | Pointer events (none/auto) |
| `user-select VALUE` | User selection (none/text/all) |
| `justify-content VALUE`| Main axis alignment |
| `align-items VALUE` | Cross axis alignment |
| `order N` | Flex/grid item order |
| `background-size VALUE`| Background size |
| `background-position VALUE`| Background position |
| `background-repeat VALUE`| Background repeat |
| `word-break VALUE` | Word break (break-all/keep-all) |
| `overflow-wrap VALUE` | Overflow wrap (break-word) |
| `font-weight VALUE` | Font weight (100-900/bold/lighter)|
| `font-style VALUE` | Font style (normal/italic/oblique)|
| `text-wrap VALUE` | Text wrapping (balance/pretty) |
| `will-change VALUE` | Performance hint (transform) |
| `touch-action VALUE` | Touch behavior (none/pan-x) |
| `vertical-align VALUE` | Vertical alignment (middle/top) |
| `contain VALUE` | CSS containment (layout/paint) |
| `content-visibility VALUE` | Lazy rendering (auto) |
| `scroll-margin N` | Scroll margin (for anchors) |
| `scroll-padding N` | Scroll padding (scroll-snap) |
| `content VALUE` | Pseudo-element content (with before:/after:)|
### Pseudo-states
Prefix any style attribute with `hover:`, `active:`, or `focus:` to apply it on that state.
```
@el [padding 16, background #3b82f6, hover:background #2563eb, active:background #1d4ed8, rounded 8, transition all 0.15s ease]
@text [color white] Click me
```
All style attributes support state prefixes: `hover:color`, `active:rounded`, `focus:border`, etc.
### Dark mode
Prefix any style attribute with `dark:` to apply it when the user's system is in dark mode.
```
@el [background white, dark:background #1a1a2e, color #333, dark:color #eee]
@text Theme-aware content
```
This generates a `@media (prefers-color-scheme: dark)` rule.
### Print styles
Prefix any style attribute with `print:` to apply it when printing.
```
@nav [print:display none]
@text Navigation (hidden in print)
```
This generates a `@media print` rule.
### Flow & Grid
| Attribute | Effect |
|------------------------|----------------------------------|
| `wrap` | Enable flex-wrap (on `@row`) |
| `grid` | Enable CSS grid layout |
| `grid-cols N` | Grid template columns (N equal) |
| `grid-rows N` | Grid template rows (N equal) |
| `col-span N` | Span N columns in grid |
| `row-span N` | Span N rows in grid |
### Identity
| Attribute | Effect |
|------------------------|----------------------------------|
| `id NAME` | HTML id attribute |
| `class NAME` | HTML class attribute |
### Modern CSS
| Attribute | Effect |
|------------------------|----------------------------------|
| `color-scheme VALUE` | Color scheme (`light`, `dark`, `light dark`) |
| `appearance VALUE` | Form element appearance (`none`) |
### Popover API
Use HTML Popover API attributes for declarative popovers without JavaScript.
```
@button [popovertarget info-panel] Show Info
@el [popover, id info-panel, padding 20, background white, rounded 8]
@text This is a popover
```
| Attribute | Effect |
|-------------------------------|---------------------------------|
| `popover` | Make element a popover |
| `popovertarget ID` | Open popover with given ID |
| `popovertargetaction VALUE` | Action (toggle/show/hide) |
### Input hints
| Attribute | Effect |
|------------------------|----------------------------------|
| `inputmode VALUE` | Virtual keyboard type (numeric/email/search/tel/url) |
| `enterkeyhint VALUE` | Enter key label (enter/done/go/next/previous/search/send) |
| `fetchpriority VALUE` | Resource priority (high/low/auto) |
| `translate VALUE` | Whether to translate (yes/no) |
| `spellcheck VALUE` | Spell check (true/false) |
## Asset inlining
SVG images can be inlined directly into the HTML output using the `[inline]` attribute:
```
@image [inline, width 24, height 24] icon.svg
```
This reads the SVG file and embeds its content directly, keeping the output self-contained.
## CLI
```
htmlang init # scaffold a new project
htmlang init my-site # scaffold in a new directory
htmlang new about-us # create about-us.hl from template
htmlang page.hl # compile page.hl → page.html
htmlang site/ # compile all .hl files in directory
htmlang --watch page.hl # compile and watch for changes
htmlang -w page.hl # short form
htmlang --serve site/ # serve a multi-page site with hot reload
htmlang -s --open site/ # serve and open browser
htmlang check page.hl # check for errors without writing output
htmlang check src/ # check all .hl files in a directory
htmlang convert page.html # convert HTML to .hl format (stdout)
htmlang build src/ -o dist/ # parallel compile + copy static assets
htmlang --compat page.hl # compile with vendor prefixes
htmlang --format json page.hl # output diagnostics as JSON
htmlang lint --format json src/ # lint with JSON output
htmlang dead-code --format json src/ # dead code report as JSON
htmlang lint page.hl # stricter lint checks (accessibility, nesting)
htmlang lint src/ # lint all .hl files in a directory
htmlang stats page.hl # show file statistics (elements, CSS, colors)
htmlang repl # interactive REPL (type .hl, get HTML)
htmlang feed src/ # generate RSS feed from @page metadata
htmlang feed src/ -b https://mysite.com # with custom base URL
htmlang components src/ # list all @fn definitions across project
htmlang deps src/ # show dependency graph (@include/@import)
htmlang dead-code src/ # find unused @fn, @define, @let across project
htmlang deploy src/ # build and deploy to GitHub Pages
htmlang playground # generate self-contained HTML playground
htmlang clean src/ # remove generated .html files
htmlang outline page.hl # show document structure tree
```
Watch mode recompiles automatically when the source file or any `@include`d/`@import`ed files change.
The `build` command automatically extracts shared CSS rules across pages into a `shared.css` file when outputting to a directory.
## Compilation target
Each `.hl` file compiles to a single self-contained `.html` file:
- Elements map to `