The design system ESA's Data Technology team prototypes with. Ecology is the hub: it owns the design standard (tokens + specs), a reference implementation of 60-plus components in Astro, and the Claude intelligence layer that makes the whole thing drivable by people who don't code. Each project gets a spoke — a small prototype site that re-skins the hub and composes its parts into real screens.
Live site: esassoc.github.io/ecology — component library, token reference, and specs.
| If you're… | Go to |
|---|---|
| New, and here to build prototypes | ONBOARDING.md — soup-to-nuts setup from a fresh machine. No coding background needed, ~45 minutes. |
| Trying to understand how the system works | How it works — hub and spokes, tokens, the two distribution channels. |
| Looking for a component | Component library |
| Wondering what the Claude commands do | Claude toolkit — generated from the shipped plugin, so it can't drift. |
| Working on the hub itself | Architecture, below. |
You need Node 20 or newer (nodejs.org, LTS) and Git. Nothing else — no global CLIs, no Docker, no database.
git clone https://github.com/esassoc/ecology.git
cd ecology
npm install
npm run devnpm run dev compiles the tokens and then serves the specimen site at
http://localhost:4321. It watches for changes; leave it running. Ctrl + C
stops it.
The other scripts:
npm run build:tokens # just compile tokens → packages/tokens/dist/
npm run build # tokens + static site build
npm run preview # serve the built output
npm run deploy # build + publish to GitHub Pages
npm test # node --test over scripts/**/*.test.mjs
node scripts/doctor.mjs # verify this machine's setupIf you're setting up to work on a spoke rather than the hub, don't stop here — the spoke needs this repo cloned as a sibling and its tokens built. ONBOARDING.md covers the whole sequence.
Ecology is two products wearing one coat:
- A portable standard — tokens (
@esa/tokens) + specs + Astro specimens. Crosses every framework boundary. - A reference implementation — the Astro components (
@esa/ecology) the UX team prototypes with.
Components travel as code inside the UX prototyping world (hub → Astro spokes), and as spec at the production handoff (spoke → a dev team's real codebase, interpreted with Claude).
This repo is Astro + plain web tech, not Angular. The original Angular
library + Storybook were the starting point and now live, archived, in
../ecology-angular.
packages/
tokens/ @esa/tokens — DTCG source → dist/tokens.css (+ tokens.js)
author tokens in tokens/{primitive,semantic}/*.json
plus authored partials: component-tokens.css, type-roles.css, layouts.css
ecology/ @esa/ecology — the components (.astro presentational, .ts Lit interactive)
docs/ @esa/docs — themeable documentation shell shared by hub + spokes
handoff/ @esa/handoff — dev-handoff exporter: rendered URL → de-scoped bundle
spoke-template/ scaffold source for new spokes (never hand-copied — see /spoke-init)
apps/
site/ the browsable specimen + spec site (dogfoods every package)
includes the live theme switcher (default / beacon / qanat)
plugins/
spoke-kit/ the Claude plugin — skills, guardrail hooks, workflow commands
scripts/ doctor, create-spoke, and the design-adherence checkers
docs/ governance, promotion path, gap analysis, improvement ledger
- Primitive — raw values (
tokens/primitive/*.json) →--color-teal-900,--spacing-400 - Semantic — intent, references primitives (
tokens/semantic/*.json) →--color-primary - Component — per-component theming surface →
--form-border-color,--sidenav-bg
Theming = override the semantic and/or component layer under a [data-theme="x"]
scope. Primitives never move; component internals are never touched. Every
component exposes a tier-3 surface, rendered as a "Theming surface" table on its
doc page.
The full contract — naming, when a property earns a tier-3 hook, the zero-regression splice mechanic — is packages/tokens/SPEC.md.
- Presentational →
.astro. Golden pattern:packages/ecology/src/components/esa-badge.astro. - Interactive → Lit Web Component (
.ts). Golden pattern:esa-switch-toggle.ts. Decorator-free Lit; form controls are form-associated. Web Components work in ANY stack — they're the portable interactivity layer, and the reason a prototype's behavior survives the handoff.
Conventions, gotchas, and the parallelism rules for component work live in CLAUDE.md.
A spoke is its own Astro repo, cloned next to this one:
# in cb-fish-design/
npm install # @esa/* resolve via file:../ecology/packages/*/* theme-cbf.css — the only required artifact */
[data-theme="cb-fish"] { --color-primary: <brand>; --radius-md: 6px; /* … */ }Then compose prototypes from @esa/ecology. Because the dependencies are
file: links, a git pull in this repo updates every spoke on the machine —
which is also why a dirty hub checkout leaks unmerged work into a spoke's
deploy (/ship gates on it).
Patterns that prove broadly useful get promoted back up into the hub. See docs/promotion-path.md.
Two rules keep a re-skin safe and portable (both proven out by ../cb-fish-design,
the first spoke):
- Primitives never move. To change a neutral or ramp value, re-point the
semantic token that consumes it (
--color-border,--color-text-muted) — do not override the primitive (--color-gray-200). Primitives are the shared floor; moving them breaks the contract for every component. - The type contract is a matched set. A brand swaps two faces —
--font-sans(body) and--font-display(headlines; defaults to sans, read by the display/ title type-roles). Font-weight values are typeface-bound: the hub's--font-weight-*match DM Sans's optical weights, so a spoke that overrides--font-sansmust also set--font-weight-*to its face's matching weights. (cb-fish remaps DM Sans 350/450/550/650 → IBM Plex 400/500/600.)
Brand-tinted surfaces use the --color-primary-subtle / --color-primary-border
pair (both promoted up from cb-fish's first build).
A spoke sometimes needs a value the hub doesn't have. Sort it into one of two cases:
- A gap in an ecology scale → promote it to the hub. If the value is a missing
rung (e.g. a 40px gap between
--spacing-600/32pxand--spacing-700/48px), add it to ecology (--spacing-650). This makes the hub more complete and durable — it's not pollution. cb-fish's old--cbf-chrome-gapbecame--spacing-650. - A genuinely spoke-specific value (a brand ramp, a one-off project dimension the
hub should never carry) → a namespaced spoke tier,
--{spoke}-*, living only in the spoke's theme file. e.g.--cbf-blue-*.
The rule that keeps ecology pristine:
Ecology components read only ecology tokens. Spoke components (
cbf-*) may read ecology tokens and--{spoke}-*tokens. A spoke token never appears in a hub component.
Spoke tokens come in two flavors: re-point material (a brand ramp whose only job
is to reassign ecology semantic tokens — components never read it directly, e.g.
--cbf-blue-950 → --color-surface-inverse), and spoke-local values (read by
cbf-* components but never by esa-*). The hub stays unaware that CB Fish exists.
Not needed to build or serve the hub — but it is how the system is actually driven. If you don't have Claude Code yet:
irm https://claude.ai/install.ps1 | iex # Windows PowerShellcurl -fsSL https://claude.ai/install.sh | bash # macOS / Linux / WSLIt's self-contained (no Node needed) and auto-updates. claude --version to
confirm, claude doctor if it misbehaves. Requires a paid Claude plan.
ONBOARDING.md has the fuller walkthrough.
This repo is also a Claude Code plugin marketplace
(.claude-plugin/marketplace.json). The spoke-kit plugin ships everything
Claude needs inside a spoke: skills (component-first, design-principles,
accessibility, …), PreToolUse guardrail hooks (no bespoke UI primitives, no hub
edits from a spoke session), and the non-dev workflow commands /new-prototype,
/design-qa, /ship, /request-lego, /spoke-init.
Spokes never copy these files — their checked-in .claude/settings.json
declares this marketplace and enables spoke-kit@ecology, so everyone gets the
same rules from one source.
Publishing a plugin change is a three-step trap for the unwary (marketplace update
alone silently leaves the old version installed). The full procedure, the frozen
identifiers, and the site's build-time generation rules are in
CLAUDE.md — read it before touching plugins/spoke-kit/.
- Token output targets:
packages/tokens/build.jsis the one seam — add SCSS / TS / Tailwind / Figma platforms there. - Specs as content collections: MD/MDX pattern specs alongside specimens, so humans + dev teams + Claude read one artifact.
- Publishing: wire GitHub Packages so spokes install real versioned packages instead of workspace links.