# E.Wright — Porcelain & Paper

Version 1.0.0 · September 21, 2026

Two production-oriented visual systems with one semantic component contract. The kit contains CSS, native HTML patterns, ES-module behaviors, tokens, documentation and runnable Node tests. It has no runtime package dependencies. The reference uses local sample data; real authentication, validation, uploads and persistence remain application responsibilities.

## Choose a material

- **Porcelain**: product interfaces, operational tasks and polished prototypes. White ceramic surfaces, navy text, soft olive emphasis, 10px control corners and ambient shadows. Light appearance only.
- **Paper**: wireframes, structure studies and annotated reviews. Neutral raised sheets, grain, directional shadows and 24px control corners.
- **Paper / dusk**: dimmer neutral Paper, still a light appearance. It is not an inverted dark mode. Do not map the operating system's dark-mode preference to this appearance automatically.

Layouts and interactions are shared. Use `data-ew-theme="porcelain"`, `paper`, or `paper-dusk`. Set the attribute before first paint. All themes expose exactly the same CSS custom properties.

## Quick start

Copy the kit into a directory served by your application. A local HTTP server is required for ES modules and the reference token fetch. No install or build is needed to consume the generated styles.

```html
<html lang="en" data-ew-theme="porcelain">
<link rel="stylesheet" href="./tokens.css">
<link rel="stylesheet" href="./components.css">
<body class="ew-system">
  <button type="button" class="ew-button" data-variant="primary">Continue</button>
  <script type="module">
    import { mount } from './components.mjs';
    const cleanup = mount(document);
    // Call cleanup() before unmounting this application island.
  </script>
</body>
```

`starter.html` is a minimal working example. `reference.html` is the full interactive catalog. The included Inter font is optional; system-font fallbacks are declared. `FONT-LICENSE.txt` accompanies the font. No remote font or analytics dependency is required by the kit.

## Files and ownership

| File | Responsibility |
| --- | --- |
| `tokens.json` | Canonical theme/foundation definitions; format `ewright-tokens/1` with CSS-ready values |
| `build.mjs` | Generates `tokens.css` and `legacy-tokens.css`; Node 22+ |
| `tokens.css` | All public `--ew-*` custom properties |
| `components.css` | Scoped visual primitives and their states |
| `components.mjs` | Tabs, dialog triggers, popovers, clipboard and dismissible notifications |
| `component-manifest.json` | Component selectors, anatomy, states and keyboard contracts |
| `model.mjs` | Contrast, pagination and local sample-form validation helpers |
| `reference.html`, `.css`, `.mjs` | Documentation and local demonstrations |
| `starter.html` | Minimal consumer example |
| `system.test.mjs` | Token, contrast, validation and navigation tests |
| `legacy-tokens.css` | Palette bridge for existing portfolio study selectors |

`reference.mjs` is documentation code, not an application service. Do not use its sample claim validation to infer business eligibility or approval. The JSON format is explicitly this kit's format, not a claim of DTCG schema compatibility.

## Foundations

Body: 16px / 1.65. Controls and labels: 14px / 1.45. Secondary captions only: 12px. Heading scale: 20, 24, 32, 44px. Use Inter 400, 500 and 600. Dimensions use rem, preserving browser font preferences. Readable line length: 65ch.

Spacing uses a 4px rhythm: 4, 8, 12, 16, 20, 24, 32, 40, 48, 64 and 80px. Use 8–12px within controls, 16–24px within groups and 32–64px between sections. Layout breakpoints are 40rem and 60rem; 80rem is the wide content limit. Components use minmax(0, 1fr), wrapping labels and container queries when available. Attachment actions stack below details when their container is below 30rem; this is also the fallback in browsers without container queries.

Colors are semantic: canvas, surface, raised/sunken surface, control, text, text-muted, action, focus and status pairs. `border` separates decorative groups. `border-control` identifies controls and passes the stronger non-text contrast threshold. Do not use decorative borders as the sole input boundary.

Elevation: flat, control, panel, overlay. Porcelain shadows are ambient; Paper shadows are directional. Grain is decorative and never a substitute for a visible boundary. Large opaque panels use 24px corners; smaller structural elements use 8px; badges use a pill.

Icons use a 24-unit grid, 20px display size, 1.8-unit strokes, round caps and joins. Label every icon-only control. Set decorative SVGs `aria-hidden="true"`; avoid redundant title announcements when visible text names the control.

Motion: 120ms for feedback, 180ms for controls, 240ms for surface changes, easing cubic-bezier(.2,0,0,1). Reduced motion disables transitions and repeating animation. Avoid motion as the only explanation of a state change.

## Component API

All components live within `.ew-system`; theme variables may be scoped to a nested `data-ew-theme` container. A nested `.ew-system` makes its canvas explicit. Avoid mounting the same behavior layer twice on overlapping roots.

### Buttons, links and state

`ew-button` uses `data-variant="primary|quiet|danger"`; omit for secondary. `data-size="large"` gives a 52px target; default is at least 44px. Use real buttons for actions and anchors with href for navigation. Loading uses a stable text label plus `aria-busy="true"` and `disabled`. A spinner is decorative. Explain disabled states nearby; do not rely on a tooltip on an unfocusable disabled button.

### Forms

Use `.ew-field` with a visible `<label for>` and unique input ID. Inputs, select and textarea use `.ew-input`. Hints use `.ew-hint`; error text uses `.ew-error`. Connect both with space-separated IDs in `aria-describedby`. Apply `aria-invalid="true"` after validation. Do not use placeholders as labels.

```html
<div class="ew-field">
  <label for="invoice">Invoice number (required)</label>
  <input class="ew-input" id="invoice" required
         aria-invalid="true" aria-describedby="invoice-error">
  <p class="ew-error" id="invoice-error">Enter the invoice number.</p>
</div>
```

