Skip to content
Destyler Destyler Destyler

Controllable State

Most Destyler machines can be uncontrolled (the machine owns state) or controlled (your app owns state and syncs it back). This guide covers the end-user API.

Uncontrolled (default)

Prefer defaultOpen, defaultChecked, or defaultValue for the initial seed. The machine keeps the value afterward and still fires onOpenChange / onCheckedChange / onValueChange if you want to observe changes.

// Dialog / popover — start open once, machine owns open afterward
dialog.machine({ id: '1', defaultOpen: true })
// Checkbox — start checked, machine owns checked afterward
checkbox.machine({ id: '1', defaultChecked: true })
// Select — start with a selection, machine owns value afterward
select.machine({ id: '1', defaultValue: ['vue'] })

Controlled

Pass open / checked / value into machine(...) so prop presence marks the field as controlled (Phase 2). Keep that prop in sync when the machine requests a change via on*Change, using the adapter’s reactive context option (or setContext in lower-level adapters).

Dialog open — Vue

<script setup lang="ts">
import * as dialog from '@destyler/dialog'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, ref, useId } from 'vue'
const open = ref(false)
const [state, send] = useMachine(
dialog.machine({
id: useId(),
open: open.value,
onOpenChange(details) {
open.value = details.open
},
}),
{
context: computed(() => ({ open: open.value })),
},
)
const api = computed(() => dialog.connect(state.value, send, normalizeProps))
</script>

Dialog open — React

import * as dialog from '@destyler/dialog'
import { normalizeProps, useMachine } from '@destyler/react'
import { useId, useState } from 'react'
export function ControlledDialog() {
const [open, setOpen] = useState(false)
const [state, send] = useMachine(
dialog.machine({
id: useId(),
open,
onOpenChange(details) {
setOpen(details.open)
},
}),
{ context: { open } },
)
const api = dialog.connect(state, send, normalizeProps)
// …render with api.getTriggerProps(), etc.
}

Checkbox checked — Vue

<script setup lang="ts">
import * as checkbox from '@destyler/checkbox'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, ref, useId } from 'vue'
const checked = ref(false)
const [state, send] = useMachine(
checkbox.machine({
id: useId(),
checked: checked.value,
onCheckedChange(details) {
checked.value = details.checked
},
}),
{
context: computed(() => ({ checked: checked.value })),
},
)
const api = computed(() => checkbox.connect(state.value, send, normalizeProps))
</script>

Checkbox checked — React

import * as checkbox from '@destyler/checkbox'
import { normalizeProps, useMachine } from '@destyler/react'
import { useId, useState } from 'react'
export function ControlledCheckbox() {
const [checked, setChecked] = useState(false)
const [state, send] = useMachine(
checkbox.machine({
id: useId(),
checked,
onCheckedChange(details) {
setChecked(details.checked)
},
}),
{ context: { checked } },
)
const api = checkbox.connect(state, send, normalizeProps)
// …render with api.getRootProps(), etc.
}

Important: do not always pass value / open / checked

Phase 3 HARD — presence only

Passing open, checked, or value into machine(...) is treated as controlled. For demos and uncontrolled UIs, omit those keys and use defaultOpen / defaultChecked / defaultValue instead. Explicit *.controlled flags are removed (breaking major).

IntentPass into machine(...)
Uncontrolled seeddefaultOpen / defaultChecked / defaultValue (omit the live key)
Controlledopen / checked / value + sync via on*Change and context / setContext

Machine authors and adapter writers should read the full MACHINE-layer contract:

CONTROLLED-API.md · tracked in #103

  • Getting Started
  • Composition
  • Component pages document Controlled usage for shipping open/value machines (dialog, checkbox, popover, select, tooltip, hover-card, collapsible, menu, floating-panel, combobox, calendar, color-picker, switch, radio, tabs, collapse, toggle, slider, number-input, otp-input, pagination, steps, carousel, edit, tree, splitter, navigation-menu, and related). Examples: Dialog, Checkbox, Popover, Select, Tooltip, Tabs.