# Genesis Rapid — React component reference

> **Audience: AI coding assistants generating React code that uses Genesis Rapid components.**
> Two contexts produce subtly different code — read [Pick the right context](#pick-the-right-context) first.

This is a v0 quick reference. Prefer it over guessing; if a component or prop isn't listed, fall back to the rules in [Conventions](#conventions) below — the React wrappers are auto-generated and follow them strictly.

---

## Pick the right context

Before generating code, decide which target you're writing for. The two contexts use **different imports and different JSX shapes** — getting them mixed up is the most common cause of broken output.

### A. Production React app (engineers consume your output)

Code goes into the Genesis client app, which has a real bundler, type system, and React. Use the **PascalCase wrapper components**:

```tsx
import { Button, Card, TextField, type TextFieldRef } from '@genesislcap/rapid-design-system/react';

<Card>
  <TextField placeholder="Name">Name</TextField>
  <Button appearance="primary" onClick={save}>Save</Button>
</Card>
```

This is the rest of this document. Skip [Artifact preview setup](#b-artifact-preview-claude-desktop-canvas) below unless you're scaffolding a live preview.

### B. Artifact preview (Claude Desktop canvas)

Code runs in Claude's React-type artifact iframe. There's no bundler and no module resolver for npm packages — but **the same React wrappers from context A are still available**, just reached differently. Code shape stays close to production code so engineers can lift the JSX directly.

The package ships **two separate CDN bundles**. Both must be loaded for React wrappers to work:

1. **Main bundle** — `dist/foundation-rapid-cdn.iife.min.js` (or the package root URL). Web components + tokens. Defines `window.rapid`.
2. **React bundle** — `dist/foundation-rapid-cdn.react.iife.min.js`. Augments `window.rapid.react` with the PascalCase wrappers. **Requires `window.React` AND the main bundle loaded first.**

The rules:

- Use a **React-type artifact**, not an HTML artifact. The artifact runtime provides React + JSX compilation; don't load React, ReactDOM, or `@babel/standalone` yourself — they collide or get blocked by the iframe's CSP.
- **Do not use `import` for URLs** (e.g. `import 'https://esm.sh/...'`). The artifact runtime only resolves a small allowlist of bare-specifier modules; arbitrary URL imports fail with `Module not found`.
- Load **both** bundles by injecting `<script>` tags in a `useEffect`, in order, and gate the component tree on the second one finishing. `window.rapid.react` is the readiness signal. **Guard against double-injection** — React 19 strict mode runs the effect twice on mount, and you don't want two parallel chains downloading and re-executing the bundles:
  ```tsx
  import * as React from 'react';
  import { useEffect, useRef, useState } from 'react';

  const BASE = 'https://cdn.jsdelivr.net/npm/@genesislcap/foundation-rapid-cdn@latest';
  const MAIN_URL = BASE;
  const REACT_URL = `${BASE}/dist/foundation-rapid-cdn.react.iife.min.js`;

  function useRapid() {
    const [ready, setReady] = useState(
      typeof window !== 'undefined' && !!window.rapid?.react,
    );
    const startedRef = useRef(false);
    useEffect(() => {
      if (ready || startedRef.current) return;
      startedRef.current = true;

      // The rapid react bundle's IIFE reads `React` off the global scope.
      // Claude's artifact runtime provides React via `import` only (module
      // scope), so expose it on window before loading the bundle.
      if (typeof window.React === 'undefined') {
        window.React = React;
      }

      const inject = (src) =>
        new Promise((resolve) => {
          const s = document.createElement('script');
          s.src = src;
          s.onload = resolve;
          document.head.appendChild(s);
        });
      // Order matters: main bundle first (defines window.rapid),
      // then react bundle (augments window.rapid.react).
      inject(MAIN_URL).then(() => inject(REACT_URL)).then(() => setReady(true));
    }, []);
    return ready;
  }
  ```
  Use `@latest` for stable, `@alpha` for prereleases, or pin a version (`@14.430.2-FUI-2528.5`).
- After both bundles have loaded, **destructure the React wrappers off `window.rapid.react`** and use them just like in context A. Same PascalCase names, same prop shapes, same event signatures — boolean props are real booleans, events fire as typed `CustomEvent`s, refs work via `forwardRef`.
- Wrap your output in an **explicit** `<rapid-design-system-provider with-defaults>` element, **rendered at the top of the JSX tree at all times** — even during the bundle-loading phase. Don't put it behind a `if (!ready) return <Loading />;` early-return. If the provider isn't in the DOM when the bundle loads, the bundle's auto-injection creates one and moves body children into it; when React then renders its own provider, reconciliation fails with `Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node`. Always-render the provider and put the loading state *inside* it. (The provider is the underlying web component — there's no React wrapper for it.)
- JSX attribute is **`className=`** — `class=` is silently dropped by React.

Minimal artifact-preview shell:

```tsx
import * as React from 'react';
import { useEffect, useRef, useState } from 'react';

const BASE = 'https://cdn.jsdelivr.net/npm/@genesislcap/foundation-rapid-cdn@latest';

export default function App() {
  const [ready, setReady] = useState(
    typeof window !== 'undefined' && !!window.rapid?.react,
  );
  const startedRef = useRef(false);

  useEffect(() => {
    if (ready || startedRef.current) return;
    startedRef.current = true;
    if (typeof window.React === 'undefined') {
      window.React = React; // rapid react bundle reads React off the global scope
    }
    const inject = (src) =>
      new Promise((resolve) => {
        const s = document.createElement('script');
        s.src = src;
        s.onload = resolve;
        document.head.appendChild(s);
      });
    inject(BASE)
      .then(() => inject(`${BASE}/dist/foundation-rapid-cdn.react.iife.min.js`))
      .then(() => setReady(true));
  }, []);

  // Provider rendered unconditionally — see note above. Loading state lives INSIDE it.
  return (
    <rapid-design-system-provider with-defaults style={{ display: 'block', minHeight: '100vh' }}>
      {!ready ? (
        <div style={{ padding: 24 }}>Loading Genesis Rapid…</div>
      ) : (
        (() => {
          const { Button, Card } = window.rapid.react;
          return (
            <div className="app-shell" style={{ padding: 24 }}>
              <Card>
                <Button appearance="primary" onClick={() => alert('clicked')}>
                  Hello
                </Button>
              </Card>
            </div>
          );
        })()
      )}
    </rapid-design-system-provider>
  );
}
```

The output JSX (everything inside the provider) is identical in shape to what an engineer would write in a real React app — only the `useRapid()` script-injection scaffolding and the `window.rapid.react` destructure are artifact-specific. When handing code back to engineers, that scaffolding is dropped and replaced with a normal `import` statement at the top.

> If a component or capability isn't on `window.rapid.react`, fall back to the raw `<rapid-*>` web component tag (e.g. `<rapid-design-system-provider>` itself, since it has no wrapper). Boolean attrs on raw tags need `'' | undefined`, not `boolean`.

> If you only need web components (no React), load just the main bundle — the React bundle is optional. Other sandboxes (Stackblitz, browser-native ESM pages) that *do* allow URL imports can do `import 'https://esm.sh/@genesislcap/foundation-rapid-cdn@latest'` as a side-effect import — but for Claude Desktop, the script-tag injection above is the only reliable path.

---

## Setup (production app only — context A)

One-time bootstrap, done in the app entry. New page components can assume it's already in place and skip this section.

```ts
// genesis-components.ts (or equivalent app entry)
import * as rapidDesignSystem from '@genesislcap/rapid-design-system';

rapidDesignSystem.provideDesignSystem().register(rapidDesignSystem.baseComponents);
```

The app's root element sits inside a `<rapid-design-system-provider>` so design tokens (colors, spacing, typography) apply.

---

## Import statement (context A — production app)

Always:

```ts
import { Button, Card, TextField /* … */ } from '@genesislcap/rapid-design-system/react';
```

Never (in context A):
- `import { Button } from '@genesislcap/rapid-design-system'` — that's the WC export, not the React wrapper.
- `<rapid-button>` in JSX — works, but loses types and forces `as any` casts.

In context B (artifact preview) the rules invert: there are no available React wrappers, so you *must* use the raw `<rapid-*>` JSX tags. See [Pick the right context](#pick-the-right-context).

For ref types in context A, use the auto-generated `XxxRef` aliases:

```ts
import { TextField, type TextFieldRef } from '@genesislcap/rapid-design-system/react';
const ref = useRef<TextFieldRef>(null);
ref.current?.focus();
```

---

## Wrappers that don't exist (fall back to raw tags)

A handful of components don't have auto-generated React wrappers — these all use a re-export-from-`foundation-ui` pattern that the CEM tooling can't trace. Reaching for them in JSX produces React error #130 ("Element type is invalid"). Use the raw web-component tag instead:

| Component         | Use this in JSX                              |
| ----------------- | -------------------------------------------- |
| `DataGrid`        | `<rapid-data-grid …>`                        |
| `DataGridCell`    | `<rapid-data-grid-cell …>`                   |
| `DataGridRow`     | `<rapid-data-grid-row …>`                    |
| `ErrorBoundary`   | `<rapid-error-boundary>{…}</rapid-error-boundary>` |
| `FileReader`      | `<rapid-file-reader …>`                      |
| `FlexLayout`      | `<rapid-flex-layout>{…}</rapid-flex-layout>` |
| `GridLayout`      | `<rapid-grid-layout>{…}</rapid-grid-layout>` |
| `GridLayoutItem`  | `<rapid-grid-layout-item>{…}</rapid-grid-layout-item>` |

When using a raw web-component tag in JSX, boolean attrs need `checked={value || undefined}` (so React serializes the attribute as present/absent) instead of `checked={true}`.

`<rapid-design-system-provider>` is also always written as a raw tag — it's the wrapper element that scopes design tokens, not a component you'd typically import.

---

## Conventions

These rules let you predict the React API for any component without looking it up:

| Source (web component)          | React prop                          | Notes                                                                |
| ------------------------------- | ----------------------------------- | -------------------------------------------------------------------- |
| Standard DOM event `change`     | `onChange?(e: Event): void`         | Typed as `Event`, not `React.FormEvent` — bivariant.                 |
| Standard DOM event `input`      | `onInput?(e: Event): void`          | Same as above.                                                       |
| Standard DOM event `click`      | `onClick?: MouseEventHandler`       | Standard React signature.                                            |
| Custom event `value-changed`    | `onValueChanged?(e: CustomEvent)`   | kebab → `on` + PascalCase. Payload on `e.detail`.                    |
| Custom event `submit-success`   | `onSubmitSuccess?(e: CustomEvent)`  |                                                                      |
| Custom event `selection-change` | `onSelectionChange?(e: CustomEvent)`|                                                                      |
| Boolean attr `inline-calendar`  | `inlineCalendar?: boolean`          | kebab attr → camelCase boolean prop. Pass bare (`<X inlineCalendar/>`). |
| String-union attr `appearance`  | `appearance?: 'primary' \| …`        | Pass plain strings.                                                  |
| TS enum attr (e.g. `crud-menu-position`) | `crudMenuPosition?: CrudMenuPosition` | **Must** import the enum; string literals are not assignable. |
| `slot="…"`                      | `slot="…"` (unchanged)              | Use to target named slots in components like `Tabs`, `Dialog`.       |

Slots in JSX use the standard `slot="name"` attribute on the child element.

---

## Design tokens

Rapid is built on FAST design tokens, exposed as **kebab-case CSS custom properties** on the `<rapid-design-system-provider>` element. Any descendant — including any rapid component — inherits them. Reference them from `style={{ color: 'var(--accent-fill-rest)' }}` or from a CSS class.

> Tokens are **not** prefixed with `rapid-`. The `rapid-` prefix only applies to component tag names. CSS variables are the standard FAST names.

### Theme defaults

The Rapid provider ships **dark mode by default**. Colors are derived from the accent + neutral palettes; the hex values below are the seed values from which fill/foreground/hover/active variants are computed. See [Light / dark mode](#light--dark-mode) below for switching themes.

| Purpose                  | Default seed | Notes                                |
| ------------------------ | ------------ | ------------------------------------ |
| accent / primary         | `#11C9FC`    | Drives all `accent-*` tokens.         |
| neutral                  | `#7C909B`    | Drives all `neutral-*` tokens.        |
| `--error-color`          | `#F9644D`    | Genesis custom token.                |
| `--success-color`        | `#7ACC79`    | Genesis custom token.                |
| `--warning-color`        | `#FFB660`    | Genesis custom token.                |
| `--info-color`           | `#11C9FC`    | Genesis custom token.                |
| `--buy-color`            | `#7ACC79`    | Trading semantic.                    |
| `--sell-color`           | `#F14376`    | Trading semantic.                    |
| `--serious-notify-color` | `#F9644D`    | Genesis custom token.                |

### Light / dark mode

> **Default to dark mode.** Generated UI should keep the provider's default (`--base-layer-luminance: 0.23`) unless the user has explicitly asked for light mode (or for a user-controlled toggle). Don't switch to light by default, and don't add a theme toggle without being asked.

Theme is controlled by a single token: **`--base-layer-luminance`**. It's a number between 0 (black) and 1 (white) that drives every neutral surface, fill, foreground, and stroke. Set it once and the whole surface palette flips — you do **not** override each `--neutral-*` variable individually.

| Mode  | `--base-layer-luminance` | Constant                       |
| ----- | ------------------------ | ------------------------------ |
| Dark  | `0.23` (default)         | `StandardLuminance.DarkMode`   |
| Light | `1`                      | `StandardLuminance.LightMode`  |

The accent palette is luminance-independent — switching modes recolors surfaces and text, but `--accent-fill-rest` (your primary action color) stays on-brand.

> **Contrast caveat for AI-generated UI:** because the accent palette doesn't adapt, `appearance="primary-outline"` and `appearance="link"` (which use the accent color for text) can read poorly on a white background — the default accent (`#11C9FC` cyan) lacks contrast in light mode. If a design needs to support light mode, prefer `appearance="primary"` (filled — the foreground recomputes to stay readable) or `appearance="neutral-outline"` (uses the neutral palette, which does adapt) for outline-style buttons.

**Switching:** the cleanest mechanism for a light/dark toggle is to set the CSS variable on the provider (or a wrapper element) — every descendant component picks it up automatically.

```tsx
// Whole-app toggle (set on the design-system-provider or document root)
<div style={{ ['--base-layer-luminance' as any]: theme === 'light' ? 1 : 0.23 }}>
  {children}
</div>

// Or scoped to one subtree (e.g. a single panel that's always light)
<section style={{ ['--base-layer-luminance' as any]: 1 }}>
  <Card>This card and everything inside it is light-mode.</Card>
</section>
```

**Important — `withDefault()` does not work after the provider has been registered.** The Rapid provider sets its own per-element token value during construction, which shadows the global default. To change the live theme you have to set the value *on the provider element*, either by setting the CSS variable directly (above) or with `setValueFor`:

```ts
// In a real React app
import { baseLayerLuminance } from '@genesislcap/rapid-design-system';
import { StandardLuminance } from '@microsoft/fast-components';
const provider = document.querySelector('rapid-design-system-provider')!;
baseLayerLuminance.setValueFor(provider, StandardLuminance.LightMode);

// In an artifact / sandbox using the foundation-rapid-cdn bundle (no imports available)
const provider = document.querySelector('rapid-design-system-provider');
window.rapid.tokens.baseLayerLuminance.setValueFor(provider, window.rapid.StandardLuminance.LightMode);
```

`withDefault()` is only useful *before* the design system is registered (i.e. as part of app bootstrap), and even then setting the CSS variable on the provider is usually clearer.

Intermediate values (e.g. `0.5`) work too but aren't standard — they produce a mid-grey theme that the palette generator wasn't tuned for. Stick to `1` or `0.23` unless you have a specific reason.

### Surfaces, fills & strokes

These are the most common tokens you'll reach for when laying out around components.

| Token                              | What it is                                                                 |
| ---------------------------------- | -------------------------------------------------------------------------- |
| `--fill-color`                     | Page background.                                                           |
| `--neutral-layer-1` … `--neutral-layer-4` | Stacked surface layers, light → dark.                                |
| `--neutral-layer-card-container`   | Card body fill.                                                            |
| `--neutral-layer-floating`         | Dialog / menu / tooltip floating surface.                                  |
| `--accent-fill-rest`               | Primary action fill (button, switch on, etc.). Variants: `-hover`, `-active`, `-focus`. |
| `--accent-foreground-rest`         | Text/icon on neutral surfaces that should read as "primary".               |
| `--foreground-on-accent-rest`      | Text/icon to place **on top of** an `accent-fill-*` surface.               |
| `--neutral-fill-rest`              | Secondary action fill. Variants: `-hover`, `-active`, `-focus`.            |
| `--neutral-fill-input-rest`        | Input field background. Variants as above.                                 |
| `--neutral-fill-stealth-rest`      | Subtle hover-only buttons (icon buttons, table rows). Variants as above.   |
| `--neutral-foreground-rest`        | Body text.                                                                 |
| `--neutral-foreground-hint`        | Muted/secondary text.                                                      |
| `--neutral-stroke-rest`            | Borders. Variants: `-hover`, `-active`, `-focus`.                          |
| `--neutral-stroke-divider-rest`    | Divider lines (lighter than `stroke-rest`).                                |
| `--focus-stroke-outer` / `--focus-stroke-inner` | Focus ring colors.                                                |

### Sizing & spacing

These drive the geometry of every component. Override on the provider (or a wrapper element) to retune density site-wide.

| Token                       | Default | What it controls                                                  |
| --------------------------- | ------- | ----------------------------------------------------------------- |
| `--design-unit`             | `4`     | Base unit (px). Multiply by it for spacing: `padding: calc(var(--design-unit) * 2px)`. |
| `--base-height-multiplier`  | `10`    | Component height = `(base-height-multiplier + density) × design-unit`. Default control height = 40px. |
| `--density`                 | `0`     | Adjust ±1 / ±2 to shrink / grow controls without re-styling each one. |
| `--control-corner-radius`   | `4`     | Border radius (px) for buttons, fields, cards.                    |
| `--stroke-width`            | `1`     | Default border width (px).                                        |
| `--focus-stroke-width`      | `2`     | Focus ring width (px).                                            |
| `--disabled-opacity`        | `0.3`   | Opacity applied to `disabled` controls.                           |

### Typography

| Token                                   | Default                                       |
| --------------------------------------- | --------------------------------------------- |
| `--body-font`                           | `"Segoe UI", Arial, Helvetica, sans-serif`    |
| `--type-ramp-base-font-size` / `-line-height`  | `14px` / `20px` — body default          |
| `--type-ramp-minus-1-font-size` / `-line-height` | `13px` / `19px` — small text          |
| `--type-ramp-minus-2-font-size` / `-line-height` | `12px` / `18px` — captions             |
| `--type-ramp-plus-1-font-size` / `-line-height`  | `15px` / `21px`                        |
| `--type-ramp-plus-2-font-size` / `-line-height`  | `16px` / `22px`                        |
| `--type-ramp-plus-3-font-size` / `-line-height`  | `17px` / `23px`                        |
| `--type-ramp-plus-4-font-size` / `-line-height`  | `18px` / `24px` — section headings     |
| `--type-ramp-plus-5-font-size` / `-line-height`  | `19px` / `25px`                        |
| `--type-ramp-plus-6-font-size` / `-line-height`  | `20px` / `26px` — page headings        |

### Using tokens in JSX

```tsx
<Card style={{
  background: 'var(--neutral-layer-card-container)',
  padding: 'calc(var(--design-unit) * 4px)',
  borderRadius: 'calc(var(--control-corner-radius) * 1px)',
}}>
  <h2 style={{ font: 'var(--type-ramp-plus-4-font-size) var(--body-font)', color: 'var(--neutral-foreground-rest)' }}>
    Settings
  </h2>
  <p style={{ color: 'var(--neutral-foreground-hint)' }}>Choose how you'd like to be notified.</p>
</Card>
```

When a component already uses a token internally (e.g. `Button` uses `--accent-fill-*`), don't restyle it from outside — pick a different `appearance` instead.

### Overriding tokens

Two ways:

```tsx
// Per-subtree override via inline CSS variable — works everywhere
<div style={{ ['--accent-fill-rest' as any]: '#ff8800' }}>
  <Button appearance="primary">Orange primary</Button>
</div>

// Override on the provider element after registration (the common case)
// Real React app:
import { accentFillRest } from '@genesislcap/rapid-design-system';
const provider = document.querySelector('rapid-design-system-provider')!;
accentFillRest.setValueFor(provider, '#ff8800' as any);

// Artifact / CDN bundle: a focused set of tokens is exposed at window.rapid.tokens.
// Available: baseLayerLuminance, fillColor, accentPalette, neutralPalette,
// errorColor, successColor, warningColor, infoColor, buyColor, sellColor,
// seriousNotifyColor, designUnit, baseHeightMultiplier, density,
// controlCornerRadius, bodyFont. For anything outside that list, use the
// CSS-variable form above.
const provider = document.querySelector('rapid-design-system-provider');
window.rapid.tokens.errorColor.setValueFor(provider, '#ff0000' as any);
```

The CSS-variable form and `setValueFor(provider, …)` are equivalent for runtime overrides — both work *because* the provider already has its own per-element value, so global `withDefault()` is shadowed and won't take effect after registration.

---

## Buttons & actions

### `Button`
Props: `appearance?: 'primary' | 'primary-outline' | 'neutral' | 'neutral-outline' | 'link' | 'danger' | 'accent' | 'lightweight' | 'outline' | 'stealth'`, `disabled?`, all standard `<button>` attrs.

```tsx
<Button appearance="primary" onClick={handleSave}>Save</Button>
<Button appearance="primary-outline" disabled>Cancel</Button>
<Button appearance="danger" onClick={handleDelete}>Delete</Button>

// With icon in a slot
<Button appearance="primary">
  Save
  <Icon name="check" size="md" slot="end" />
</Button>
```

### `DropdownMenu`, `Menu`, `MenuItem`, `ActionsMenu`
Use for action lists. `MenuItem` accepts `onClick`; named slot `start`/`end` for icons.

---

## Form fields

### `TextField`
Props: `value?`, `placeholder?`, `disabled?`, `readonly?`, `type?` ('text' | 'email' | 'password' | 'tel' | 'url' | 'datetime-local' | …), `appearance?`, plus children for the label.

```tsx
<TextField
  value={name}
  placeholder="Enter your name"
  onChange={(e: Event) => setName((e.target as HTMLInputElement).value)}
>
  Name
</TextField>
```

### `TextArea`
Props: `value?`, `placeholder?`, `rows?`, `cols?`, `resize?` ('none' | 'vertical' | 'horizontal' | 'both'), children = label.

### `NumberField`
Props: `value?`, `min?`, `max?`, `step?`, `withFormatting?: boolean`, `placeholder?`, children = label.

```tsx
<NumberField min={0} max={100} step={1} withFormatting>
  Quantity
</NumberField>
```

### `Checkbox`, `Switch`
Boolean state. `checked?: boolean`, `onChange?(e: Event)`. Children render as the label.

```tsx
<Checkbox checked={agreed} onChange={(e) => setAgreed((e.target as HTMLInputElement).checked)}>
  I agree
</Checkbox>

<Switch checked={notifyOn} onChange={…}>Notifications</Switch>
```

### `Radio`, `RadioGroup`
```tsx
<RadioGroup orientation="horizontal" value={size} onChange={…}>
  <Radio value="s">Small</Radio>
  <Radio value="m">Medium</Radio>
  <Radio value="l">Large</Radio>
</RadioGroup>
```

### `Select` + `ListboxOption`
Note: the option component is `ListboxOption`, not `Option`.

```tsx
<Select value={pick} onChange={(e) => setPick((e.target as HTMLSelectElement).value)}>
  <ListboxOption value="a">Alpha</ListboxOption>
  <ListboxOption value="b">Beta</ListboxOption>
</Select>
```

### `Combobox`
Free-text-with-suggestions. Same option shape as `Select`.

```tsx
<Combobox position="below">
  <ListboxOption value="a">Alpha</ListboxOption>
  <ListboxOption value="b">Beta</ListboxOption>
</Combobox>
```

### `Multiselect`, `CategorizedMultiselect`
Multi-value selection. Pass `options={[…]}` (array prop, not children) and listen for `onSelectionChange`.

```tsx
<Multiselect options={[{value: 'a', text: 'Alpha'}, {value: 'b', text: 'Beta'}]}
             onSelectionChange={(e: CustomEvent) => setSelected(e.detail)} />
```

### `Slider`, `SliderLabel`
Range input. `min?`, `max?`, `step?`, `value?`, `orientation?`.

### `DatePicker`, `Calendar`
Props: `value?`, `format?`, `inlineCalendar?: boolean`, `hideWeekends?: boolean`, `label?`, fires `onValueChanged?(e: CustomEvent)` with the new date string in `e.detail`.

```tsx
<DatePicker
  value={date}
  format="yyyy-MM-dd"
  inlineCalendar
  onValueChanged={(e: CustomEvent) => setDate(String(e.detail ?? ''))}
/>
```

### `FileUpload`, `FileReader`, `UrlInput`, `SearchBar`, `SearchBarCombobox`
Specialised inputs. Same conventions: `value`/`onChange` for value, slots for icons/labels.

---

## Layout & containers

### `Card`
Generic container with elevation/border. Children are the body.

```tsx
<Card style={{ padding: 16 }}>
  <h3>Title</h3>
  <p>Body content.</p>
</Card>
```

### `Divider`
Visual separator. `orientation?: 'horizontal' | 'vertical'`.

### `Tabs`, `Tab`, `TabPanel`
Slot-driven. `Tab`s go in `slot="tab"`, panels in `slot="tabpanel"`. Order pairs them.

```tsx
<Tabs activeid="overview">
  <span slot="start">Account</span>
  <Tab slot="tab" id="overview">Overview</Tab>
  <Tab slot="tab" id="settings">Settings</Tab>
  <TabPanel slot="tabpanel">Overview content</TabPanel>
  <TabPanel slot="tabpanel">Settings content</TabPanel>
</Tabs>
```

### `Accordion`, `AccordionItem`
```tsx
<Accordion>
  <AccordionItem slot="item" id="a1" expanded>
    <span slot="heading">Section 1</span>
    <p>Content 1</p>
  </AccordionItem>
  <AccordionItem slot="item" id="a2">
    <span slot="heading">Section 2</span>
    <p>Content 2</p>
  </AccordionItem>
</Accordion>
```

### `Banner`, `Breadcrumb`/`BreadcrumbItem`, `Toolbar`, `SectionNavigator`, `Stepper`/`StepperTab`/`StepperTabPanel`
Containers for navigation/announcement patterns. `BreadcrumbItem` accepts `href`. `Stepper` follows the same slot pattern as `Tabs` (`slot="tab"`, `slot="tabpanel"`).

### `FlexLayout`, `GridLayout`, `GridLayoutItem`, `HorizontalScroll`, `Disclosure`
Layout primitives. Use `FlexLayout`/`GridLayout` for app shells; `Disclosure` for show/hide chunks.

---

## Feedback

### `Badge`, `StatusPill`
Inline labels. Children are the text.

```tsx
<Badge>New</Badge>
<StatusPill>Active</StatusPill>
```

### `Progress`, `ProgressRing`, `Skeleton`
`Progress`/`ProgressRing` have `value?: number` (0–100) and `min`/`max`. `Skeleton` for loading placeholders.

### `Snackbar`, `Toast`
Notifications. `Toast` has `notify?: 'info' | 'warning' | 'success' | 'critical'`. Typically toggled imperatively via a ref or a singleton helper — see existing code for the project's convention.

### `Tooltip`
Anchored to another element by id.

```tsx
<Button id="save-btn">Save</Button>
<Tooltip anchor="save-btn">Saves the form</Tooltip>
```

### `Avatar`, `Icon`, `StackingIcons`, `ConnectionIndicator`, `EnvironmentIndicator`
Visual indicators. `Icon` props: `name?: string`, `size?: '1x' | '2x' | … | 'sm' | 'md' | 'lg'`, `variant?: 'regular' | 'solid' | 'brand' | …`.

```tsx
<Icon name="check" size="md" />
<Icon name="github" size="2x" variant="brand" />
```

---

## Overlays

`Dialog` and `Modal` are imperative — open with `ref.current.show()`, close with `ref.current.close()`. Slots: `top` (header), default (body), `bottom` (footer / actions).

### `Dialog`
```tsx
const dialogRef = useRef<DialogRef>(null);

<>
  <Button onClick={() => dialogRef.current?.show()}>Open</Button>
  <Dialog ref={dialogRef} type="default">
    <div slot="top">Confirm action</div>
    <p>Are you sure you want to continue?</p>
    <Button slot="bottom" appearance="primary" onClick={() => dialogRef.current?.close()}>
      Confirm
    </Button>
  </Dialog>
</>
```

`type?: 'default' | 'error' | 'success'`. Width via CSS custom property: `style={{ '--dialog-max-width': '600px' }}`.

### `Modal`
Like `Dialog` but draggable; supports `draggable?: boolean`.

### `Flyout`
Edge-anchored panel. Useful for filter drawers, side menus.

---

## Navigation

### `Anchor`
Renders a styled `<a>`. Props: `href`, `target`, `appearance?`.

### `Menu`, `MenuItem`, `DropdownMenu`, `ActionsMenu`
Click-to-open menus. `DropdownMenu` is the typical "select an option from a button" pattern; `ActionsMenu` is the "..." overflow menu with named actions.

### `TreeView`, `TreeItem`
Hierarchical lists. `TreeItem` accepts children (nested items) and `expanded?: boolean`.

### `SegmentedControl`, `SegmentedItem`
Toggle group. Same shape as `RadioGroup` but visually a single pill.

```tsx
<SegmentedControl value="day">
  <SegmentedItem value="day">Day</SegmentedItem>
  <SegmentedItem value="week">Week</SegmentedItem>
  <SegmentedItem value="month">Month</SegmentedItem>
</SegmentedControl>
```

### `Listbox`, `ListboxOption`
Standalone list selection (without the `Select` chrome). Same option shape.

### `Optgroup`
Groups `ListboxOption`s inside `Select` / `Listbox`. Has a `label`.

---

## Refs & imperative APIs

Every wrapper is `React.forwardRef`. Use the generated `XxxRef` type:

```ts
import { TextField, type TextFieldRef } from '@genesislcap/rapid-design-system/react';

const ref = useRef<TextFieldRef>(null);
ref.current?.focus();
const value = (ref.current as any)?.value;
```

Common imperative methods: `.show()` / `.close()` (Dialog, Modal), `.focus()` / `.blur()` (form fields), `.value` (form fields).

---

## Patterns

**Controlled form field:**
```tsx
const [name, setName] = useState('');
<TextField
  value={name}
  onChange={(e: Event) => setName((e.target as HTMLInputElement).value)}
>
  Name
</TextField>
```

**Submit + success/failure events from a connected form (e.g. EntityManagement):**
```tsx
<EntityManagement
  resourceName="ALL_USERS"
  onSubmitSuccess={(e: CustomEvent) => toast.show('Saved')}
  onSubmitFailure={(e: CustomEvent) => toast.show(`Error: ${e.detail?.message}`, 'critical')}
/>
```

**Open a dialog from a list-row action:**
```tsx
const [editing, setEditing] = useState<User | null>(null);
const dialog = useRef<DialogRef>(null);

const onEdit = (user: User) => { setEditing(user); dialog.current?.show(); };

<Dialog ref={dialog}>
  <div slot="top">Edit user</div>
  {editing && <UserForm user={editing} />}
  <Button slot="bottom" onClick={() => dialog.current?.close()}>Done</Button>
</Dialog>
```

---

## Common pitfalls

Fixes for mistakes seen in real generated output. Avoid these by default.

### Both contexts

- **`class=` instead of `className=` in JSX.** React silently drops `class`. None of your CSS rules will apply, and the page renders as an unstyled void on a dark background. Always `className=`.
- **Don't manually call `provideDesignSystem().register(...)` in a page or component.** Registration is a one-time, app-bootstrap concern (context A) or done by the CDN bundle's side-effect import (context B). Re-registering is at best a no-op; at worst it produces "Cannot redefine custom element" errors.
- **Don't create a second `<rapid-design-system-provider>`.** The app already has one. Nesting another inside it (especially without `with-defaults`) wraps your subtree in an *unthemed* provider so design tokens never apply.
- **`primary-outline` and `link` button appearances read poorly on light backgrounds.** The accent palette is luminance-independent, so the cyan accent doesn't darken in light mode. If light mode is supported, prefer `appearance="primary"` (filled) or `appearance="neutral-outline"` (uses the neutral palette, which adapts).
- **Use `setValueFor(provider, …)`, not `withDefault(...)`, to override design tokens at runtime.** The provider element has its own per-element token values, which shadow global defaults.

### Context A only (production app)

- **Don't import from `@genesislcap/rapid-design-system`** for components — that's the web-component export and you'll lose types/refs. Always import from `/react`.
- **Don't import React wrappers as default exports.** Every wrapper is a *named* export.

### Context B only (artifact preview)

- **Never use HTML-type artifacts with `<script type="text/babel">`.** Babel-standalone needs `'unsafe-eval'` and inline-script CSP allowances that Claude Desktop's iframe doesn't grant. The compiled script silently fails to inject and you get a black screen with no console errors. Use a React-type artifact instead.
- **Don't load React, ReactDOM, or `@babel/standalone` yourself.** The artifact runtime already provides React; user-loaded copies collide.
- **Don't `import 'https://esm.sh/...'` (or any URL).** The artifact runtime only resolves a small bare-specifier allowlist; URL imports fail with `Module not found`. Inject a `<script>` tag at runtime instead — see [the artifact-preview setup](#b-artifact-preview-claude-desktop-canvas).
- **Use the React wrappers from `window.rapid.react`, not raw `<rapid-*>` JSX tags**, for everything except the design-system-provider itself. The wrappers behave identically to the production-app import (`import { Button } from '@genesislcap/rapid-design-system/react'`), so the artifact code stays close in shape to what engineers ship.
- **Always declare an explicit provider wrapping your component tree, rendered unconditionally** (don't gate it behind an `if (!ready)` early-return). Relying on the bundle's auto-injection (which moves body children at `DOMContentLoaded`) races with React mounting and produces a `Failed to execute 'removeChild' on 'Node'` reconciliation error. Render the provider always, put the loading state inside it.
- **Only fall back to raw web component tags when no wrapper exists.** The provider (`<rapid-design-system-provider>`) is the main case. For raw tags, boolean attrs need `'' | undefined`, not `boolean` (`disabled={count <= 0 ? '' : undefined}`).

---

## When this reference is silent

If you need a component or prop not listed here:

1. The full export list is the same as the names in the [Components by category](#buttons--actions) sections — there are 66 React wrappers in total. Anything from the `baseComponents` set has a wrapper.
2. To predict the prop API for an unlisted component, apply the [Conventions](#conventions) rules to its web component attribute names — that's exactly how the wrappers are generated.
3. For complex components (`GridPro`, `EntityManagement`, `Scheduler`), consult the project's existing usage rather than guessing — they have non-trivial setup (datasources, options) that this reference doesn't cover.
