---
prompt-id: "13"
name: Framework wrappers — CSS to React / Vue / Svelte / Web Components
status: live
mcp: none
model: Sonnet 4.6
environment: Claude.ai or Claude Code
inputs:
  - output/tokens.css
  - output/components/foundation.css
  - output/components/composite.css
  - target framework (React | Vue | Svelte | Web Components)
outputs:
  - src/components/<Component>.tsx (React)
  - src/components/<Component>.vue (Vue)
  - src/components/<Component>.svelte (Svelte)
  - src/components/<Component>.ts + .html (Web Components)
chains: [3]
prereqs: ["03", "04"]
documented-in: Ch22 — Framework Wrappers
---

# Prompt 13 — Framework wrappers

Wraps the CSS classes from `foundation.css` and `composite.css` into framework-native components
with typed prop interfaces, variant enums, accessibility annotations, and usage examples.
Closes the gap between "shipped CSS" and "shipped components."

---

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
META-HEADER — run before generating
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

STEP -2 — DETECT ENVIRONMENT
  Claude Code: write component files to src/components/
  Claude.ai:   provide files as paste-ready blocks with paths

STEP -1 — CONTEXT CHECK
  output/tokens.css                → REQUIRED
  output/components/foundation.css → REQUIRED
  output/components/composite.css  → REQUIRED
  output/chain-state.md            → read if present

STEP 0 — CONFIRM FRAMEWORK AND SCOPE

Ask the user:

  Q1 — Target framework:
    "(a) React + TypeScript   (b) Vue 3 + TypeScript
     (c) Svelte 5             (d) Web Components (vanilla)"

  Q2 — Component scope:
    "Which components do you want wrapped?
     (a) Foundation set only (Button, Input, Badge, Card, Alert,
         Dialog, Table, Form, Empty state)
     (b) Foundation + Composite
     (c) All (Foundation + Composite + any domain components)
     (d) Specific components — list them"

  Wait for answers before generating any code.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
MAIN PROMPT BODY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PROMPT 13 — FRAMEWORK WRAPPER GENERATION

YOUR ROLE
You are a component library engineer. Your CSS design system
is done — tokens.css, foundation.css, composite.css. Your job
is to write the framework layer: typed components that map
CSS class names to prop interfaces. You are NOT redesigning
anything. You are wrapping CSS in a framework-friendly API.

WRAPPER PHILOSOPHY
  — The CSS does the visual work. The component manages state
    and class composition.
  — Props map to CSS class variants. The component never
    adds inline styles.
  — One component = one responsibility. No mega-components.
  — Props are kebab-case in HTML/Vue, camelCase in TypeScript.

PATTERN FOR EVERY COMPONENT (React TypeScript example)

  Button:
    — Props: variant, size, disabled, loading, icon, fullWidth
    — Prop types are enums (not loose strings):
        type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger'
        type ButtonSize = 'sm' | 'md' | 'lg'
    — Component body: classNames assembled from props + base class
    — Loading state: disabled + spinner inside button
    — Polymorphic: accepts 'as' prop (button | a | Link)
    — Full type-safety: no 'any', no 'string' where enum fits
    — ARIA: aria-disabled when disabled, aria-busy when loading
    — Children: ReactNode (for button label + icon combination)
    — Forward ref: yes, for focus management

REACT + TYPESCRIPT — GENERATION RULES

For each component:

  1. Define Props interface with JSDoc on each prop
  2. Define variant/size enums as string literal unions
  3. Use clsx or a simple cn() helper to compose class names
  4. Forward refs with React.forwardRef<HTMLElement, Props>
  5. Spread remaining HTML attributes (buttonProps via ...rest)
  6. Export: named export + default export
  7. Add a short JSDoc above the component with example usage

File output structure:
  src/
    components/
      Button/
        Button.tsx        ← component
        Button.types.ts   ← Props interface + variant types
        index.ts          ← re-export
      Input/
        Input.tsx
        Input.types.ts
        index.ts
      ...

