The first template owns its query inside the field’s view. Extract the field so a parent page can share that query with other output. The page will own state; the field will own its markup and input handler.
| Need | Use | Primary contract |
|---|---|---|
| Extract reusable markup | Template functions and components | Use a component when setup needs an Effect lifetime. |
| Bind an attribute, property, or boolean | Element bindings | Target the browser field the value actually owns. |
| Render changing collections | Keyed collections | Stable keys retain each item’s DOM and owned work. |
| Pass a record of element bindings | Spreads and data | Each spread entry selects its own binding kind. |
| Access a mounted element | References | Element work belongs to the mounted lifetime. |
| Choose DOM, HTML, or hydration | Rendering and hydration | Rendering and browser attachment have distinct owners. |
Make the state boundary match the reusable component
import { RefSubject } from "@typed/fx";
import { component, html } from "@typed/template";
import * as EventHandler from "@typed/template/EventHandler";
const SearchField = (query: RefSubject.RefSubject<string>) => {
const readQuery = EventHandler.make((event: Event) =>
RefSubject.set(query, (event.currentTarget as HTMLInputElement).value),
);
return html`<label>
Search terms
<input type="search" .value=${query} oninput=${readQuery} />
</label>`;
};
export const SearchPage = component(function* () {
const query = yield* RefSubject.make("");
return html`<main>
<h1>Saved articles</h1>
${SearchField(query)}
<output>Current query: ${query}</output>
</main>`;
});
The field needs no yielded setup, so a direct template function is sufficient. The parent uses
component to yield state creation and return a template. Neither adds a wrapper element around
the child’s label. The running Effect scope owns the state and subscriptions; constructing either
value starts no fiber. Mounting DOM output supplies that lifetime.
For other setup and output choices, see Component and What a template can render.
Name the browser surface you intend to change
The same state can drive different native operations. Choose syntax by the operation, rather than using one generic property record for everything:
| Desired change | Template form | What Typed owns |
|---|---|---|
| Display the query as text | ${query} | one text position or dynamic range |
| Set a control’s current edit buffer | .value=${query} | the live value property |
| Describe expansion to assistive technology | aria-expanded=${expanded} | one serialized attribute |
| Enable or disable a control | ?disabled=${disabled} | the boolean attribute’s presence |
| Run work from an input event | oninput=${handler} | a scoped native registration |
An attribute containing "false" and an absent boolean attribute mean different things. See
Attributes, properties, and boolean state for the set and clear
rules. Classes, data records, and
element references have dedicated binding forms.
Continue with Handle native events with Effect to read browser event data, then Change a keyed template collection to preserve item identity as a list changes.