windo()
windo() is the authoring entry point. You call it once per *.windo.tsx file and return a single definition object — that object is the complete description of one canvas entry: what component renders, with which props, in what variants, under which contexts, and how its state evolves.
The factory
windo() does not come from a package import. defineWindoConfig() returns it bound to your project's groups and contexts, so the group field is checked against your configured slugs and the factory argument carries your context registry. You pass windo() a function; the function receives that argument and returns a WindoDefinition.
The function body runs once, at definition time — that is where static fields like title, group, and configurableProps are read. Every function-valued field below runs later, against the live ctx. component, actions, and providers run on every render; defaultProps, props, variants, code, and state are context-aware too but resolve on their own schedule, covered under Context-aware fields below. Don't close over render-time values in the outer body.
import { windo } from '../windo.config' import { Button } from './Button' export default windo(w => ({ title: 'Button', group: w.groups.controls.slug, defaultProps: { label: 'Save', variant: 'primary' }, component: props => <Button {...props} />, }))
The two generics
windo<Props, State>(factory) carries two type parameters. Props is the component's prop type — it threads through defaultProps, variants, props, code, and component. State is the optional component-local state shape; it defaults to an empty object and types ctx.state, ctx.setState, and every WindoAction. Both are usually inferred, but you can name them when inference needs a hand — most often when a windo declares state and actions.
type ToastProps = { message: string } type ToastState = { open: boolean } export default windo<ToastProps, ToastState>(w => ({ title: 'Toast', group: w.groups.feedback.slug, state: { open: false }, actions: [{ label: 'Show', run: ctx => ctx.setState({ open: true }) }], defaultProps: { message: 'Saved' }, component: (props, ctx) => <Toast {...props} open={ctx.state.open} />, }))
WindoDefinition
The object your factory returns. Only title, group, defaultProps, and component are required; everything else is optional and unlocks one piece of canvas behaviour. Function-valued fields are marked — those receive the live ctx at render time.
| Field | Type | Description |
|---|---|---|
| title | string | Required. Display name in the sidebar and chrome. |
| group | GroupSlug | Required. The slug of one configured group. Type-checked against your config's groups — reach for it via w.groups.<name>.slug. |
| tags | Tag[] | Tags this component carries, drawn from the config's declared tags. Type-checked against that list. Drives the sidebar's tag filter; a component can carry any number. Optional. |
| component | (props: Props, ctx: WindoRenderContext<State>) => ReactNode | Required. Renders the component. Runs at render time with the merged props (defaults plus editor overrides) and the live ctx. |
| defaultProps | Ctxual<Props, State> | Required. The full prop set, including functions and JSX the schema cannot describe. May be a ctx => props function, re-resolved live on every render. The JSON editor merges its overrides on top, per key. |
| status | 'stable' | 'beta' | 'deprecated' | Maturity badge in the sidebar. Optional. |
| description | string | One-line summary shown in the chrome. Optional. |
| deprecation | string | Deprecation notice copy. Pair with status: deprecated. Optional. |
| placement | Ctxual<WindoPlacement, State> | Where the component anchors in the canvas frame, e.g. 'center', 'fill', 'top-left'. Append -padding to inset from the edges. May be a ctx => placement function, re-resolved live on every render. Optional. |
| configurableProps | z.ZodType | A zod schema for the JSON-editable subset of props. Validates and parses live edits; z.output must be a subset of Props. Optional. |
| variants | Ctxual<WindoVariant<Props>[], State> | Named prop patches ({ label, props }) shown in the gallery and click-to-apply. May be a ctx => variants function, snapshotted when the component is selected. Optional. |
| props | Ctxual<WindoPropDoc[], State> | An authored documentation table ({ name, type, default?, desc? }). Hand-written, not derived from the schema. May be a ctx => rows function, snapshotted when the component is selected. Optional. |
| code | (values: Props, ctx: WindoRenderContext<State>) => string | Builds the snippet for the Code tab from the current prop values and the live ctx. Snapshotted when the component is selected. Optional. |
| uses | string[] | Names of provider contexts this component opts into. Their providers wrap the component inside the iframe. Optional. |
| providers | ComponentType<{ children: ReactNode; ctx: WindoRenderContext<State> }> | A local provider wrapping only this windo, in addition to any uses contexts. Optional. |
| state | State | ((ctx: WindoInitContext<State>) => State) | Initial component-local state. Its shape is the State generic and seeds ctx.state. May be a function of an init ctx (no state/setState yet), resolved once when the component is selected. Optional. |
| actions | WindoAction<State>[] | Out-of-band drivers of state: button + select actions become toolbar controls, enter/exit/hover bind to the stage's pointer events. Each runs against the live ctx. Optional. |
Context-aware fields
Several fields accept their static value or a function of the live ctx, so a windo can adapt to the canvas environment — viewport, colour scheme, locale, shared state — without you maintaining one definition per case. The shared shape is Ctxual<T, State>:
type Ctxual<T, State = unknown> = T | ((ctx: WindoRenderContext<State>) => T)
defaultProps, placement, variants, and props are each Ctxual. code always takes the live ctx as a second argument: (values, ctx) => string. state is context-aware too, but resolves against a narrower WindoInitContext — the same ctx with state and setState removed, because it is defining the initial state and neither exists yet:
type WindoInitContext<State = unknown> = Omit<WindoRenderContext<State>, 'state' | 'setState'>
When each one resolves
The fields share the Ctxual shape but not the same clock — which matters the moment a resolver reads something that changes, like ctx.viewport or ctx.ctxState.
- Live, every render —
placementanddefaultProps. They re-run wheneverctxchanges (viewport, colour scheme,ctxState), so they track the environment continuously. - At selection —
variants,props, andcodeare snapshotted once when the component is selected and stay fixed until you reselect it. Later env changes do not re-run them. - Once, at selection —
stateresolves against the initctxeach time the component is selected, seedingctx.state. Nothing re-seeds it afterwards.
placement is the one to reach for when layout should follow the environment — it re-resolves on every render, so it tracks ctx as the viewport or colour scheme shifts:
export default windo<DialogProps>(w => ({ title: 'Dialog', group: w.groups.overlay.slug, placement: ctx => (ctx.viewport.name === 'mobile' ? 'fill' : 'center'), defaultProps: { title: 'Confirm' }, component: props => <Dialog {...props} />, }))
Letting ctx own a prop
Render merges props as { ...defaultProps, ...editorValues } — the Controls draft wins per key. That draft is seeded from the zod schema's static defaults, not from defaultProps. So a prop listed in both configurableProps and a ctx-aware defaultProps will always show the editor's value; the ctx-derived one never surfaces.
To make a prop genuinely ctx-driven — say a label localised from ctx.ctxState.language — keep it out of configurableProps and let defaultProps own it. Reserve configurableProps for props the user should hand-edit.
Required vs. optional, at a glance
The smallest valid windo is four fields: title, group, defaultProps, component. Each optional field maps to one surface in the chrome — configurableProps powers the live controls, variants the gallery, state and actions the toolbar, uses and providers the context wrappers, code and props the documentation tabs.