npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hotelfriendag/design-tokens

v0.7.0

Published

HotelFriend Design System — portable bundle (tokens, components, AI rules). Three-tier model per RFC-0002.

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 from hotelfriend/backend-hf docs/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. See ROADMAP.md for remaining items and CHANGELOG.md for 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-tokens

Public 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 $variables are compile-time (good for your own rules); the .hf-* component CSS resolves var(--color-hf-*) at runtime, so the page needs tokens.css loaded 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's bg-blue-500 keeps its default
  • text-hf-base = 14px — Tailwind's text-base keeps its default 16px
  • rounded-hf-sm = 6px — Tailwind's rounded-sm keeps its default 2px
  • shadow-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-xs 11 · text-hf-sm 13 · text-hf-base 14 · text-hf-md 15 · text-hf-lg 16 · text-hf-xl 18
  • Radius: rounded-hf-sm 6 · -md 8 · -lg 9 · -xl 12 · -pill 99
  • 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      # Windows

Use the unpkg link, not jsDelivr — jsDelivr serves .html as text/plain (shows source); unpkg serves it as text/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 root CLAUDE.md (Claude Code, autodetected)
  • ai-rules/cursorrules.template.cursorrules
  • ai-rules/github-copilot-instructions.md.github/copilot-instructions.md
  • ai-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 version

A 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 hookscripts/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 files

Precedence when files disagree:

  1. components.html — visual decisions (colors, sizes, anatomy)
  2. tokens.figma.json — token values (regenerate pre-built/* after edits)
  3. UI_DESIGN.md — WHY decisions were made
  4. pre-built/* — generated, never hand-edit
  5. portal-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 hex

CI 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 publishes

The 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.