Render context
ctx is the live wire into the canvas. Every render-time function a windo declares — component, defaultProps, an action's run and disabled — receives it as its trailing argument, and through it reads the ambient environment (color scheme, viewport, motion, direction, locale), drives component-local state, and reaches the console and opted-in contexts.
Where it comes from
You never construct a WindoRenderContext. The iframe runtime builds one per render from the ambient state the chrome pushes down, then hands the same object to your component(props, ctx), to a function-form defaultProps(ctx), and to each action's run(ctx, active) / disabled(ctx). Read it at render time — never close over it in the factory body, which runs once at definition time before any ctx exists.
component: (props, ctx) => ( <Banner {...props} theme={ctx.colorScheme} compact={ctx.viewport.name === 'mobile'} /> )
Fields
The state generic threads through: ctx.state and ctx.setState are typed by the windo's State. The rest of the object is fixed.
| Field | Type | Description |
|---|---|---|
| colorScheme | 'light' | 'dark' | The active scheme, toggled from the chrome. Branch your component on it instead of reading the host page — the preview is isolated. |
| viewport | WindoViewport | The current frame size: { width, height, name } where name is 'mobile' | 'tablet' | 'desktop'. Use it to drive responsive rendering without a media query. |
| reducedMotion | boolean | True when the chrome's reduced-motion toggle is on. Gate animations on it so motion-sensitive previews stay still. |
| direction | 'ltr' | 'rtl' | Writing direction. Mirror layout and icon orientation off this to preview right-to-left rendering. |
| locale | string | The active locale string (e.g. en-US). Feed it to Intl formatters or your own i18n lookup. |
| logger | WindoLogger | Console channel for the preview. logger.log(...args) posts an entry to the chrome's Console tab — the windo runs in an iframe, so this is how output surfaces. |
| state | State | Current component-local state, seeded from the windo's state field and typed by the State generic. Read-only here — mutate through setState. |
| setState | (patch: Partial<State>) => void | Merges a partial patch into the component-local state and re-renders. This is how actions and the component itself advance state. |
| contexts | Record<string, unknown> | Resolved values of the contexts this windo opted into via uses, keyed by context name. Each value is whatever that context resolved to (its resolve return, or its control values by default). |
colorScheme, viewport, reducedMotion, direction, locale
These five are the ambient environment — the values the chrome's toolbar controls. They arrive read-only on every render, so the way to respond is to read them in component and branch. Because the preview lives in an isolated iframe, these are the source of truth for the environment; don't reach for the host document's theme or matchMedia.
component: (props, ctx) => { const price = new Intl.NumberFormat(ctx.locale, { style: 'currency', currency: 'USD', }).format(props.amount) return ( <Tag dir={ctx.direction} animate={!ctx.reducedMotion} compact={ctx.viewport.name !== 'desktop'} > {price} </Tag> ) }
logger
The preview renders inside an iframe, so a bare console.log lands in the iframe's own console, not the chrome. ctx.logger.log(...args) bridges that gap — each call posts a WindoLogEntry to the chrome's Console tab, where you read it alongside the canvas. Reach for it to trace renders, action fires, and prop changes.
component: (props, ctx) => ( <Select {...props} onChange={value => { ctx.logger.log('selected', value) ctx.setState({ value }) }} /> )
state and setState
ctx.state is the windo's component-local state — the same shape you declared in the state field, typed by the State generic. It is read-only on the ctx; to advance it, call ctx.setState(patch), which merges the partial patch and re-renders. This pair is what makes a windo interactive: the component reads ctx.state to render, and both the component and the windo's actions call ctx.setState to move it forward.
state: { open: false },
actions: [
{ label: 'Show', run: ctx => ctx.setState({ open: true }) },
{ label: 'Hide', run: ctx => ctx.setState({ open: false }), disabled: ctx => !ctx.state.open },
],
component: (props, ctx) => <Toast {...props} open={ctx.state.open} />,contexts
When a windo opts into a provider context via uses, the resolved value of each one shows up on ctx.contexts, keyed by context name. The value is whatever that context's resolve returned — or, when it has none, its raw control values. It is typed unknown, so narrow it before use.
uses: ['theme'], component: (props, ctx) => { const theme = ctx.contexts.theme as { accent: string } return <Card {...props} accent={theme.accent} /> }