# whynot IR — Schema > The **technology-neutral blueprint** for the whynot design language. > Part of WHYNOT-WP-0002. The IR is the pivot between the canonical React > designbook and every per-stack adapter (Lit is the reference adapter). ## Why an IR exists Claude Design's `/design-sync` produces a **React-bound** designbook. A non-React design system "has nothing for the design agent to build with." The IR breaks that binding: React stays the *authoring surface*, but the IR — committed, diffable, framework-free — is the actual contract that adapters project onto each stack. **Directionality is one-way: React → IR → stacks.** Nothing writes back to `ir/` except the extractor (`scripts/ir-extract.mjs`, T05). A change to the shared language is made in Claude Design and re-propagated; the IR is never hand-edited. ## Layout ``` ir/ SCHEMA.md ← this file (narrative spec) README.md ← the committed-blueprint decision + workflow schema/ tokens.schema.json ← JSON Schema for tokens.json (W3C DTCG) component.schema.json ← JSON Schema for each components/.json tokens.json ← all design tokens, W3C DTCG format (emitted by T05) components/.json ← one contract per component (emitted by T05) exemplars/.{png,html} ← reference render from the designbook (emitted by T05) ``` ## Tokens — `ir/tokens.json` Adopts the **W3C Design Tokens Community Group** format: every token is an object with `$value` and (optionally inherited) `$type`; groups nest; `$description` carries documentation. This is a published standard rather than a bespoke shape, so the token layer can feed Style Dictionary or any DTCG tool unchanged. ```json { "color": { "$type": "color", "ink": { "$value": "#0A0A0A", "$description": "Near-black. The only fill most of the time." }, "line": { "$value": "#E5E5E2", "$description": "Default 1px wireframe rule." } } } ``` > **Migration note.** The repo's current `tokens/*.json` use the older draft shape > (`value`/`type`, no `$` prefix). The extractor (T05) normalises them to the > `$`-prefixed DTCG shape on the way into `ir/tokens.json`. Validate with > `schema/tokens.schema.json`. ## Component contract — `ir/components/.json` Captures everything an adapter needs to scaffold a stub and detect drift, with no framework assumptions. Validate with `schema/component.schema.json`. Fields: | Field | Meaning | |---|---| | `name` | PascalCase canonical name (`Button`). | | `tag` | Advisory custom-element tag (`wn-button`). | | `group` | Designbook group (`atoms`, `chrome`, `form`). | | `description` | Purpose, from the React `.prompt.md`. | | `props[]` | Public inputs — see below. | | `slots[]` | Named/default content slots. | | `events[]` | Emitted events (e.g. `wn-dismiss`). | | `variants[]` | Variant axes — named dimensions with discrete values. | | `docsRef` | Path to source docs under `designbook/`. | | `exemplarRef` | Path to the reference render under `ir/exemplars/`. | ### The prop → attribute mapping (the crux) React props are camelCase properties; Lit/Vue/plain-HTML bind **attributes** (kebab-case). Each prop therefore records **both** identities plus a portability flag: ```json { "name": "iconEnd", // React prop, camelCase "type": "string", "attribute": "icon-end", // HTML attribute an attribute-driven adapter binds "portable": true } ``` - `attribute: false` means the prop is deliberately **not** an attribute (property-only, or non-portable). - `portable: false` marks props that don't map cleanly to an attribute — objects, render props, callbacks. **Adapters MUST surface non-portable props as drift, never silently drop them** (open risk in the workplan). Such props pair with `type` ∈ {`object`, `function`, `node`}. This mapping is exactly what the Lit elements already encode, e.g. `iconEnd: { type: String, attribute: "icon-end" }` in `src/elements/atoms.js`. ## Worked exemplar — `Button` Derived from the existing `` (`src/elements/atoms.js`) to show the target shape the React extractor must produce: ```json { "name": "Button", "tag": "wn-button", "group": "atoms", "description": "Primary action control. Renders a