Values and bindings
Markup never contains expressions. It contains references to values from the script or props, and REALM keeps them up to date.
References
| Reference | Reads |
|---|---|
#todo.draft |
draft on the top-level script name todo (state, a store, a getter, any top-level const, let, function or import) |
#item.title |
A loop variable inside repeat or <repeat-list> |
#todo.items.length |
Any property path, including length |
@heading |
The prop heading from defineProps |
!#panel.open |
The negation of a reference. ! is the only operator |
A reference is resolved in the component whose markup contains it first, then in its parent components. See Nested components and scope. An unknown name logs a warning and shows the slot's fallback content.
Only reactive values update. A top-level const filters = [...] can be referenced, but changes to a plain array are not seen. Put changing values in useState.
Showing values: slots
A <slot> whose name starts with # or @ shows that value as its content:
<p>Hello, <slot name="@name"></slot></p>
<p><slot name="#todo.remaining"></slot> left</p>
<p><slot name="#user.name">Guest</slot></p>- The value is shown as text. Numbers,
0andfalseare shown as they are. - While the value is
"",nullorundefined, the slot shows its own children,Guestabove. This is the native fallback content of<slot>. - The slot stays a
<slot>element inside the shadow root. Nothing is added outside the component. - Slots without
#or@, such as<slot>and<slot name="footer">, are ordinary native slots for content placed inside the component.
HTML values
Add html to show the value as HTML. It is sanitised first: <script>, <style>, <iframe>, <object>, <embed>, <link>, <meta>, <base>, <template> and <noscript> are removed, along with every on* attribute, srcdoc, and javascript:, vbscript: and data:text/html URLs.
<div><slot name="#post.body" html></slot></div>html-unsafe inserts the value without sanitising. Only use it for HTML you produced yourself. Inserted HTML is never scanned for handlers, references or bindings.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Values and 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="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/textarea.html"></import-element>
<import-element from="./html-preview.html"></import-element>
<html-preview></html-preview>
</body>
</html><custom-element name="html-preview">
<script>
const post = useState({
body: '<p>Some <strong>bold</strong> text and <a href="javascript:alert(1)" onclick="alert(2)">a link</a>.</p><script>alert(3)<\/script>',
});
function edit(field) {
post.body = field.value;
}
</script>
<style>
:host {
display: grid;
gap: calc(var(--spacing) * 3);
width: min(calc(var(--spacing) * 120), 100%);
}
.output {
padding: calc(var(--spacing) * 3);
border: 1px solid var(--border);
border-radius: var(--radius-md);
}
</style>
<ui-textarea aria-label="HTML source" _value="#post.body" oninput="edit(this)"></ui-textarea>
<div class="output"><slot name="#post.body" html></slot></div>
</custom-element>The javascript: URL, the onclick attribute and the <script> never reach the page.
Values in SVG
A value slot inside <svg> is replaced with a text node, so <text><slot name="#chart.label"></slot></text> works.
Binding attributes: the _ prefix
_ followed by an attribute name binds that attribute to a reference:
<a _href="#link.url">Open</a>
<section _hidden="!#panel.open">…</section>
<button _disabled="@disabled">Save</button>
<li _data-done="#item.done" _aria-selected="#item.selected">…</li>
<input _value="#form.email">
<user-card _user="#session.user"></user-card>
<div _--progress="#upload.percent"></div>How each value is applied:
| Target | Rule |
|---|---|
| Any attribute | true adds it with an empty value, false, null and undefined remove it, anything else is written as text |
aria-*, draggable, spellcheck, contenteditable |
Booleans are written as "true" and "false" |
value, checked, selected, indeterminate on elements that have that property |
Sets the property, so the field's current value changes, not only its default |
| A custom element (any tag with a dash) with an object or array value | Sets the property (_user-data sets userData), so the object arrives intact |
--name |
Sets a CSS custom property on the element's inline style |
| SVG attributes | Case is restored for attributes such as viewBox |
Changes come back through events
Bindings are one-way. To update state from a field, handle its native event:
<input _value="#todo.draft" oninput="updateDraft(this)">function updateDraft(field) {
todo.draft = field.value;
}There is no hidden two-way binding, so it is always clear where a value is written.
The class attribute
_class="#theme.className" replaces the whole class value. To toggle a single class, prefer a data attribute (_data-active="#tab.active") and select it in CSS, or use a reactive CSS state.
References in other attributes
The same reference syntax is used by:
<is-visible when>and<is-hidden when>: Control flowrepeatand<repeat-list of>: Lists and keysinto,busy,headersandparams: Fetching data
ref="name" is not a reference. It registers the element in the script's refs object.