Group choices with `.ew-fieldset` and a visible `.ew-legend`. Native checkbox/radio inputs go inside `.ew-choice` labels. A checkbox with `role="switch"` uses the switch visual treatment; its checked state is the semantic state. Use it for immediately applied settings. Radio buttons share a name. `.ew-range` remains a native range input with a visible value output.

Consumer code owns validation. Validate on submit, preserve values, focus the first invalid control and revalidate corrections. Announce an error summary once; avoid repeatedly interrupting users while they type. Validate again on the server. Use native autocomplete and inputmode appropriate to the field. Keep read-only inputs selectable.

### Tabs

Wrap in `.ew-tabs[data-ew-tabs]`; supply a named `role="tablist"`, button tabs with `role="tab"`, unique IDs, `aria-controls` and `aria-selected`, and panels with `role="tabpanel"` and `aria-labelledby`. Leave panels visible in initial HTML for progressive fallback; `mount` hides inactive panels. Keep one tab in the tab order. Left/Right (or Up/Down for vertical lists) wrap; Home/End select edges. Automatic activation is for preloaded, immediate content. If panels fetch slow data, implement manual activation in the consuming application instead.

### Dialog and sheet

Use a native `<dialog class="ew-dialog">` with a unique ID, visible heading and `aria-labelledby`. A trigger with `data-ew-dialog="id"` opens it; a button with `data-ew-close` closes the closest dialog. The browser supplies modal containment and Escape behavior. `mount` restores the triggering element on close. Use `autofocus` on an appropriate safe control; for long structured content, focus a heading with tabindex=-1. Add `data-variant="sheet"` to place the same dialog on the right. Do not open multiple modal tasks at once.

### Disclosures and help

`.ew-disclosure` uses native `<details><summary>`. `.ew-popover` is a disclosure with an `.ew-popover-panel`; links keep ordinary Tab navigation. Escape and outside clicks close it. It deliberately has no menu role. `.ew-tooltip` is persistent, keyboard/touch accessible contextual help implemented as a details disclosure; it is not a hover-only ARIA tooltip.

### Feedback

`.ew-alert` and `.ew-badge` accept `data-tone="success|warning|danger|info"`. Static examples do not have live roles. Add `role="alert"` for a newly occurring critical event and `role="status"` for a non-critical update. Include words and symbols so color is not required. `toast(message)` creates a non-critical, dismissible live notice. It does not automatically expire; never put essential recovery solely in a toast. Prefer a scoped, pre-existing `.ew-toast-region` for consistent theming. When notifying inside a modal, put the region inside that modal's top layer.

### Data and navigation

Tables use captions, scoped headers and real table cells inside `.ew-table-scroll[tabindex="0"]`, a named overflow region. Right-align numbers with `.numeric`. Set `aria-sort` on only the active sorted header. Pagination uses normal buttons or navigational links; reflect current location and disabled endpoints. Breadcrumbs use an ordered list and `aria-current="page"`. `.ew-steps` uses an ordered list with `aria-current="step"`; numbers alone do not indicate current state.

### Loading, empty, progress and evidence

Use a native `<progress class="ew-progress">` with a label and real numeric progress. A `.ew-skeleton` is decorative inside one labeled loading region. Empty states name what is missing and one next action. Avatars accompany visible names.

Attachments use `.ew-evidence` as the container, `.ew-document` as the grid, `.ew-document-icon`, `.ew-document-copy` and a button. Filenames wrap; action placement responds to the card. Never apply fixed viewport widths or a hard-coded left margin to the action. Evidence metadata and content are application data: escape untrusted strings and verify uploads server-side.

## Integration with React and other frameworks

Import CSS once. `mount(root)` only enhances descendants in that root and returns cleanup. In React, call it inside an effect on a ref and return cleanup; remount when the enhanced DOM structure is replaced. Let your framework own data. Avoid dual ownership: if your framework manages tab/dialog state, do not add the enhancement data attributes. All static CSS is independently usable. Calling toast accepts plain text, never HTML.

## Accessibility and support

Targets WCAG 2.2 AA; this is not a product-level conformance certification. The tests verify token ratios: at least 4.5:1 regular text, 3:1 control and focus contrast. Default targets are 44px, exceeding the 24px WCAG 2.2 minimum. Focus uses a 2px outline and 3px offset. Reduced motion and forced colors have explicit treatments.

Designed for current Safari, Firefox and Chromium with native dialog and ES modules. Container query fallback stacks the evidence action. No support is promised for Internet Explorer. Before release in a product, manually test keyboard use, screen readers, native form controls, forced colors, reduced motion, 320px layout, 200% text enlargement, 400% zoom/reflow, long translated strings and actual network failures. Automatic checks cannot establish full accessibility or browser compatibility.

Sources: [WCAG 2.2](https://www.w3.org/TR/WCAG22/), [APG tabs](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/), [APG dialogs](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/).

## Build, tests and governance

```sh
node build.mjs
node --test system.test.mjs
```

Do not edit generated token styles directly. Edit tokens.json, regenerate, and review all appearances. The portfolio build runs this step automatically and publishes the same palette bridge used by the live studies. Existing study geometry remains in its adapter styles; the portable `.ew-*` components are the preferred API for new screens.

Use semantic versioning: removed/renamed tokens or behavioral breaking changes require a major version; compatible additions a minor version; fixes a patch. Each contribution should name the problem, usage, anatomy, variants, states, keyboard behavior, responsive behavior, tests and migration guidance. New components must use semantic tokens and avoid introducing inaccessible bespoke replacements for native controls. Owner: E.Wright. Record releases in CHANGELOG.md.
