Component script
The first <script> inside a <custom-element> is the component script. It is plain JavaScript in strict mode.
<custom-element name="todo-app">
<script type="realm">
const props = defineProps({ title: prop.string().default("Todo") });
const todo = useState({ items: [], draft: "" });
function add() {
todo.items.push({ id: crypto.randomUUID(), title: todo.draft });
todo.draft = "";
}
observe(() => {
document.title = `${todo.items.length} · ${props.title}`;
});
</script>
…
</custom-element>How it runs
- Once per element, when the element is first connected, before its markup is created.
- Top-level
const,let,var,functionandclassdeclarations (including destructured names) and imported names are exposed: markup reads them with#name.pathand handlers call them by name. refsis empty while the top level runs. UseonConnectedorobservefor work that needs the markup.- Leading
importstatements are hoisted and the script runs as a module. Relative specifiers resolve against the file that holds the definition. - An error thrown at the top level is logged, and the markup still renders.
Helpers in scope
These names are available without importing. They are not exposed to markup unless you assign them to a top-level name.
| Group | Helpers |
|---|---|
| Props and state | defineProps, prop, useState, observe, tick, defineStore |
| Element | host, refs, signal, internals, attrs, useChildren, defineMethods, dispatch, listen, lookup |
| Timers | setTimeout, setInterval, clearTimeout, clearInterval |
| Lifecycle | onConnected, onDisconnected, onFormReset, onFormDisabled, onFormStateRestore |
Signatures
defineProps<T extends Record<string, PropType>>(schema: T): Record<keyof T, unknown>
useState<T extends object>(initial: T): T
observe(fn: () => unknown): () => void
tick(): Promise<void>
defineStore<T extends object>(name: string, initial: T, options?: StoreOptions): T
useChildren(selector: string, attributes?: string[]): Element[]
defineMethods(methods: object): void
dispatch(type: string, detail?: unknown, options?: CustomEventInit): boolean
listen(type: string, handler: (event: Event) => unknown, target?: EventTarget): void
lookup(name: string): unknown
onConnected(fn: () => unknown): void
onDisconnected(fn: () => void): void
onFormReset(fn: () => void): void
onFormDisabled(fn: (disabled: boolean) => void): void
onFormStateRestore(fn: (state: unknown, mode: string) => void): void
setTimeout(callback: (...args: unknown[]) => void, delay?: number, ...args: unknown[]): number
setInterval(callback: (...args: unknown[]) => void, delay?: number, ...args: unknown[]): number
clearTimeout(id?: number): void
clearInterval(id?: number): void
attrs: Record<string, string | null>
host: HTMLElement
refs: Record<string, Element>
signal: AbortSignal
internals: ElementInternals
prop: { string, number, boolean, enum, date, array, object }The Context type exported by @realmorg/realm describes this object.
Order
Write scripts in this order, so they read the same way across components:
definePropsuseState(anddefineStore)- functions
defineMethods,listen, lifecycle hooksobserve