Nested components and scope
Components can nest. A reference such as #session.user or a handler name such as logout resolves in this order:
- loop variables of the surrounding
repeator<repeat-list>, - the component whose markup contains the reference (its owner),
- each parent component, from the nearest outwards,
- for handler names only: globals, such as
consoleorhistory.
Content belongs to its owner
Content you place inside a child component is still yours. Here #todo.remaining is written in todo-app's markup, so it reads todo-app's state, even though it is displayed inside ui-card:
<custom-element name="todo-app">
<script>
const todo = useState({
items: [],
get remaining() {
return this.items.filter((item) => !item.done).length;
},
});
</script>
<ui-card>
<ui-card-header>
<ui-card-title>Today</ui-card-title>
</ui-card-header>
<ui-card-content><slot name="#todo.remaining"></slot> left</ui-card-content>
</ui-card>
</custom-element>This is standard shadow DOM. The value slot lives in todo-app's shadow root and is projected into ui-card through ui-card's own <slot>.
Parents fill in missing names
When the owner does not define a name, REALM looks in the component that contains it, then that component's parent, and so on. A deeply nested component can read shared values without passing them through every level:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nested components and scope 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/button.html"></import-element>
<import-element from="./app-shell.html"></import-element>
<import-element from="./nav-bar.html"></import-element>
<app-shell></app-shell>
</body>
</html><custom-element name="app-shell">
<script>
const session = useState({ user: "Ana" });
function signOut() {
session.user = "";
}
</script>
<style>
:host {
display: grid;
gap: calc(var(--spacing) * 2);
}
</style>
<nav-bar></nav-bar>
</custom-element><custom-element name="nav-bar">
<style>
:host {
display: flex;
gap: calc(var(--spacing) * 3);
align-items: center;
}
</style>
<span>Signed in as <slot name="#session.user">nobody</slot></span>
<ui-button size="sm" variant="outline" onclick="signOut()">Sign out</ui-button>
</custom-element>nav-bar has no session and no signOut, so both resolve to app-shell. REALM logs a one-time console warning for each name read from a parent (<nav-bar> reads "session" from <app-shell>.), so you can see where implicit sharing happens.
Rules:
- A component's own names always win over a parent's.
- A name that no component defines logs a warning and shows the slot's fallback content.
- The lookup follows the composed tree, so it works through shadow roots and slots.
Pass data explicitly for reusable components
Parent lookup is convenient inside one app. For components you reuse elsewhere, make the contract explicit with props and events:
<user-menu _user="#session.user" onsignout="signOut()"></user-menu>Objects and arrays passed with _ bindings arrive as properties, intact. The child declares them with defineProps and emits events with dispatch or dispatchEvent.
App-wide state
State that several unrelated components need, or that should outlive a component, belongs in a store.