EXAMPLE — Button.tsx (React):

  import React from 'react';
  import { ButtonProps } from './Button.types';

  function cn(...classes: (string | undefined | false)[]) {
    return classes.filter(Boolean).join(' ');
  }

  /**
   * Primary action button. Uses the active archetype's CTA token.
   * @example <Button variant="primary" size="md">Save</Button>
   */
  export const Button = React.forwardRef<
    HTMLButtonElement,
    ButtonProps
  >(({ variant = 'primary', size = 'md', disabled, loading,
       fullWidth, className, children, ...rest }, ref) => {
    return (
      <button
        ref={ref}
        className={cn(
          'btn',
          `btn--${variant}`,
          `btn--${size}`,
          fullWidth && 'btn--full-width',
          loading && 'btn--loading',
          className,
        )}
        disabled={disabled || loading}
        aria-disabled={disabled || loading}
        aria-busy={loading}
        {...rest}
      >
        {loading && <span className="btn__spinner" aria-hidden="true" />}
        {children}
      </button>
    );
  });
  Button.displayName = 'Button';

VUE 3 + TYPESCRIPT — GENERATION RULES

  — Use <script setup lang="ts"> syntax
  — Props defined with defineProps<{...}>()
  — Emits defined with defineEmits<{...}>()
  — Class binding via :class computed object
  — Slots: default slot for content; named slots for icon
  — v-bind="$attrs" for attribute passthrough

SVELTE 5 — GENERATION RULES

  — Use rune syntax ($props(), $derived())
  — Snippet for icon slot
  — Class composited with clsx or template literal
  — Export type for props as TypeScript interface in .ts sidecar

WEB COMPONENTS — GENERATION RULES

  — Extend HTMLElement
  — Observed attributes map to reflected properties
  — Dispatch CustomEvents for interactions (change, dismiss)
  — Encapsulate styles with adopted stylesheets or
    link to external tokens.css + component.css
  — Define with customElements.define('ds-button', ButtonElement)
  — Prefix all custom element names with 'ds-'

COMPOSITE COMPONENTS
Apply the same pattern for composite components.
Focus on:
  — Modal: controlled (isOpen prop + onClose callback) vs
    uncontrolled (ref.open() / ref.close() imperative API)
  — Tabs: controlled (activeTab prop) + keyboard navigation
    wired in useEffect/onMount
  — Combobox: controlled value + onSelect callback;
    options as array of { label, value, disabled? }
  — Toast: exported useToast() hook or toast() function
    that imperatively adds to a queue

ACCESSIBILITY IN WRAPPERS
Each component wrapper handles the JS side of the a11y
annotations from foundation.css and composite.css:
  — Button: aria-disabled, aria-busy
  — Modal: aria-modal, aria-labelledby, focus trap in useEffect
  — Tabs: role="tablist", aria-selected, arrow-key handler
  — Combobox: aria-expanded, aria-activedescendant, role="listbox"
Annotate each with a comment pointing to the relevant WCAG SC.

STORYBOOK STORIES (optional — generate on request)
If user asks, also generate <Component>.stories.tsx for each
component — one story per variant, one story for each state.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
META-FOOTER
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SAVE INSTRUCTION
Write component files to: src/components/<ComponentName>/
  Claude Code: write all files directly
  Claude.ai:   provide each file as a labelled paste-ready block

CHAIN-STATE LOG
Append to output/chain-state.md:

  ## Prompt 13 — Framework wrappers
  Date: <today>
  Archetype: <confirmed archetype>
  Framework: <React | Vue | Svelte | Web Components>
  Components wrapped: <list>
  Output: src/components/

HANDOFF
  "Framework wrappers done. Your design system is now a
   fully typed component library.
   Run Prompt 11 (Drift audit) or Prompt 12 (A11y audit)
   on the generated component files to verify the wrappers
   haven't introduced any regressions."
```
