@hotelfriendag/design-tokens
v0.7.0
Published
HotelFriend Design System — portable bundle (tokens, components, AI rules). Three-tier model per RFC-0002.
Maintainers
Readme
HotelFriend Design System
Cross-project design foundation: tokens, generated outputs for every stack (CSS / SCSS / TS / Tailwind v3 + v4 / shadcn), the .hf-* component layer, and AI-tool rules. Distributed as the public npm package @hotelfriendag/design-tokens.
Source repo:
HotelFriendAG/design-system(private). Extracted on 2026-05-25 fromhotelfriend/backend-hfdocs/portable-design/to be the single source of truth other HotelFriend projects consume.Status: RFC-0001 Phase 1 complete (semantic three-tier model + collision-safe
hf-prefix + drift CI). Published on npmjs.com via Trusted Publishing. SeeROADMAP.mdfor remaining items andCHANGELOG.mdfor phase history.
Contract
CONTRACT.md is the normative design-system contract — the single home for
versioning & breaking-change policy, the governed variation model (density / theme / brand),
error-UX mapping, the accessibility responsibility split, i18n posture, performance budgets, the
consumer build pipeline, token/Figma governance, the supported-stack matrix, and the proposal
process. Read it before proposing changes or integrating; this README and the integration playbooks
are the consumption detail under it.
Install
pnpm add @hotelfriendag/design-tokensPublic package — no .npmrc, no auth, no CI secrets. Works from any project, any CI.
Wire it up (pick your stack)
The package exposes each generated file via a subpath export, e.g. @hotelfriendag/design-tokens/tailwind.css, /components.css, /status.css, /_tokens.scss, /tokens.css, /tokens.ts, /shadcn-tokens.css, /dark.css, /web.css, /utilities.css, /_ionic.scss, /themes.json.
Tailwind v4 (recommended — e.g. ui-hf)
/* your main CSS entry, where @import "tailwindcss" lives */
@import "tailwindcss";
@import "@hotelfriendag/design-tokens/tailwind.css"; /* @theme tokens, hf- prefix */
@import "@hotelfriendag/design-tokens/components.css"; /* .hf-* primitives */
@import "@hotelfriendag/design-tokens/status.css"; /* .status-{domain}-{state} */Put the DS imports after any project-local @theme {} block so DS values win --color-hf-* collisions. Import tailwind.css only — tailwind.additive.css is byte-identical (the hf- prefix made the additive filter unnecessary), importing both duplicates every declaration.
<button class="bg-hf-accent hover:bg-hf-accent-hover text-hf-on-accent h-10 px-5 rounded-hf-sm text-hf-base font-semibold">Save</button>
<span class="hf-pill status-booking-confirmed">Confirmed</span>SCSS (Yii / Laravel / WP — e.g. backend-hf)
The package ships pre-built/_tokens.scss with 135 compile-time variables — $colorAccent, $colorFg, $colorBorder, $radiusSm, $shadowModal, … Semantic tokens are resolved to concrete values (e.g. $colorAccent: #24AFE8;), so no runtime cascade is needed. node_modules must be on your Sass load path.
// Dart Sass (sass-embedded / dart-sass) — node_modules on loadPaths, then:
@use '@hotelfriendag/design-tokens/pre-built/tokens' as ds;
.my-btn {
background: ds.$colorAccent; // #24AFE8
color: ds.$colorOn-accent; // #0B2F46 — AA text-on-accent (decision D1)
border-radius: ds.$radiusSm; // 6px
box-shadow: ds.$shadowModal;
}// webpack sass-loader — bare path resolves node_modules (older sass-loader: prefix with ~)
@import '@hotelfriendag/design-tokens/pre-built/tokens';
.my-btn { background: $colorAccent; border-radius: $radiusSm; }For the .hf-* component primitives, also pull the generated CSS once (it references var(--color-hf-*) — emit tokens.css into the page too so the custom properties exist at runtime):
@use '@hotelfriendag/design-tokens/pre-built/tokens'; // SCSS variables@import "@hotelfriendag/design-tokens/tokens.css"; /* :root custom properties */
@import "@hotelfriendag/design-tokens/components.css"; /* .hf-* primitives */
@import "@hotelfriendag/design-tokens/status.css"; /* status pills */Why both? SCSS
$variablesare compile-time (good for your own rules); the.hf-*component CSS resolvesvar(--color-hf-*)at runtime, so the page needstokens.cssloaded for those custom properties to exist.
Vue 3 / Nuxt (vanilla CSS variables)
// nuxt.config.ts
export default defineNuxtConfig({
css: [
'@hotelfriendag/design-tokens/tokens.css', // :root custom properties
'@hotelfriendag/design-tokens/components.css', // .hf-* primitives
'@hotelfriendag/design-tokens/status.css',
],
});Then use var(--color-hf-accent), var(--font-size-hf-base), or .hf-modal / .hf-pill .status-booking-confirmed in any template.
Dark theme
dark.css is a [data-theme="dark"] override of the ~15 semantic tokens (surfaces, text, borders, accent tints). Import it after tokens.css and flip <html data-theme="dark">:
@import "@hotelfriendag/design-tokens/tokens.css";
@import "@hotelfriendag/design-tokens/dark.css"; /* [data-theme="dark"] overrides */Then <html data-theme="dark"> (or any container) switches the whole subtree. pre-built/themes.json is the machine-readable list of which semantic vars a theme overrides (the validation contract for custom themes, enforced by validator check #8).
Web / marketing theme
web.css is a [data-theme="web"] theme for the marketing surface (web-hf, hotelfriend.com). It is the same brand as the portal — zero color overrides (accent, neutrals, Roboto unchanged) — and forks only the rhythm: larger fluid type (--font-size-hf-*, with clamp() page/hero), rounder radius (--radius-hf-*), and softer layered elevation (--shadow-hf-*). It demonstrates the [data-theme] axis extended to non-color semantic tiers (founder decision D5). Import it after tokens.css and flip <html data-theme="web">:
@import "@hotelfriendag/design-tokens/tokens.css";
@import "@hotelfriendag/design-tokens/web.css"; /* [data-theme="web"] — non-color rhythm */Then <html data-theme="web"> (or any region) re-rhythms the subtree. web-hf's pricing-tier sub-brand (gold/navy) stays local to web-hf (decision D6) and is intentionally not shipped here.
Utilities (non-Tailwind)
For Angular / plain-SCSS apps without Tailwind, utilities.css ships atomic helpers .bg-hf-{name}, .text-hf-{name}, .border-hf-{name}, .shadow-hf-{name} for the semantic tier (primitives via tokens.css vars directly). Requires tokens.css loaded:
@import "@hotelfriendag/design-tokens/tokens.css";
@import "@hotelfriendag/design-tokens/utilities.css"; /* .bg-hf-*, .text-hf-*, .border-hf-*, .shadow-hf-* */TypeScript / CSS-in-JS
import { tokens } from '@hotelfriendag/design-tokens'; // compiled tokens.js + .d.ts
const Button = styled.button`
background: ${tokens.color.accent.default}; // #24AFE8 (semantic + primitive share tokens.color.*)
border-radius: ${tokens.radius.sm}; // 6px
`;Next.js + shadcn/ui
Append shadcn-tokens.css to app/globals.css (after @import "tailwindcss"). shadcn components pick up --primary, --background, --ring, etc.
@import "@hotelfriendag/design-tokens/shadcn-tokens.css";Vanilla / static HTML
<link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/tokens.css">
<link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/components.css">
<link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/status.css">
<span class="hf-pill status-booking-confirmed">Confirmed</span>No-npm fallback
If a consumer can't reach npmjs.com, the repo also works as a git submodule (relative @import paths). See the appendix in INTEGRATION-ui-hf.md.
What you get
bg-hf-accent=#24AFE8(brand) — Tailwind'sbg-blue-500keeps its defaulttext-hf-base= 14px — Tailwind'stext-basekeeps its default 16pxrounded-hf-sm= 6px — Tailwind'srounded-smkeeps its default 2pxshadow-hf-modal,text-hf-fg,border-hf-border,bg-hf-bg-surface, … — full semantic set- Your existing utility usage is untouched (collision-safe prefix).
App code should reference semantic tokens (accent, fg, bg-*, border, status-*) — primitives (hf-blue-500, hf-gray-300) are an implementation detail the theming layer can swap.
Component primitives shipped in components.css
| Class | Anatomy | See |
|---|---|---|
| .hf-btn + --primary / --danger / --outline-primary / --outline-default / --cancel (sizes --sm / --icon) | Buttons — 40px h, 6px radius, token-driven chrome | components.html#buttons |
| .hf-input / .hf-textarea / .hf-select (+ .hf-select-wrap) | Form controls — 39px h, accent focus ring, token caret | components.html#inputs |
| .hf-form-field + __label / __hint / __error (--required, --error) | Field wrapper — label + control + hint/error | components.html#inputs |
| .hf-switch | Toggle — styled checkbox, 40×22 track, accent when on | components.html#inputs |
| .hf-card + __header/__title/__description/__body/__footer (--flat) | Elevated card — 12px radius, card shadow | components.html#cards |
| .hf-drawer + __backdrop/__panel/__header/__body/__footer (--left) | Side-panel — backdrop + sliding panel | components.html#layout |
| .hf-table (+ __num, --static) | Table — thead section bg, subtle row dividers, warm hover; style a plain <table> | components.html#table |
| .hf-empty + __icon/__title/__text/__action (--compact, --row) | Empty state — page-level, in-card, in-table-row | components.html#empty |
| .hf-pill + .status-{domain}-{state} | Status badge — 6px radius, 15% bg + 100% text + 1px border | components.html#status |
| .hf-tab / .hf-tab--sm / .hf-pill-tabs | Underline + segmented tabs | components.html#tabs |
| .hf-pagination + __item / __ellipsis | Subtle gray active (NOT accent!) — 34×34, 8px radius | components.html#pagination |
| .hf-modal + __header/__title/__body/__footer/__close | 6px radius, footer no top border | components.html#modal |
| .hf-alert + --success/--info/--warn/--error | White bg + 3px top accent bar + 26×26 squared icon | components.html#alerts |
| .hf-alert--tinted / --banner / --compact | Modifiers — bg tint / full-width strip / compact-in-card | components.html#alerts |
| .hf-toast + --success/--error/--warn/--info | Floating notification — 9px radius; variant colors the icon + 3px left edge | components.html#alerts |
| .hf-check / .hf-radio | Custom checkbox+radio — 18×18, filled accent on check | components.html#inputs |
| .hf-dropdown-menu + __header / __item / __item-icon / __shortcut / __divider (+ __item--danger) | 9px radius dropdown, portal shadow (legacy flat .hf-dropdown-{header\|item\|divider} kept as deprecated aliases) | components.html#dropdown |
| .skeleton + .hf-spin | Loading-state primitives (shimmer + rotation keyframes) | components.html#empty |
Usage examples
Copy-paste markup for the main .hf-* components — framework-agnostic (works in HTML, Vue, JSX; swap class/className). Icons are illustrative; wire your own (Lucide, etc.).
Status pill
<span class="hf-pill status-booking-confirmed">Confirmed</span>
<span class="hf-pill status-order-waiting">Waiting</span>
<span class="hf-pill status-room-item-cleaning-dirty">Dirty</span>Tabs
<div class="flex border-b border-hf-border">
<button class="hf-tab is-active">Overview</button>
<button class="hf-tab">Bookings <span class="hf-tab__count">12</span></button>
<button class="hf-tab">Guests</button>
<button class="hf-tab" disabled>Reports</button>
</div>
<!-- compact sub-filter variant: add .hf-tab--sm -->Pagination
<div class="hf-pagination">
<button class="hf-pagination__item" disabled aria-label="Previous">‹</button>
<button class="hf-pagination__item is-active">1</button>
<button class="hf-pagination__item">2</button>
<button class="hf-pagination__item">3</button>
<span class="hf-pagination__ellipsis">…</span>
<button class="hf-pagination__item">12</button>
<button class="hf-pagination__item" aria-label="Next">›</button>
</div>Modal
<div class="hf-modal max-w-[500px] mx-auto">
<div class="hf-modal__header">
<h2 class="hf-modal__title">Edit Guest</h2>
<button class="hf-modal__close" aria-label="Close">✕</button>
</div>
<div class="hf-modal__body">…</div>
<div class="hf-modal__footer">
<button class="hf-btn hf-btn--cancel">Cancel</button>
<button class="hf-btn hf-btn--primary">Save</button>
</div>
</div>
<!-- footer with top border: add .hf-modal--with-footer-border on .hf-modal -->Alert
<div class="hf-alert hf-alert--success">
<div class="hf-alert__icon"><!-- icon svg --></div>
<div class="hf-alert__body">
<div class="hf-alert__title">Saved successfully</div>
<p class="hf-alert__text">Booking #1284 has been updated.</p>
</div>
<button class="hf-alert__close" aria-label="Dismiss">✕</button>
</div>
<!-- variants: --success | --info | --warn | --error · modifiers: --tinted | --banner | --compact -->Toast
<div class="hf-toast hf-toast--success">
<span class="hf-toast__icon"><!-- icon svg --></span>
<span class="hf-toast__text">Booking <strong>#2841</strong> confirmed</span>
<button class="hf-toast__close" aria-label="Dismiss">✕</button>
</div>
<!-- variants color the icon + 3px left edge: --success | --error | --warn | --info -->Dropdown menu
<div class="hf-dropdown-menu w-52">
<div class="hf-dropdown-menu__header">Actions</div>
<div class="hf-dropdown-menu__item">View details</div>
<div class="hf-dropdown-menu__item">Edit <span class="hf-dropdown-menu__shortcut">⌘E</span></div>
<div class="hf-dropdown-menu__item is-disabled">Refresh</div>
<div class="hf-dropdown-menu__divider"></div>
<div class="hf-dropdown-menu__item hf-dropdown-menu__item--danger">Delete</div>
</div>
<!-- pre-0.4 flat names (.hf-dropdown-header / -item / -divider) still work as deprecated aliases -->Checkbox & radio
<input type="checkbox" class="hf-check" checked />
<input type="checkbox" class="hf-check" disabled />
<input type="radio" name="grp" class="hf-radio" checked />Buttons
<button class="hf-btn hf-btn--primary">Save</button>
<button class="hf-btn hf-btn--outline-primary">Preview</button>
<button class="hf-btn hf-btn--outline-default">Settings</button>
<button class="hf-btn hf-btn--cancel">Cancel</button>
<button class="hf-btn hf-btn--danger">Delete</button>
<button class="hf-btn hf-btn--primary hf-btn--sm">Small</button>
<button class="hf-btn hf-btn--icon hf-btn--outline-default" aria-label="Edit">✎</button>Density / touch mode
Buttons and inputs read their height from a density scope — switch a whole app (or one container) with data-density; no per-component change. Default = desktop (40px); touch (POS/mobile) = 48px controls + 44px tap.
<html data-density="touch"> <!-- 48px controls -->
<div data-density="comfortable">…</div> <!-- 44px, scoped -->| density | button | small | input |
|---|---|---|---|
| default | 40px | 32px | 39px |
| comfortable | 44px | 36px | 44px |
| touch | 48px | 40px | 48px |
Form controls & field
<div class="hf-form-field">
<label class="hf-form-field__label hf-form-field__label--required">Email</label>
<input type="email" class="hf-input" placeholder="[email protected]" />
<span class="hf-form-field__hint">We'll never share it.</span>
</div>
<div class="hf-form-field hf-form-field--error">
<label class="hf-form-field__label">Room</label>
<div class="hf-select-wrap">
<select class="hf-select">
<option>Deluxe</option>
<option>Suite</option>
</select>
</div>
<span class="hf-form-field__error">Please choose a room.</span>
</div>
<textarea class="hf-textarea" placeholder="Notes…"></textarea>
<!-- toggle: a styled checkbox -->
<input type="checkbox" class="hf-switch" checked />
<!-- standalone invalid (no .hf-form-field wrapper) — for framework form state, e.g. Angular.
Either trigger gives the red border + red focus ring: -->
<input class="hf-input" aria-invalid="true" />
<input class="hf-input hf-input--error" /> <!-- alias class, same effect -->Card
<section class="hf-card">
<header class="hf-card__header">
<div>
<h3 class="hf-card__title">Channel settings</h3>
<p class="hf-card__description">Enable the sales channels for this property.</p>
</div>
<button class="hf-btn hf-btn--outline-default hf-btn--sm">Edit</button>
</header>
<div class="hf-card__body">…</div>
<footer class="hf-card__footer">
<button class="hf-btn hf-btn--cancel">Cancel</button>
<button class="hf-btn hf-btn--primary">Save</button>
</footer>
</section>
<!-- borderless/flat variant: add .hf-card--flat -->Drawer (side-panel)
<div class="hf-drawer">
<div class="hf-drawer__backdrop"></div>
<aside class="hf-drawer__panel">
<header class="hf-drawer__header">
<h2 class="hf-drawer__title">Reservation #2841</h2>
<button class="hf-drawer__close" aria-label="Close">✕</button>
</header>
<div class="hf-drawer__body">…</div>
<footer class="hf-drawer__footer">
<button class="hf-btn hf-btn--cancel">Close</button>
<button class="hf-btn hf-btn--primary">Check in</button>
</footer>
</aside>
</div>
<!-- slide from the left: add .hf-drawer--left on .hf-drawer -->Table
<!-- style a plain <table>; wrap in a bordered container for the card look -->
<div class="rounded-lg border border-hf-border overflow-hidden">
<table class="hf-table">
<thead>
<tr><th>Guest</th><th>Status</th><th class="hf-table__num">Total</th></tr>
</thead>
<tbody>
<tr>
<td>Amanda Peterson</td>
<td><span class="hf-pill status-booking-confirmed">Confirmed</span></td>
<td class="hf-table__num">€ 1,240</td>
</tr>
</tbody>
</table>
</div>
<!-- disable the row hover (static summary tables): add .hf-table--static -->Empty state
<!-- empty when the request SUCCEEDS with no results; use .skeleton while it's IN FLIGHT -->
<div class="hf-empty">
<div class="hf-empty__icon"><!-- icon svg --></div>
<h3 class="hf-empty__title">No bookings found</h3>
<p class="hf-empty__text">Try adjusting your filters.</p>
<div class="hf-empty__action">
<button class="hf-btn hf-btn--primary">New booking</button>
</div>
</div>
<!-- --compact (in-card) · --row (inside an .hf-table colspan cell) -->Cheat-sheet — common tokens & utilities
Tailwind v4 derives utilities from the @theme tokens (bg-hf-*, text-hf-*, rounded-hf-*, shadow-hf-*). Vanilla CSS uses the var(--color-hf-*) form; SCSS uses $colorAccent, $radiusSm, … See UI_DESIGN.md §9 for the full token list.
Brand & text color
| Utility | Token | Value |
|---|---|---|
| bg-hf-accent / text-hf-accent | --color-hf-accent | #24AFE8 (brand) |
| hover:bg-hf-accent-hover | --color-hf-accent-hover | #149AD1 |
| bg-hf-accent-subtle / -subtler | --color-hf-accent-subtle | light tints |
| text-hf-fg | --color-hf-fg | body #2B2B2B |
| text-hf-fg-muted / -subtle / -faint | --color-hf-fg-* | secondary → placeholder |
| text-hf-on-accent | --color-hf-on-accent | #0B2F46 (AA text on accent — D1) |
Surfaces & borders
| Utility | Token |
|---|---|
| bg-hf-bg-surface | card / modal / popover (white) |
| bg-hf-bg-page / -section / -muted | page → section → muted grays |
| border-hf-border / -subtle / -strong | default → faint → hover border |
Scales
- Text:
text-hf-xs11 ·text-hf-sm13 ·text-hf-base14 ·text-hf-md15 ·text-hf-lg16 ·text-hf-xl18 - Radius:
rounded-hf-sm6 ·-md8 ·-lg9 ·-xl12 ·-pill99 - Shadow:
shadow-hf-subtle·shadow-hf-card·shadow-hf-modal·shadow-hf-hover - Status:
text-hf-status-{success|warning|error|info|cancel}+bg-hf-status-{…}-bg— or just use.hf-pill .status-{domain}-{state}
Visual reference (live)
components.html is the canonical rendered showcase of every .hf-* component. The package is public on npm, so unpkg serves it rendered in the browser — no hosting needed:
→ https://unpkg.com/@hotelfriendag/design-tokens/components.html
Always serves the latest; pin a version with …/[email protected]/components.html. Or open the copy in your own project:
open node_modules/@hotelfriendag/design-tokens/components.html # macOS
xdg-open node_modules/@hotelfriendag/design-tokens/components.html # Linux
start node_modules\@hotelfriendag\design-tokens\components.html # WindowsUse the unpkg link, not jsDelivr — jsDelivr serves
.htmlastext/plain(shows source); unpkg serves it astext/html(renders).
AI rules
The package ships agent rules under ai-rules/. Drop one into the consumer's root so Claude / Cursor / Copilot follow the same UI rules:
ai-rules/CLAUDE.md→ consumer rootCLAUDE.md(Claude Code, autodetected)ai-rules/cursorrules.template→.cursorrulesai-rules/github-copilot-instructions.md→.github/copilot-instructions.mdai-rules/system-prompt-compact.md→ compact prompt for ChatGPT / v0 / Lovable
If the consumer already has a CLAUDE.md, reference the package rules from it (e.g. point at node_modules/@hotelfriendag/design-tokens/ai-rules/CLAUDE.md) instead of overwriting. The agent learns the canonical palette, typography, spacing/radius/shadow scales, the .hf-* library, and the hard rules ("never hard-code hex", "always provide hover/focus/disabled").
Keeping a consumer up to date
pnpm update @hotelfriendag/design-tokens # pull the latest published versionA convenient consumer script: "design-system:update": "pnpm update @hotelfriendag/design-tokens && pnpm ls @hotelfriendag/design-tokens". Commit the bumped lockfile with chore: bump design-system to <version>.
Drift detection & CI (for consumers)
Stylelint config — forbids raw hex / named colors / literal box-shadow:
// .stylelintrc.cjs
module.exports = {
extends: [
'stylelint-config-standard',
'./node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs',
],
overrides: [
{ files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
],
};Pre-commit hook — scripts/pre-commit.sh runs the token validator when bundle files are staged (husky-compatible).
Maintainer reference
Repo layout & precedence
design-system/
├── components.html PRIMARY · canonical visual reference (open in browser)
├── UI_DESIGN.md NARRATIVE · rationale, anatomy, decisions
├── tokens.figma.json TOKENS · Tokens Studio export — single source of truth
├── status-map.json domain→semantic status mapping (feeds status.css)
├── src/components.css SOURCE for .hf-* rules (edit here)
├── generate-tokens.cjs Node generator (no deps)
├── pre-built/ GENERATED — never hand-edit (CI drift gate)
└── ai-rules/ agent rule filesPrecedence when files disagree:
components.html— visual decisions (colors, sizes, anatomy)tokens.figma.json— token values (regeneratepre-built/*after edits)UI_DESIGN.md— WHY decisions were madepre-built/*— generated, never hand-editportal-audit.html— archival only (legacy portal snapshot)
Regenerating outputs
tokens.figma.json is the single source of truth; src/components.css is the source for the .hf-* layer. After editing either:
npm run build # regenerates all pre-built/* (tokens, tailwind v3+v4, scss, ts/js/dts, shadcn, status.css, components.css)
npm run validate # drift detector: every var() resolves, no bare hexCI runs git diff --exit-code -- pre-built/ so outputs must be reproducible from source. Edit src/, never pre-built/.
Publishing
Releases are tag-triggered via .github/workflows/release.yml — push a v* tag and the workflow builds, validates, then npm publish-es to npmjs.com via Trusted Publishing (OIDC). No NPM_TOKEN secret, no PATs: GitHub Actions mints a short-lived OIDC token that npm exchanges for publish credentials.
--provenance is intentionally NOT used: npmjs rejects sigstore provenance from private source repositories, and this repo stays private (only the built artifact is public).
npm version patch # or minor / major → bumps package.json, creates v* tag, commits
git push --follow-tags # workflow runs on the tag and publishesThe Trusted Publisher binding on npmjs (Package → Settings → Trusted publishing) is configured for org HotelFriendAG, repo design-system, workflow release.yml. Changing any of those requires updating the binding before the next release.
prepublishOnly re-runs the generator + validator so the published package is always self-consistent.
- Changelog / phase history:
CHANGELOG.md - Remaining work:
ROADMAP.md - First-consumer playbook:
INTEGRATION-ui-hf.md - Architecture rationale:
RFC-0001·RFC-0002 - Visual reference:
components.html· Token narrative:UI_DESIGN.md
