Tags
marko-zag ships four tags, auto-discovered from the package’s marko.json
taglib — no imports needed in .marko files. They compose in a fixed order;
see The Three-Tag Pattern for the full worked
example.
<machine-props>
Builds the machine-props closure for <service> from a component’s input:
picks the machine-owned props by name, injects a stable generated id
(overridable via input.id), and merges every other attribute written on
the tag — callback adaptations and inline overrides alike.
<machine-props/machineProps from=input pick=switchMachine.props
onCheckedChange(details) {
input.checkedChange?.(details.checked);
}/>
Input
| Attribute | Type | Description |
|---|---|---|
from |
Record<string, any> |
The component’s full input object. |
pick |
readonly string[] |
Machine prop names — the machine module’s exported props array (e.g. switchMachine.props). |
| anything else | any |
Callback adaptations and machine-prop overrides, merged last. |
Returns
A closure () => ({ id, ...picked, ...overrides }).
Why a name array, not a splitter function?
pick=takes plain strings
because they serialize. Passing a split function would hit Marko’s
tag-input serialization wall. The callback adaptation stays in your
component file on purpose — it is the component’s public contract.
<service>
Creates and owns a running Zag service — the Marko analog of
useMachine(machine, props).
<service/service machine=() => switchMachine.machine props=machineProps/>
Input
| Attribute | Type | Description |
|---|---|---|
machine |
() => any |
Machine getter — always a closure written in your template (() => switchMachine.machine), never the raw machine (unserializable). |
props |
() => Record<string, any> |
The closure from <machine-props>. Optional. |
Returns
A serializable ServiceHandle:
| Field | Type | Description |
|---|---|---|
service |
MarkoService | null |
Running service on the client; null during SSR. |
machine |
() => any |
Forwarded getter, for <connect>'s SSR fallback. |
props |
() => Record<string, any> |
Forwarded props closure. |
rev |
number |
Update counter — fresh handle identity per machine update. |
The tag tracks every reactive read inside your props closure: controlled
props re-notify the machine with no hand-typed dependency list.
<connect>
Derives the connected API from a <service> handle — the Marko analog of
connect(service, normalizeProps). Returns the API as a getter you call
at use sites.
<connect/api=(service, normalizeProps) =>
switchMachine.connect(service, normalizeProps)
service=switchService
/>
<label ...api().getRootProps()>
Input
| Attribute | Type | Description |
|---|---|---|
| (value shorthand) | (service, normalizeProps) => Api |
The connect closure — written in your template (raw switchMachine.connect as input is unserializable). |
service |
ServiceHandle |
Handle returned by <service>. |
During SSR the handle carries no running service, so a throwaway
never-started one is built from the handle’s machine/props; Zag’s
connect() is a pure read, so this renders correct initial attributes.
Several <connect>s may share one <service>.
<portal>
SSR-safe portal: renders content inline on the server (inside a
display: contents host), then reparents the host to the target on mount.
Marko tracks nodes by reference, so moved nodes keep working.
<portal>
<if=api().open>
<div ...api().getBackdropProps()/>
...
</if>
</portal>
Input
| Attribute | Type | Description |
|---|---|---|
to |
string |
CSS selector for the portal target. Defaults to document.body. |
disabled |
boolean |
Disable reparenting (render in place). |
content |
Marko.Body |
The portalled content (body content works too). |
On destroy the tag removes the host manually — required, not defensive:
Marko’s own removal walks the original parent chain, which no longer
contains the reparented host.