# Sprout

A standalone design system for an LMS. Warm paper, leaf green actions, rounded controls, and small illustrated moments around learning.

Open `index.html` directly in a browser. It needs no server, build, CDN, account, font download, or network connection. Keep the folder together because the catalogue loads local CSS and JavaScript.

The folder is independent of the application. It contains no imports, tokens, fonts, images, or component code from the existing design system. The plant and notebook illustrations were drawn for this catalogue as inline SVG. The live application has not been integrated with Sprout.

## Files

| File | Purpose |
| --- | --- |
| `tokens.css` | Semantic colors, typography, spacing, corners, and motion. Light and dark modes. |
| `components.css` | Shared component styles, scoped to `.sprout` inside a CSS layer. |
| `src/` | Editable React 19 components. React is the only runtime dependency. |
| `examples/LessonCard.tsx` | A composed course card with correct link semantics. |
| `index.html` | Offline visual catalogue using the same component CSS. |
| `catalogue.css` | Catalogue layout and LMS example layouts. Not required by the React components. |
| `catalogue.js` | Local demo interactions. No data persistence. |
| `verify.mjs` | Browser regression checks against the local file, with screenshots saved to a temporary folder. |

## Use in React

Copy `src`, `tokens.css`, and `components.css` into a dedicated folder in your application, such as `src/sprout`. Import the CSS once from your application's stylesheet entry or root layout. Paths below assume the root layout is in `src/app`.

```tsx
import "../sprout/tokens.css";
import "../sprout/components.css";
import {
  Badge,
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardFooter,
  buttonProps,
} from "../sprout/src";

export function CoursePreview() {
  return (
    <section className="sprout" data-theme="light" aria-label="Course preview">
      <Card>
        <CardHeader>
          <Badge tone="lilac">Biology</Badge>
          <CardTitle>Small wonders</CardTitle>
          <CardDescription>A closer look at the living world.</CardDescription>
        </CardHeader>
        <CardFooter>
          <a {...buttonProps()} href="/lessons/1">Start learning</a>
        </CardFooter>
      </Card>
    </section>
  );
}
```

