windo/API/Config helpers
API ReferenceAuthoring

Config helpers

Three helpers shape every windo project. defineWindoConfig registers your groups and contexts and hands back a windo factory that already knows your slugs. defineContext describes an ambient or provider context. configurableProps binds a zod schema to a component's props so the editor's live controls stay type-safe.

Imports

All three are named exports of the package root.

TSimports.ts
import { defineWindoConfig, defineContext, configurableProps } from '@westopp/windo'

defineWindoConfig

The single entry point for a project. You pass it your groups (and optionally contexts), and it returns two things: the config object the runtime reads, and a windo factory you re-export and call from every *.windo.tsx file. Splitting it this way is what makes authoring type-safe — because windo is born from your config, a windo's group field is checked against the exact slugs you registered, and w.contexts inside the factory is typed to the contexts you defined.

TS
function defineWindoConfig(config: WindoConfig): {
config: WindoConfig
windo: <Props, State>(factory: (w) => WindoDefinition<Props, State>) => WindoModule<Props, State>
}

The argument is the WindoConfig object:

OptionTypeDescription
groupsWindoGroup[]Required. The groups your sidebar is organised into. Each windo references one by slug. Declared const, so the slugs become a literal union the windo factory checks against.
contextsRecord<string, WindoContextDefinition>Optional. Named contexts available to components — ambient controls, providers, or both. Build each with defineContext.
tagsstring[]Optional. The tag vocabulary a windo may draw from. Where a group places a component in one section, tags are free-form labels — a component carries any number — and the sidebar gains a filter to narrow by them. Declared const, so a windo's tags are type-checked against this list.
includestring | string[]Optional. Glob(s) for discovery, relative to project root. Default: **/*.windo.tsx.
titlestringOptional. The project title shown in the workbench chrome.

Each entry in groups is a WindoGroup: a name (the label), a slug (what windos reference), and an optional description.

FieldTypeDescription
namestringRequired. Human-readable group label shown in the sidebar.
slugstringRequired. Stable identifier a windo names in its group field.
descriptionstringOptional. Short blurb for the group.

Tags

Groups answer "where does this live?" — one section per component. tags answer "what is this for?" — a component carries as many as apply, and the sidebar grows a filter that narrows the library to the tags you pick (match any of them, or all). Declare the vocabulary here once; a windo's tags field is then checked against it, the same way group is checked against your slugs. Leave tags out and the filter simply never appears.

Re-export windo and config from the same module so your windo files import a single, fully-typed factory.

TSwindo.config.ts
import { defineWindoConfig, defineContext } from '@westopp/windo'

const result = defineWindoConfig({
title: 'Meridian',
groups: [
  { name: 'Actions', slug: 'actions' },
  { name: 'Forms', slug: 'forms' },
  { name: 'Data display', slug: 'data-display' },
],
tags: ['web-app', 'admin-panel', 'marketing'],
contexts: {
  theme: defineContext({
    label: 'Theme',
    controls: {
      mode: { type: 'enum', options: ['light', 'dark'], default: 'light' },
    },
  }),
},
})

export const windo = result.windo
export const config = result.config

defineContext

Describes a named context. A context can do either or both of two jobs: contribute ambient controls (values plus the UI toggles that drive them, available to every windo for free) and mount a provider (a React wrapper placed around windos that opt in via uses). defineContext exists so the control map's value types flow through to the provider props and the resolve argument without a cast.

FieldTypeDescription
labelstringOptional. Display name in the chrome's Context panel.
descriptionstringOptional. Short explanation shown alongside the context.
controlsWindoControlMapOptional. Ambient controls keyed by name. Each is a WindoControlSpec — type ("enum" | "boolean" | "string" | "number"), default, and optional label/options/min/max/step. No opt-in needed; the toggles render in the chrome.
providerComponentType<{ children; values; ctx }>Optional. A React wrapper mounted inside the iframe around windos that list this context in uses. Receives the resolved control values and the live render context.
resolve(values, ctx) => ProvidedOptional. Derives the value exposed on ctx.contexts[name]. Defaults to the raw control values.
TS
function defineContext<C extends WindoControlMap, Provided = WindoControlValues<C>>(def: {
label?: string
description?: string
controls?: C
provider?: ComponentType<{ children: ReactNode; values: WindoControlValues<C>; ctx: WindoRenderContext }>
resolve?: (values: WindoControlValues<C>, ctx: WindoRenderContext) => Provided
}): WindoContextDefinition

A theme context that both exposes a mode toggle and provides it to opted-in components:

TSXtheme.context.tsx
import { defineContext } from '@westopp/windo'

export const theme = defineContext({
label: 'Theme',
controls: {
  mode: { type: 'enum', label: 'Color mode', options: ['light', 'dark'], default: 'light' },
},
provider: ({ children, values }) => (
  <div data-theme={values.mode}>{children}</div>
),
})

configurableProps

Binds a zod schema to a component's prop type. It's a curried helper: the first call takes the component's Props as a type argument, the second takes the schema. The type parameter constrains the schema so its output stays a subset of the props — the schema's z.input is the JSON the editor lets you edit, and its z.output is what the component receives after parsing. Pass the result straight to a windo's configurableProps field.

TS
function configurableProps<P>(): <S extends z.ZodType<Partial<P>>>(schema: S) => S

The schema describes the live-editable subset of a component's props. Props that take functions or JSX stay in defaultProps; everything the editor should expose as a control goes here.

TSXbadge.windo.tsx
import { configurableProps } from '@westopp/windo'
import { z } from 'zod'
import type { BadgeProps } from './components/Badge'

const schema = configurableProps<BadgeProps>()(
z.object({
  children: z.string().default('Active'),
  variant: z.enum(['neutral', 'brand', 'info', 'success', 'warning', 'danger']).default('success'),
  dot: z.boolean().default(true),
  outline: z.boolean().default(false),
})
)