_ bindings
_ followed by an attribute name binds that attribute to a reference. The binding is one-way: it follows the value whenever it changes. Used on any element in component markup.
<element _name="#ref"></element>
<element _name="!#ref"></element>
<element _--custom-property="#ref"></element><a _href="#link.url">Open</a>
<section _hidden="!#panel.open">…</section>
<button _disabled="@disabled">Save</button>
<input _value="#form.email">
<user-card _user="#session.user"></user-card>
<div _--progress="#upload.percent"></div>The _name attribute itself is removed when the markup is processed. The value must be a reference. Anything else logs a warning and the binding is skipped.
How values are applied
The first matching rule wins:
| Target | Value | Result |
|---|---|---|
_--name |
null, undefined, false |
style.removeProperty("--name") |
_--name |
anything else | style.setProperty("--name", String(value)) |
value on <progress> or <meter> |
null, undefined |
Attribute removed, so the element stays indeterminate |
value, checked, selected, indeterminate on an element with that property |
any | Sets the property. value gets value ?? "". The others get Boolean(value) |
| Any attribute of a custom element (tag with a dash) | object or array | Sets the camelCase property (_user-data sets userData), so the object arrives intact |
aria-*, draggable, spellcheck, contenteditable |
true, false |
Attribute set to "true" or "false" |
| Any attribute | true |
Attribute set to "" |
| Any attribute | false, null, undefined |
Attribute removed |
| Any attribute | anything else | Attribute set to String(value) |
On SVG elements, the attribute name's case is restored for camelCase SVG attributes such as viewBox, preserveAspectRatio, gradientTransform, stdDeviation and textLength, so _viewbox and _viewBox both work.
Changes come back through events
Bindings never write back to state. Update state from a handler:
<input _value="#todo.draft" oninput="todo.draft = this.value">class
_class replaces the whole class attribute. To toggle one style, bind a data attribute and select it in CSS (_data-active="#tab.active"), or use a reactive :state().
Example
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>_ bindings demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
<import-element from="./volume-meter.html"></import-element>
<volume-meter></volume-meter>
</body>
</html><custom-element name="volume-meter">
<script>
const level = useState({
value: 40,
get loud() {
return this.value > 70;
},
});
</script>
<style>
:host {
display: grid;
gap: calc(var(--spacing) * 2);
width: min(calc(var(--spacing) * 80), 100%);
}
.bar {
height: calc(var(--spacing) * 2);
width: calc(var(--level) * 1%);
border-radius: var(--radius-full);
background: var(--primary);
}
.bar[data-loud] {
background: var(--destructive);
}
</style>
<input type="range" min="0" max="100" aria-label="Volume" _value="#level.value" oninput="level.value = Number(this.value)">
<div class="bar" _--level="#level.value" _data-loud="#level.loud" _aria-label="#level.value" role="img"></div>
</custom-element>