This follows [shadcn's open code and composition principles](https://ui.shadcn.com/docs). You own the component source, keep native props and refs, compose small parts, and customize semantic tokens. Sprout is not an official shadcn theme or a CLI registry. It does not require Tailwind or an existing `components.json`.

`buttonProps()` applies button styling to an anchor or your router's link component. Use `Button` for actions. This avoids nested interactive elements and a custom `asChild` implementation. Button defaults to `type="button"`; use `type="submit"` for forms.

Components accept `className`, `style`, native event handlers, ARIA attributes, and React 19 refs. For events or state in Next.js, add `"use client"` to your consuming interactive component. No provider is required. Controlled form inputs follow the browser's React API: `checked` with `onChange`, or `defaultChecked` for uncontrolled inputs.

## Component inventory

| Exports | Options and usage |
| --- | --- |
| `Button`, `buttonProps` | `variant`: default, secondary, outline, ghost, destructive. `size`: default, sm, lg, icon. Give icon-only buttons an accessible label. |
| `Badge` | `tone`: neutral, success, warning, danger, lilac, peach. Always include status text. |
| `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` | Compose only the parts you need. `CardTitle` renders an `h3`; edit its source if your page hierarchy requires another level. |
| `Field`, `Label`, `FieldDescription`, `FieldError` | Associate input IDs, labels, help text, and error text explicitly. |
| `Input`, `Textarea`, `Select` | Native elements. `Select` accepts native `option` children and uses platform keyboard behavior. |
| `Checkbox`, `Switch` | Native checkbox inputs. Switch adds `role="switch"`. Wrap them in a label with `sp-check` for a 44px target. |
| `Progress` | Requires `aria-label`. `max` defaults to 100; numbers are clamped. Omit `value` for native indeterminate progress. Show a visible count alongside it. |
| `Alert`, `AlertTitle`, `AlertDescription` | Neutral, success, warning, danger. Add `role="status"` for dynamic feedback or `role="alert"` for urgent errors. Static notices have no live role by default. |
| `Avatar` | Requires `aria-label`, renders initials or other supplied children. |
| `Skeleton`, `Separator` | Decorative loading placeholders and a semantic horizontal rule. Put loading text on the containing region. |

The native table, radio quiz, course card, and empty state in the catalogue are composition examples. They are not exported as separate React components. Dialogs, menus, comboboxes, and tabs are outside this first edition. When adding them, use maintained accessible behavior such as Radix or Base UI and apply Sprout tokens.

## Fields and errors

```tsx
<Field>
  <Label htmlFor="title">Course title</Label>
  <Input
    id="title"
    name="title"
    required
    aria-invalid={Boolean(error)}
    aria-describedby={error ? "title-hint title-error" : "title-hint"}
  />
  <FieldDescription id="title-hint">Use a name your learners will recognize.</FieldDescription>
  {error && <FieldError id="title-error">{error}</FieldError>}
</Field>
```

Provide the form state and submission handler in your application. On failed submission, focus the first invalid field. Keep help and error IDs unique when multiple forms appear on a page.

## Visual rules

- Use paper as the page background and white as the card background. Reserve leaf green for primary actions. Usually one primary action per task area is enough.
- Pair each colored background with its matching foreground token. Peach and lilac identify subjects or illustrations; neither means success or failure.
- Use Palatino for short welcome messages and editorial headings. Use Trebuchet for controls, course titles, data, and lesson text. Both use local fallback stacks, so rendering varies by operating system.
- Use the 4px spacing scale. Default to 24px within cards and 16px between related components. Keep 48px or more between major sections.
- Use 8px corners for small elements, 12px for controls, 20px for cards, and full rounding for badges. Avoid turning every label into a pill.
- Put illustrations beside empty states, course covers, or milestones. Keep timed assessments, grading tables, and long lesson text visually quiet.
- Animate direct feedback for 140ms. Honor reduced motion. Do not animate layout, timers, or correctness feedback for decoration.
- Write specific, encouraging feedback. Explain the next step after an error. Avoid shame, forced streaks, and praise that obscures assessment results.

## Themes and isolation

Set `data-theme="dark"` on the same element as `.sprout`. Theme choice belongs to the consuming app; the catalogue starts in light mode and toggles locally without storage.

All component selectors require `.sprout`, all tokens start with `--sp-`, and classes start with `sp-`. Catalogue styles also require `.catalogue`. Sprout does not install fonts, change `:root`, reset the document body, or modify another design system's tokens.

This is selector scoping, not a Shadow DOM boundary. An application's global element styles can still affect descendants. Place Sprout in a dedicated route or iframe if you need complete CSS isolation. Avoid nesting theme scopes with opposing themes. `components.css` uses a named layer so local, unlayered application styles can override it. `cn()` joins class names; it does not merge Tailwind utilities or resolve conflicting CSS declarations.

## Accessibility and validation

The catalogue includes native keyboard controls, visible focus, a skip link, explicit field labels, feedback text, progress labels, reduced-motion handling, and a searchable table with an empty state. Compact buttons retain a 44px minimum height. Text status accompanies color. Disabled examples are intentionally dimmed.

Typecheck the source from this repository without a build:

```sh
apps/web/node_modules/.bin/tsc --noEmit --strict --jsx react-jsx --target ES2022 --module ESNext --moduleResolution bundler --typeRoots ./apps/web/node_modules/@types --skipLibCheck sprout-design-system/src/index.ts sprout-design-system/examples/LessonCard.tsx
node --check sprout-design-system/catalogue.js
node sprout-design-system/verify.mjs
```

The type roots in that command only borrow installed React typings for validation. They are not source or runtime dependencies on the application's design system.

The browser checks use `/usr/bin/chromium`, or the executable path in `SPROUT_BROWSER`. They open the local HTML in a temporary browser profile without starting an application server. They cover form errors, quiz retries, progress, search, themes, reduced motion, mobile overflow, external requests, and JavaScript exceptions. The script prints the temporary screenshot directory when it finishes.

Before adopting this in a production flow, review it with your actual content and assistive technology. The demo creates no real courses, saves no notes, and sends no network requests. Reloading resets its state.
