Status: version 1, beta. Every component is marked beta and does not count as stable until a person reviews it against the definition of ready. Loom Base is MIT-licensed and builds as an npm package and a shadcn-compatible registry; it is not yet published.
Loom Base covers the components most products need
- 69 component contracts, with behaviour written as data: the 31 near-universal components found in our benchmark of product design systems, layout primitives, six AI interface patterns, and components for human approval of AI proposals.
- React and React Native from the same contracts. On the web, components build on React Aria Components for accessibility. On native, they use React Native's own accessibility features.
- Tokens in three tiers (primitive, semantic, component) in DTCG format, with colour in OKLCH and a hex fallback, and modes for light, dark and high contrast, density, reduced motion and Japanese typography.
- Page templates, such as a list and detail view, a settings page, a wizard, sign-in, empty and error pages.
- Docs for people and agents: one page per component, a typed component catalogue and an
llms.txtfile.
Every component is checked in every mode
These numbers are read from the coverage report (packages/base/COVERAGE.md) each time this page is built.
- 453 of 453 parity tests pass, on web and native. Native forms: 65 full, 3 partial, 1 without one.
- 1,113 accessibility runs with axe, 0 failing: all 371 Storybook stories, in every colour mode.
- 2,436 of 2,436 declared colour pairs pass WCAG 2.2 AA contrast, in light, dark and high contrast. A brand layer that drops a pair below AA fails the build.
- 26 of 26 keyboard scripts pass in a real browser.
Loom Base: 69 components by check
Diagram| Component | Web | Native | Web tests | Native tests | Stories | axe | Keyboard |
|---|---|---|---|---|---|---|---|
| AILabel | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: focus ring pass (complete) |
| AIStatus | Web: yes (complete) | Native: yes (complete) | Web tests: 4/4 (complete) | Native tests: 3/3 (complete) | Stories: 8 (complete) | axe: pass (24 runs) (complete) | Keyboard: focus ring pass (complete) |
| Alert | Web: yes (complete) | Native: yes (complete) | Web tests: 4/4 (complete) | Native tests: 3/3 (complete) | Stories: 8 (complete) | axe: pass (24 runs) (complete) | Keyboard: focus ring pass (complete) |
| AppHeader | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: focus ring pass (complete) |
| AppShell | Web: yes (complete) | Native: partial (partial) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 3 (complete) | axe: pass (9 runs) (complete) | Keyboard: focus ring pass (complete) |
| Avatar | Web: yes (complete) | Native: yes (complete) | Web tests: 4/4 (complete) | Native tests: 3/3 (complete) | Stories: 7 (complete) | axe: pass (21 runs) (complete) | Keyboard: – (none) |
| Badge | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 8 (complete) | axe: pass (24 runs) (complete) | Keyboard: – (none) |
| BandScale | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: – (none) |
| Box | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 12 (complete) | axe: pass (36 runs) (complete) | Keyboard: – (none) |
| Breadcrumbs | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: focus ring pass (complete) |
| ButtonGroup | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: focus ring pass (complete) |
| Button | Web: yes (complete) | Native: yes (complete) | Web tests: 13/13 (complete) | Native tests: 9/9 (complete) | Stories: 15 (complete) | axe: pass (45 runs) (complete) | Keyboard: 4/4 scripts, focus ring pass (complete) |
| Card | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: – (none) |
| CheckboxGroup | Web: yes (complete) | Native: yes (complete) | Web tests: 6/6 (complete) | Native tests: 4/4 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| Checkbox | Web: yes (complete) | Native: yes (complete) | Web tests: 8/8 (complete) | Native tests: 7/7 (complete) | Stories: 9 (complete) | axe: pass (27 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| Citation | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: focus ring pass (complete) |
| Cloth | Web: yes (complete) | Native: yes (complete) | Web tests: 4/4 (complete) | Native tests: 2/2 (complete) | Stories: 4 (complete) | axe: pass (12 runs) (complete) | Keyboard: focus ring pass (complete) |
| ColourBlock | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 6 (complete) | axe: pass (18 runs) (complete) | Keyboard: focus ring pass (complete) |
| ComboBox | Web: yes (complete) | Native: yes (complete) | Web tests: 6/6 (complete) | Native tests: 4/4 (complete) | Stories: 4 (complete) | axe: pass (12 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| ConfirmAction | Web: yes (complete) | Native: yes (complete) | Web tests: 4/4 (complete) | Native tests: 3/3 (complete) | Stories: 7 (complete) | axe: pass (21 runs) (complete) | Keyboard: focus ring pass (complete) |
| Dialog | Web: yes (complete) | Native: yes (complete) | Web tests: 11/11 (complete) | Native tests: 7/7 (complete) | Stories: 8 (complete) | axe: pass (24 runs) (complete) | Keyboard: 3/3 scripts, focus ring pass (complete) |
| Disclosure | Web: yes (complete) | Native: yes (complete) | Web tests: 6/6 (complete) | Native tests: 4/4 (complete) | Stories: 4 (complete) | axe: pass (12 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| Divider | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: – (none) |
| Drawer | Web: yes (complete) | Native: yes (complete) | Web tests: 6/6 (complete) | Native tests: 5/5 (complete) | Stories: 6 (complete) | axe: pass (18 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| EmptyState | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 3 (complete) | axe: pass (9 runs) (complete) | Keyboard: focus ring pass (complete) |
| ErrorSummary | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 1/1 (complete) | Stories: 3 (complete) | axe: pass (9 runs) (complete) | Keyboard: focus ring pass (complete) |
| FeedbackControls | Web: yes (complete) | Native: yes (complete) | Web tests: 4/4 (complete) | Native tests: 3/3 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: focus ring pass (complete) |
| FormField | Web: yes (complete) | Native: yes (complete) | Web tests: 4/4 (complete) | Native tests: 2/2 (complete) | Stories: 4 (complete) | axe: pass (12 runs) (complete) | Keyboard: – (none) |
| Grid | Web: yes (complete) | Native: partial (partial) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 7 (complete) | axe: pass (21 runs) (complete) | Keyboard: – (none) |
| Heading | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 8 (complete) | axe: pass (24 runs) (complete) | Keyboard: – (none) |
| HealthBand | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 6 (complete) | axe: pass (18 runs) (complete) | Keyboard: – (none) |
| Icon | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 11 (complete) | axe: pass (33 runs) (complete) | Keyboard: – (none) |
| Link | Web: yes (complete) | Native: yes (complete) | Web tests: 6/6 (complete) | Native tests: 4/4 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| LooseThreads | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: focus ring pass (complete) |
| Masthead | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 3 (complete) | axe: pass (9 runs) (complete) | Keyboard: – (none) |
| Menu | Web: yes (complete) | Native: yes (complete) | Web tests: 7/7 (complete) | Native tests: 4/4 (complete) | Stories: 6 (complete) | axe: pass (18 runs) (complete) | Keyboard: 2/2 scripts, focus ring pass (complete) |
| Numeral | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 3 (complete) | axe: pass (9 runs) (complete) | Keyboard: – (none) |
| PageHeader | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: – (none) |
| Pagination | Web: yes (complete) | Native: yes (complete) | Web tests: 7/7 (complete) | Native tests: 5/5 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| Popover | Web: yes (complete) | Native: yes (complete) | Web tests: 5/5 (complete) | Native tests: 3/3 (complete) | Stories: 8 (complete) | axe: pass (24 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| ProgressBar | Web: yes (complete) | Native: yes (complete) | Web tests: 4/4 (complete) | Native tests: 3/3 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: – (none) |
| ProofDiff | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: – (none) |
| Provider | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 11 (complete) | axe: pass (33 runs) (complete) | Keyboard: – (none) |
| RadioGroup | Web: yes (complete) | Native: yes (complete) | Web tests: 7/7 (complete) | Native tests: 5/5 (complete) | Stories: 8 (complete) | axe: pass (24 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| Seal | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 4 (complete) | axe: pass (12 runs) (complete) | Keyboard: – (none) |
| SectionNav | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 1 (complete) | axe: pass (3 runs) (complete) | Keyboard: focus ring pass (complete) |
| Select | Web: yes (complete) | Native: yes (complete) | Web tests: 7/7 (complete) | Native tests: 5/5 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| SideNav | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: focus ring pass (complete) |
| Sidenote | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 1 (complete) | axe: pass (3 runs) (complete) | Keyboard: – (none) |
| Skeleton | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: – (none) |
| SkipLink | Web: yes (complete) | Native: none (none) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| SourcesList | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: focus ring pass (complete) |
| Spinner | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: – (none) |
| Spread | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: – (none) |
| Stack | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 17 (complete) | axe: pass (51 runs) (complete) | Keyboard: – (none) |
| StoryHeader | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 3 (complete) | axe: pass (9 runs) (complete) | Keyboard: – (none) |
| StreamingText | Web: yes (complete) | Native: yes (complete) | Web tests: 4/4 (complete) | Native tests: 3/3 (complete) | Stories: 7 (complete) | axe: pass (21 runs) (complete) | Keyboard: focus ring pass (complete) |
| Switch | Web: yes (complete) | Native: yes (complete) | Web tests: 10/10 (complete) | Native tests: 8/8 (complete) | Stories: 7 (complete) | axe: pass (21 runs) (complete) | Keyboard: 2/2 scripts, focus ring pass (complete) |
| Table | Web: yes (complete) | Native: yes (complete) | Web tests: 5/5 (complete) | Native tests: 4/4 (complete) | Stories: 8 (complete) | axe: pass (24 runs) (complete) | Keyboard: focus ring pass (complete) |
| Tabs | Web: yes (complete) | Native: yes (complete) | Web tests: 5/5 (complete) | Native tests: 3/3 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| Tag | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 6 (complete) | axe: pass (18 runs) (complete) | Keyboard: focus ring pass (complete) |
| TextArea | Web: yes (complete) | Native: yes (complete) | Web tests: 5/5 (complete) | Native tests: 4/4 (complete) | Stories: 6 (complete) | axe: pass (18 runs) (complete) | Keyboard: focus ring pass (complete) |
| TextField | Web: yes (complete) | Native: yes (complete) | Web tests: 9/9 (complete) | Native tests: 6/6 (complete) | Stories: 15 (complete) | axe: pass (45 runs) (complete) | Keyboard: focus ring pass (complete) |
| Text | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 10 (complete) | axe: pass (30 runs) (complete) | Keyboard: – (none) |
| ToastRegion | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: focus ring pass (complete) |
| Tooltip | Web: yes (complete) | Native: partial (partial) | Web tests: 3/3 (complete) | Native tests: 2/2 (complete) | Stories: 5 (complete) | axe: pass (15 runs) (complete) | Keyboard: 2/2 scripts, focus ring pass (complete) |
| VisuallyHidden | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 1 (complete) | axe: pass (3 runs) (complete) | Keyboard: – (none) |
| WarpRail | Web: yes (complete) | Native: yes (complete) | Web tests: 3/3 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: 1/1 scripts, focus ring pass (complete) |
| WeaveDraft | Web: yes (complete) | Native: yes (complete) | Web tests: 2/2 (complete) | Native tests: 1/1 (complete) | Stories: 2 (complete) | axe: pass (6 runs) (complete) | Keyboard: focus ring pass (complete) |
- complete
- partial
- none or not applicable
packages/base/COVERAGE.md at build time. unit parity tests from the run of 2026-10-25; browser axe from 2026-10-25; browser keyboard from 2026-10-25.Every decision about it is a record
Loom Base is governed the way we govern client systems. Its decisions are files in the repository, each with its reason:
Decisions, as records
8 records0001Loom ships its own base design systemDecided by the owner0002The base system is called "Loom Base" for nowDecided by the owner0003Every component has component tokensDecided by the owner0004React Aria Components on the web, React Native core APIs on nativeApplied in v1; follows the research recommendation and the build brief0005Kasuri is the default theme; Calm stays as an alternativeProposed, then rejected by the owner in review ("has almost no personality"). Kasuri is kept as an alternative theme; [0006](0006-atelier-press-default-theme.md) proposed Atelier Press as the default; [0007](0007-selvedge-default-theme.md) (accepted) makes Selvedge the default and supersedes this record0006Atelier Press is the default theme and shell; Kasuri and Calm stay as alternatives (now an alternative theme)Superseded as the default by [0007](0007-selvedge-default-theme.md): the owner chose Selvedge (25 Oct 2026). Atelier Press is kept as an alternative theme (`tokens/themes/atelier-press.theme.json` with `motifs/press.css`); its nine components stay in Base0007Selvedge is the default theme and shell; Atelier Press, Kasuri and Calm stay as alternativesAccepted by owner choice, 25 Oct 20260008Loom Base is MIT-licensed and ships as an npm package and a shadcn-compatible registryDecided by the owner
packages/base/decisions/ when this page is built. Each record has its reason and its consequences.Fully themeable, with limits that protect people
A brand layer can change the look within lock levels. Some things are locked for every brand: the focus ring, the minimum target size and the colours that mark AI content. Status colours can be extended but not changed. Most colour, type and radius roles can be replaced, with a recorded reason. Spacing defaults can be tuned freely.
No system yet? Start from Loom Base
For a team without a design system, Loom forks Loom Base, themes it from your app's own colours and type in light and dark, prunes it to the components your app uses, and writes a migration plan. It also seeds a loom/ folder with governance sized to your team. You own the result from day one. Partial
What does not work yet: the starter has run on one pilot app so far.
Some of it is not finished, and we say which parts
We say this plainly. Some native forms are partial (3 components) and 1 component has no native form. There is no message catalogue yet, no visual regression baselines, no Figma library and no release tooling. On our own audit, Loom Base scores 73 out of 100, "Taking shape". The audit measures structure, governance and tooling; the gaps are mostly content tooling, release tooling, a docs site and Figma.
We audit ourselves with the same rubric
Loom's own control area was built before our standard existed, and it scores 26 out of 100 on our own audit, "Loose threads". We are rebuilding it on Loom Base. We publish both numbers because a strict standard has to apply to us as well. Working
We ask clients to trust our engine. Loom Base is how we earn that trust first.
Questions
Can we use Loom Base if we already have a design system? You keep your own system and your own look. Loom Base is for teams without a system, and for Loom's own screens.
Who owns a system started from Loom Base? You do, from day one. It lives in your repository.
Does every system started from Loom Base look the same? No. Loom Base has a default look, and a brand layer replaces it with yours, within the lock levels.