Programmatic Control
In some cases, you may want to control the state or context values of a machine
programmatically via its props
or based on certain conditions. This is
typically known as "controlling" the components.
Zag provides a number of ways to control the state of a machine programmatically.
Setting initial context
All machines support setting the controlled and uncontrolled values for context properties. For example:
defaultOpen
andopen
for controlling the open state of disclosure componentsdefaultValue
andvalue
for controlling the value of input machines
For example, if you want an accordion to start with a specific selected value. Here's how to achieve that:
const service = useMachine(accordion.machine, { defaultValue: ["item-1"], })
Controlled Usage
You can pass the context value to the useMachine
hook directly and provide the
onValueChange
callback to react to the changes.
const service = useMachine(accordion.machine, { value: props.value, onValueChange(details) { console.log(details) }, })
Reactive Context
Different frameworks handle reactivity differently when working with Zag machines. Here's how to ensure your machine context stays reactive to prop changes.
React
React props are natively reactive with Zag machines. Simply pass them directly
to useMachine
function Checkbox(props) { const service = useMachine(checkbox.machine, { checked: props.checked, onCheckedChange: props.onCheckedChange, }) const api = checkbox.connect(service, normalizeProps) return <label {...api.getRootProps()}>Toggle</label> }
Vue
Use a computed ref or a function that returns the context to make it reactive
<script setup lang="ts"> import { computed } from "vue" const service = useMachine( checkbox.machine, computed(() => ({ checked: props.checked, onCheckedChange: props.onCheckedChange, })), ) </script>
Solid
Use a function that returns the context to make it reactive
function Checkbox(props) { const service = useMachine(checkbox.machine, () => ({ checked: props.checked, onCheckedChange: props.onCheckedChange, })) const api = createMemo(() => checkbox.connect(service, normalizeProps)) return <label {...api().getRootProps()}>Toggle</label> }
Svelte
To make props reactive in Svelte, pass a function that returns the context
<script lang="ts"> let checked = $state(machineProps.checked ?? false) const service = useMachine(checkbox.machine, () => ({ ...machineProps, checked, onCheckedChange(details) { checked = details.checked machineProps.onCheckedChange?.(details) }, })) const api = $derived(checkbox.connect(service, normalizeProps)) </script> <label {...api.getRootProps()}>Toggle</label>
Why this works:
- Reactive Context: The function re-evaluates whenever dependencies change, ensuring the machine always has current prop values
- State Preservation: The machine's internal state remains intact while context gets updated
- Framework Integration: This pattern aligns with the framework's reactivity model
This approach ensures single-directional data flow while maintaining reactivity.
Using API methods
The connect
method of the machines provide helpful methods (APIs) to change
the machine state or update its context.
This approach is the recommended approach to programmatically update a machine.
Let's say we'd like to change the expanded accordion item in an accordion group. Here's how to do that:
Edit this page on GitHubfunction Accordion() { // 1. Bind the machine in your framework const service = useMachine(accordion.machine) // 2. Call the connect function const api = accordion.connect(service) // 3. Use exposed methods api.setValue("item-1") return (...) }