Rat docs

HTML attributes

Every HTML attribute is authored with bracket syntax. attr['value'] writes a literal string, a bare attr with no brackets writes a boolean flag, and a value containing [expr] interpolates against scope at render time — the same rule used for text content. There is no allowlist; any attribute name passes through.

Quoted literal

The workhorse form

Brackets hold the attribute value verbatim. Quote it when the value is a string literal; the renderer emits the standard name-equals-value shape.

<a href['/about'] title['About this site']> About
Result

Boolean flags

No brackets, no value

Attributes without brackets render as bare flags — the HTML5 shorthand for boolean presence. Same rule for every attr that takes no value.

<input type['email'] required autocomplete['email']>
<input type['checkbox'] checked disabled>
Result

snake_case becomes kebab-case

One rule, no per-attr branching

Rat identifiers cannot carry a literal hyphen, so kebab-case HTML attributes are authored with underscores. Every underscore in the attribute name lowers to a hyphen in the rendered HTML — aria_label becomes aria-label, data_user_id becomes data-user-id, accept_charset becomes accept-charset. There is no allowlist of supported attrs; the renderer maps every name through this same path, so anything the WHATWG spec or your custom element accepts will reach the DOM.

<button aria_label['Close dialog'] data_action['close']> ×
<form accept_charset['utf-8']>
Result

Interpolated values

Bracket reads inside the quoted string

A bracket inside the attribute value resolves the expression at render time. Reads of page state — anything declared under the page block — also re-emit a reactive-attr config, so a later write re-applies the attribute without a full re-render. See Reactivity for the write side.

> server
slug: 'getting-started'

> page
count: 3

<a href['/posts/[slug]']> Read post
<button data_count['[count]'] on_click[count << count + 1]> [count] clicks
Result

Bare identifier inside brackets is a literal string

Quote to read state, not to make it a name

The bracket payload is captured as text — a bare identifier inside the brackets renders as that literal name, not a read of a page var. To pull a state value into an attribute, wrap the read in a quoted string: write value['[hue]'], not value[hue]. This is the same string-interpolation rule that text content uses; the brackets are the value, and any read inside the string happens at render time.

> page
hue: 200

<input value['[hue]'] type['number']>
Result

DOM-property attributes

text_content and inner_html lower to JS properties, not HTML attrs

A handful of attribute names map to JavaScript DOM properties instead of HTML attributes. text_content and inner_html both write the element's body, not a name-equals-value pair on the tag — at render time the value flows into the element's children, and on a later state write the runtime assigns element.textContent or element.innerHTML directly. Use text_content when the value is plain text; use inner_html when the value is trusted markup that should pass through raw. Any author-written children are replaced.

> page
greeting: 'hello, world'
markup: '<em>now</em>'

<span text_content['[greeting]']>
<span inner_html['[markup]']>
Result
hello, worldnow

Dual-write attributes

value, checked, selected, open keep both attr and live property in sync

Four attribute names dual-write: value, checked, selected, open. The HTML attribute lands on the markup for view-source and hydration; the runtime also assigns the JS property on every state change, because typing into a focused input or toggling a details element detaches the live property from the attribute. Without the dual-write a state update would be visible to view-source but invisible to the user. Authoring is the same as any other attribute — Rat picks the right write path from the resolved name.

> page
name: 'Ada'
agreed: true

<input value['[name]'] on_input[name << event.target.value]>
<input type['checkbox'] checked['[agreed]'] on_change[agreed << event.target.checked]>
Result

Event handlers

on_click and friends — same bracket shape

Event handlers are authored the same way as any other attribute, with the body holding a Rat expression instead of a string. The handler compiler routes calls to page state, services, and page-tier functions through the right dispatch path — see the Reactivity page for the full grammar.

> page
name: ''

<input value['[name]'] on_input[name << event.target.value]>
<button on_click[name << '']> Clear
Result

Next: Reactivity — turning a read into a live data-bound span, mutating state with the write arrow, and wiring up event handlers.