Theme
On this page (9)

§The contract

Each component instance binds a tiny API directly on the element. State lives on the element (never in module scope), so twenty components on one page can each hold a different state:

// the element - df$ selects it (get(0) = the node itself)
const el = df$('#confirm').get(0);
// set a declared state by name - throws on unknown names
el.api.setState('open', { /* optional config */ });
// read it back - always reflects the real UI
el.api.getState(); // → { name: 'open', config: { ... } }

Three guarantees, verified by the build's verifier for every component:

§Declared, not implied

Each component lists its states as names (const dialogStates = ['default', 'open']) - default first. Unknown names throw with the supported list.

§Honest reads

getState() reflects what the user sees: pressing Escape on an open dialog returns default, not the stale last setState value.

§Documented visually

Every state is a named entry in the component's skill (## States), a section on its doc page, and a real screenshot in both light and dark mode.

§Try it

The buttons below call api.setState() and print what api.getState() reads back. Closing the dialog with Escape or the backdrop flips the observed state to default on its own - the read always matches the UI.

§Drive the dialog by name

The same API keyboard users, tests and agents use: the buttons call api.setState() and print what api.getState() reads back. Close the dialog with Escape, the backdrop or its button - the named state follows the UI, so the badge flips back to default on its own.

§Discovering the states

Three places name the same list. The skill frontmatter is the machine-readable source - dist/SKILL.md indexes it, and every component's doc page shows the states with a live demo:

---
name: Dialog
type: MOL
...
supportedStates: default, open
---
## States
- `default` - closed (authored initial state)
- `open` - shown modally via showModal()
```js
df$('#confirm').get(0).api.setState('open');
```

The component scripts also register their states and a registry-level API on globalThis.df$ - useful when you have no element handle yet:

df$.shadcn.dialogStates;  // ['default', 'open']
// registry form - element passed explicitly, same contract as el.api
const { dialogApi } = df$.shadcn;
const confirm = df$('#confirm').get(0);
dialogApi.setState(confirm, 'open');
dialogApi.getState(confirm);
// → { name: 'open', config: {} }

§States across components

Every multi-state component, with its declared state names (from each skill's supportedStates). Components with no .js file are pure HTML/CSS - their only state is default, and their variants are markup attributes (Data Attribute API), not runtime states.

declared states per JS component
ComponentStates
Accordiondefault · all-open · all-closed
Alert Dialogdefault · open
Animation Canvasdefault · overview
Avatardefault · error
Border Layoutdefault · collapsed
Comboboxdefault · open
Command Palettedefault · open
Context Menudefault · open
Cookie Consentdefault · open · preferences · services
Countdowndefault · running · paused · finished
Dialogdefault · open
Diffdefault · before · after
Dropdown Menudefault · open
File Inputdefault · dragover · selected · error
Imagedefault · error
Menubardefault · open
Mermaiddefault · rendered · error
Navigation Menudefault · open
OTP Inputdefault · filled · invalid
Paneldefault · minimized · maximized
Popoverdefault · open
Presentationdefault · notes · fullscreen
Product Showcasedefault · playing
Progressdefault · indeterminate · complete
Radial Progressdefault · indeterminate · complete
Search & Filterdefault · filled · searching
Sessiondefault · detached · streaming
Sheetdefault · open
Sidebardefault · collapsed
Sliderdefault · disabled
Tabledefault · sorted · selected
Tabsdefault · active · disabled
Theme Switcherdefault · open
Toggledefault · pressed
Toggle Groupdefault · disabled
Tooltipdefault · visible
Tree Viewdefault · expanded
Typewriterdefault · paused · done
Virtual Listdefault · loading · empty
Windowdefault · maximized · minimized · closed

§Why it exists

The VAE page describes the proof loop: every declared state of every component must be visually verifiable - a screenshot per state per color scheme, driven through api.setState() by scripts/create-screenshots.ts - and asserted by an e2e test. That is only possible because the states are named, uniform, and callable from plain JavaScript, no matter what the component does internally. Your agents get the same handle: the skills name the states, the API drives them.

See also: How to Use (where the .js layer sits), Data Attribute API (markup configuration, as opposed to runtime state).

Comments, ideas or improvements? Edit this page's source on GitHub