@hotelfriendag/design-tokens 0.3.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,15 @@ Cross-project design foundation: tokens, generated outputs for every stack (CSS
6
6
  >
7
7
  > **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`](ROADMAP.md) for remaining items and [`CHANGELOG.md`](CHANGELOG.md) for phase history.
8
8
 
9
+ ## Contract
10
+
11
+ [`CONTRACT.md`](CONTRACT.md) is the **normative design-system contract** — the single home for
12
+ versioning & breaking-change policy, the governed variation model (density / theme / brand),
13
+ error-UX mapping, the accessibility responsibility split, i18n posture, performance budgets, the
14
+ consumer build pipeline, token/Figma governance, the supported-stack matrix, and the proposal
15
+ process. Read it before proposing changes or integrating; this README and the integration playbooks
16
+ are the *consumption* detail under it.
17
+
9
18
  ## Install
10
19
 
11
20
  ```bash
@@ -16,7 +25,7 @@ Public package — no `.npmrc`, no auth, no CI secrets. Works from any project,
16
25
 
17
26
  ## Wire it up (pick your stack)
18
27
 
19
- 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`.
28
+ 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`, `/utilities.css`, `/_ionic.scss`, `/themes.json`.
20
29
 
21
30
  ### Tailwind v4 (recommended — e.g. `ui-hf`)
22
31
 
@@ -32,7 +41,7 @@ The package exposes each generated file via a subpath export, e.g. `@hotelfriend
32
41
  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.
33
42
 
34
43
  ```html
35
- <button class="bg-hf-accent hover:bg-hf-accent-hover text-white h-10 px-5 rounded-hf-sm text-hf-base font-semibold">Save</button>
44
+ <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>
36
45
  <span class="hf-pill status-booking-confirmed">Confirmed</span>
37
46
  ```
38
47
 
@@ -46,7 +55,7 @@ The package ships `pre-built/_tokens.scss` with 135 compile-time variables — `
46
55
 
47
56
  .my-btn {
48
57
  background: ds.$colorAccent; // #24AFE8
49
- color: #fff;
58
+ color: ds.$colorOn-accent; // #0B2F46 — AA text-on-accent (decision D1)
50
59
  border-radius: ds.$radiusSm; // 6px
51
60
  box-shadow: ds.$shadowModal;
52
61
  }
@@ -87,6 +96,26 @@ export default defineNuxtConfig({
87
96
 
88
97
  Then use `var(--color-hf-accent)`, `var(--font-size-hf-base)`, or `.hf-modal` / `.hf-pill .status-booking-confirmed` in any template.
89
98
 
99
+ ### Dark theme
100
+
101
+ `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">`:
102
+
103
+ ```css
104
+ @import "@hotelfriendag/design-tokens/tokens.css";
105
+ @import "@hotelfriendag/design-tokens/dark.css"; /* [data-theme="dark"] overrides */
106
+ ```
107
+
108
+ 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).
109
+
110
+ ### Utilities (non-Tailwind)
111
+
112
+ 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:
113
+
114
+ ```css
115
+ @import "@hotelfriendag/design-tokens/tokens.css";
116
+ @import "@hotelfriendag/design-tokens/utilities.css"; /* .bg-hf-*, .text-hf-*, .border-hf-*, .shadow-hf-* */
117
+ ```
118
+
90
119
  ### TypeScript / CSS-in-JS
91
120
 
92
121
  ```ts
@@ -134,17 +163,307 @@ App code should reference **semantic** tokens (`accent`, `fg`, `bg-*`, `border`,
134
163
 
135
164
  | Class | Anatomy | See |
136
165
  |---|---|---|
166
+ | `.hf-btn` + `--primary` / `--danger` / `--outline-primary` / `--outline-default` / `--cancel` (sizes `--sm` / `--icon`) | Buttons — 40px h, 6px radius, token-driven chrome | components.html#buttons |
167
+ | `.hf-input` / `.hf-textarea` / `.hf-select` (+ `.hf-select-wrap`) | Form controls — 39px h, accent focus ring, token caret | components.html#inputs |
168
+ | `.hf-form-field` + `__label` / `__hint` / `__error` (`--required`, `--error`) | Field wrapper — label + control + hint/error | components.html#inputs |
169
+ | `.hf-switch` | Toggle — styled checkbox, 40×22 track, accent when on | components.html#inputs |
170
+ | `.hf-card` + `__header/__title/__description/__body/__footer` (`--flat`) | Elevated card — 12px radius, card shadow | components.html#cards |
171
+ | `.hf-drawer` + `__backdrop/__panel/__header/__body/__footer` (`--left`) | Side-panel — backdrop + sliding panel | components.html#layout |
172
+ | `.hf-table` (+ `__num`, `--static`) | Table — thead section bg, subtle row dividers, warm hover; style a plain `<table>` | components.html#table |
173
+ | `.hf-empty` + `__icon/__title/__text/__action` (`--compact`, `--row`) | Empty state — page-level, in-card, in-table-row | components.html#empty |
137
174
  | `.hf-pill` + `.status-{domain}-{state}` | Status badge — 6px radius, 15% bg + 100% text + 1px border | components.html#status |
138
175
  | `.hf-tab` / `.hf-tab--sm` / `.hf-pill-tabs` | Underline + segmented tabs | components.html#tabs |
139
176
  | `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT accent!) — 34×34, 8px radius | components.html#pagination |
140
177
  | `.hf-modal` + `__header/__title/__body/__footer/__close` | 6px radius, footer no top border | components.html#modal |
141
178
  | `.hf-alert` + `--success/--info/--warn/--error` | White bg + 3px top accent bar + 26×26 squared icon | components.html#alerts |
142
179
  | `.hf-alert--tinted` / `--banner` / `--compact` | Modifiers — bg tint / full-width strip / compact-in-card | components.html#alerts |
143
- | `.hf-toast` | Floating notification — 9px radius | components.html#alerts |
180
+ | `.hf-toast` + `--success/--error/--warn/--info` | Floating notification — 9px radius; variant colors the icon + 3px left edge | components.html#alerts |
144
181
  | `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled accent on check | components.html#inputs |
145
- | `.hf-dropdown-menu` + `__item / __header / __shortcut / __icon / __divider` | 9px radius dropdown, portal shadow | components.html#dropdown |
182
+ | `.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 |
146
183
  | `.skeleton` + `.hf-spin` | Loading-state primitives (shimmer + rotation keyframes) | components.html#empty |
147
184
 
185
+ ## Usage examples
186
+
187
+ 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.).
188
+
189
+ ### Status pill
190
+
191
+ ```html
192
+ <span class="hf-pill status-booking-confirmed">Confirmed</span>
193
+ <span class="hf-pill status-order-waiting">Waiting</span>
194
+ <span class="hf-pill status-room-item-cleaning-dirty">Dirty</span>
195
+ ```
196
+
197
+ ### Tabs
198
+
199
+ ```html
200
+ <div class="flex border-b border-hf-border">
201
+ <button class="hf-tab is-active">Overview</button>
202
+ <button class="hf-tab">Bookings <span class="hf-tab__count">12</span></button>
203
+ <button class="hf-tab">Guests</button>
204
+ <button class="hf-tab" disabled>Reports</button>
205
+ </div>
206
+ <!-- compact sub-filter variant: add .hf-tab--sm -->
207
+ ```
208
+
209
+ ### Pagination
210
+
211
+ ```html
212
+ <div class="hf-pagination">
213
+ <button class="hf-pagination__item" disabled aria-label="Previous">‹</button>
214
+ <button class="hf-pagination__item is-active">1</button>
215
+ <button class="hf-pagination__item">2</button>
216
+ <button class="hf-pagination__item">3</button>
217
+ <span class="hf-pagination__ellipsis">…</span>
218
+ <button class="hf-pagination__item">12</button>
219
+ <button class="hf-pagination__item" aria-label="Next">›</button>
220
+ </div>
221
+ ```
222
+
223
+ ### Modal
224
+
225
+ ```html
226
+ <div class="hf-modal max-w-[500px] mx-auto">
227
+ <div class="hf-modal__header">
228
+ <h2 class="hf-modal__title">Edit Guest</h2>
229
+ <button class="hf-modal__close" aria-label="Close">✕</button>
230
+ </div>
231
+ <div class="hf-modal__body">…</div>
232
+ <div class="hf-modal__footer">
233
+ <button class="hf-btn hf-btn--cancel">Cancel</button>
234
+ <button class="hf-btn hf-btn--primary">Save</button>
235
+ </div>
236
+ </div>
237
+ <!-- footer with top border: add .hf-modal--with-footer-border on .hf-modal -->
238
+ ```
239
+
240
+ ### Alert
241
+
242
+ ```html
243
+ <div class="hf-alert hf-alert--success">
244
+ <div class="hf-alert__icon"><!-- icon svg --></div>
245
+ <div class="hf-alert__body">
246
+ <div class="hf-alert__title">Saved successfully</div>
247
+ <p class="hf-alert__text">Booking #1284 has been updated.</p>
248
+ </div>
249
+ <button class="hf-alert__close" aria-label="Dismiss">✕</button>
250
+ </div>
251
+ <!-- variants: --success | --info | --warn | --error · modifiers: --tinted | --banner | --compact -->
252
+ ```
253
+
254
+ ### Toast
255
+
256
+ ```html
257
+ <div class="hf-toast hf-toast--success">
258
+ <span class="hf-toast__icon"><!-- icon svg --></span>
259
+ <span class="hf-toast__text">Booking <strong>#2841</strong> confirmed</span>
260
+ <button class="hf-toast__close" aria-label="Dismiss">✕</button>
261
+ </div>
262
+ <!-- variants color the icon + 3px left edge: --success | --error | --warn | --info -->
263
+ ```
264
+
265
+ ### Dropdown menu
266
+
267
+ ```html
268
+ <div class="hf-dropdown-menu w-52">
269
+ <div class="hf-dropdown-menu__header">Actions</div>
270
+ <div class="hf-dropdown-menu__item">View details</div>
271
+ <div class="hf-dropdown-menu__item">Edit <span class="hf-dropdown-menu__shortcut">⌘E</span></div>
272
+ <div class="hf-dropdown-menu__item is-disabled">Refresh</div>
273
+ <div class="hf-dropdown-menu__divider"></div>
274
+ <div class="hf-dropdown-menu__item hf-dropdown-menu__item--danger">Delete</div>
275
+ </div>
276
+ <!-- pre-0.4 flat names (.hf-dropdown-header / -item / -divider) still work as deprecated aliases -->
277
+ ```
278
+
279
+ ### Checkbox & radio
280
+
281
+ ```html
282
+ <input type="checkbox" class="hf-check" checked />
283
+ <input type="checkbox" class="hf-check" disabled />
284
+ <input type="radio" name="grp" class="hf-radio" checked />
285
+ ```
286
+
287
+ ### Buttons
288
+
289
+ ```html
290
+ <button class="hf-btn hf-btn--primary">Save</button>
291
+ <button class="hf-btn hf-btn--outline-primary">Preview</button>
292
+ <button class="hf-btn hf-btn--outline-default">Settings</button>
293
+ <button class="hf-btn hf-btn--cancel">Cancel</button>
294
+ <button class="hf-btn hf-btn--danger">Delete</button>
295
+ <button class="hf-btn hf-btn--primary hf-btn--sm">Small</button>
296
+ <button class="hf-btn hf-btn--icon hf-btn--outline-default" aria-label="Edit">✎</button>
297
+ ```
298
+
299
+ ### Density / touch mode
300
+
301
+ 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.
302
+
303
+ ```html
304
+ <html data-density="touch"> <!-- 48px controls -->
305
+ <div data-density="comfortable">…</div> <!-- 44px, scoped -->
306
+ ```
307
+
308
+ | density | button | small | input |
309
+ |---|---|---|---|
310
+ | default | 40px | 32px | 39px |
311
+ | `comfortable` | 44px | 36px | 44px |
312
+ | `touch` | 48px | 40px | 48px |
313
+
314
+ ### Form controls & field
315
+
316
+ ```html
317
+ <div class="hf-form-field">
318
+ <label class="hf-form-field__label hf-form-field__label--required">Email</label>
319
+ <input type="email" class="hf-input" placeholder="guest@example.com" />
320
+ <span class="hf-form-field__hint">We'll never share it.</span>
321
+ </div>
322
+
323
+ <div class="hf-form-field hf-form-field--error">
324
+ <label class="hf-form-field__label">Room</label>
325
+ <div class="hf-select-wrap">
326
+ <select class="hf-select">
327
+ <option>Deluxe</option>
328
+ <option>Suite</option>
329
+ </select>
330
+ </div>
331
+ <span class="hf-form-field__error">Please choose a room.</span>
332
+ </div>
333
+
334
+ <textarea class="hf-textarea" placeholder="Notes…"></textarea>
335
+
336
+ <!-- toggle: a styled checkbox -->
337
+ <input type="checkbox" class="hf-switch" checked />
338
+
339
+ <!-- standalone invalid (no .hf-form-field wrapper) — for framework form state, e.g. Angular.
340
+ Either trigger gives the red border + red focus ring: -->
341
+ <input class="hf-input" aria-invalid="true" />
342
+ <input class="hf-input hf-input--error" /> <!-- alias class, same effect -->
343
+ ```
344
+
345
+ ### Card
346
+
347
+ ```html
348
+ <section class="hf-card">
349
+ <header class="hf-card__header">
350
+ <div>
351
+ <h3 class="hf-card__title">Channel settings</h3>
352
+ <p class="hf-card__description">Enable the sales channels for this property.</p>
353
+ </div>
354
+ <button class="hf-btn hf-btn--outline-default hf-btn--sm">Edit</button>
355
+ </header>
356
+ <div class="hf-card__body">…</div>
357
+ <footer class="hf-card__footer">
358
+ <button class="hf-btn hf-btn--cancel">Cancel</button>
359
+ <button class="hf-btn hf-btn--primary">Save</button>
360
+ </footer>
361
+ </section>
362
+ <!-- borderless/flat variant: add .hf-card--flat -->
363
+ ```
364
+
365
+ ### Drawer (side-panel)
366
+
367
+ ```html
368
+ <div class="hf-drawer">
369
+ <div class="hf-drawer__backdrop"></div>
370
+ <aside class="hf-drawer__panel">
371
+ <header class="hf-drawer__header">
372
+ <h2 class="hf-drawer__title">Reservation #2841</h2>
373
+ <button class="hf-drawer__close" aria-label="Close">✕</button>
374
+ </header>
375
+ <div class="hf-drawer__body">…</div>
376
+ <footer class="hf-drawer__footer">
377
+ <button class="hf-btn hf-btn--cancel">Close</button>
378
+ <button class="hf-btn hf-btn--primary">Check in</button>
379
+ </footer>
380
+ </aside>
381
+ </div>
382
+ <!-- slide from the left: add .hf-drawer--left on .hf-drawer -->
383
+ ```
384
+
385
+ ### Table
386
+
387
+ ```html
388
+ <!-- style a plain <table>; wrap in a bordered container for the card look -->
389
+ <div class="rounded-lg border border-hf-border overflow-hidden">
390
+ <table class="hf-table">
391
+ <thead>
392
+ <tr><th>Guest</th><th>Status</th><th class="hf-table__num">Total</th></tr>
393
+ </thead>
394
+ <tbody>
395
+ <tr>
396
+ <td>Amanda Peterson</td>
397
+ <td><span class="hf-pill status-booking-confirmed">Confirmed</span></td>
398
+ <td class="hf-table__num">€ 1,240</td>
399
+ </tr>
400
+ </tbody>
401
+ </table>
402
+ </div>
403
+ <!-- disable the row hover (static summary tables): add .hf-table--static -->
404
+ ```
405
+
406
+ ### Empty state
407
+
408
+ ```html
409
+ <!-- empty when the request SUCCEEDS with no results; use .skeleton while it's IN FLIGHT -->
410
+ <div class="hf-empty">
411
+ <div class="hf-empty__icon"><!-- icon svg --></div>
412
+ <h3 class="hf-empty__title">No bookings found</h3>
413
+ <p class="hf-empty__text">Try adjusting your filters.</p>
414
+ <div class="hf-empty__action">
415
+ <button class="hf-btn hf-btn--primary">New booking</button>
416
+ </div>
417
+ </div>
418
+ <!-- --compact (in-card) · --row (inside an .hf-table colspan cell) -->
419
+ ```
420
+
421
+ ## Cheat-sheet — common tokens & utilities
422
+
423
+ 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`](UI_DESIGN.md) §9 for the full token list.
424
+
425
+ **Brand & text color**
426
+
427
+ | Utility | Token | Value |
428
+ |---|---|---|
429
+ | `bg-hf-accent` / `text-hf-accent` | `--color-hf-accent` | `#24AFE8` (brand) |
430
+ | `hover:bg-hf-accent-hover` | `--color-hf-accent-hover` | `#149AD1` |
431
+ | `bg-hf-accent-subtle` / `-subtler` | `--color-hf-accent-subtle` | light tints |
432
+ | `text-hf-fg` | `--color-hf-fg` | body `#2B2B2B` |
433
+ | `text-hf-fg-muted` / `-subtle` / `-faint` | `--color-hf-fg-*` | secondary → placeholder |
434
+ | `text-hf-on-accent` | `--color-hf-on-accent` | `#0B2F46` (AA text on accent — D1) |
435
+
436
+ **Surfaces & borders**
437
+
438
+ | Utility | Token |
439
+ |---|---|
440
+ | `bg-hf-bg-surface` | card / modal / popover (white) |
441
+ | `bg-hf-bg-page` / `-section` / `-muted` | page → section → muted grays |
442
+ | `border-hf-border` / `-subtle` / `-strong` | default → faint → hover border |
443
+
444
+ **Scales**
445
+
446
+ - **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
447
+ - **Radius:** `rounded-hf-sm` 6 · `-md` 8 · `-lg` 9 · `-xl` 12 · `-pill` 99
448
+ - **Shadow:** `shadow-hf-subtle` · `shadow-hf-card` · `shadow-hf-modal` · `shadow-hf-hover`
449
+ - **Status:** `text-hf-status-{success|warning|error|info|cancel}` + `bg-hf-status-{…}-bg` — or just use `.hf-pill .status-{domain}-{state}`
450
+
451
+ ## Visual reference (live)
452
+
453
+ `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:
454
+
455
+ **→ https://unpkg.com/@hotelfriendag/design-tokens/components.html**
456
+
457
+ Always serves the latest; pin a version with `…/design-tokens@0.5.0/components.html`. Or open the copy in your own project:
458
+
459
+ ```bash
460
+ open node_modules/@hotelfriendag/design-tokens/components.html # macOS
461
+ xdg-open node_modules/@hotelfriendag/design-tokens/components.html # Linux
462
+ start node_modules\@hotelfriendag\design-tokens\components.html # Windows
463
+ ```
464
+
465
+ > Use the **unpkg** link, not jsDelivr — jsDelivr serves `.html` as `text/plain` (shows source); unpkg serves it as `text/html` (renders).
466
+
148
467
  ## AI rules
149
468
 
150
469
  The package ships agent rules under `ai-rules/`. Drop one into the consumer's root so Claude / Cursor / Copilot follow the same UI rules:
package/UI_DESIGN.md CHANGED
@@ -241,11 +241,14 @@ box-shadow: 0 3px 4px rgba(0, 0, 0, 0.03);
241
241
 
242
242
  ### Buttons
243
243
 
244
+ > **Design-system API (v0.4+):** these portal recipes now ship as the `.hf-btn` primitive in `pre-built/components.css`. Use `.hf-btn` + a modifier in new code; the legacy portal classes below are the source these were extracted from:
245
+ > `.btn-primary`→`.hf-btn--primary` · `.btn-hf-outline-primary`→`.hf-btn--outline-primary` · neutral `.btn`→`.hf-btn--outline-default` · `.btn-cancel`→`.hf-btn--cancel` · `.btn-delete`→`.hf-btn--danger` · `.btn-sm`→`.hf-btn--sm` · icon-only→`.hf-btn--icon`.
246
+
244
247
  Live observation: 25+ unique button variants across 13 sidebar sections + 7 sub-tabs + 7 edit forms. Below is the canonical set; legacy variants (e.g. `btn.btn-b-gray`, `btn-text-blue`) exist but should be migrated.
245
248
 
246
249
  | Style | Class | Look |
247
250
  | ----------- | ------------------------------ | ------------------------------------------------------------ |
248
- | **Primary** | `.btn.btn-primary` | Solid `#24AFE8` bg, white text, 40px h, 15/600. Hover: `#149AD1` bg. |
251
+ | **Primary** | `.btn.btn-primary` | Solid `#24AFE8` bg, AA dark text `#0B2F46` (founder decision D1 — white failed WCAG AA at 2.51:1), 40px h, 15/600. Hover: `#149AD1` bg. |
249
252
  | **Primary sm** | `.btn.btn-primary.btn-sm` | Solid bg, 32px h, 15/600, padding `7px 14px`. |
250
253
  | **Primary search** | `.btn.btn-primary.pr13` | 38px h, padding `10px 13px` — used as inline search submit. |
251
254
  | **Outline** | `.btn.btn-hf-outline-primary` | White bg, `#24AFE8` border + text. 40px h, 15/600. |
@@ -430,6 +433,7 @@ These are the **canonical component classes** shipped in `pre-built/components.c
430
433
  - **Anatomy:** white bg, 9px radius, `0 6px 18px rgba(0,0,0,.10)` shadow, 1px border `rgba(72,91,120,.15)`
431
434
  - **Layout:** inline-flex with `__icon` (20×20) + `__text` (flex:1) + optional `__close` (20×20)
432
435
  - **Sizing:** min-width 280px, max-width 420px
436
+ - **Variants (v0.6):** `--success` / `--error` / `--warn` / `--info` — bound to the `--color-hf-status-*` families (same structure as `.hf-alert--*`). The variant sets the accent color via `currentColor` (which tints the `__icon`) and adds a 3px left edge; the message text stays `--color-hf-fg`. Use these instead of hand-coloring the icon.
433
437
  - **Positioning:** typically `position: fixed; bottom: 16px; right: 16px; z-index: 50`
434
438
  - **Reference:** `components.html#alerts` · **Portal source:** `.alert-box` (different DOM structure) · **Decision:** New modern primitive — portal `.alert-box` is positioned but uses heavier `.alert` chrome. Toast is lighter.
435
439
 
@@ -445,29 +449,84 @@ These are the **canonical component classes** shipped in `pre-built/components.c
445
449
 
446
450
  ### `.hf-dropdown-menu` — Dropdown
447
451
  - **Anatomy:** 9px radius, white bg, `box-shadow: 0 1px 10px 0 rgba(0,0,0,.1)` (portal's `$dropdown_shadow`), 1px border `rgba(228,232,239,.6)`, padding `6px 0`, min-width 180px
448
- - **Children:**
452
+ - **Children** (canonical BEM, v0.4+):
449
453
  - `__header` — uppercase 11px/600 `#99A1B7` section label
450
454
  - `__item` — flex row, padding `8px 16px`, 14px text, gap 10px
451
- - `__item__icon` — 16px, color `#99A1B7` (faint), becomes `#50627E` on item hover
452
- - `__item__shortcut` — right-aligned 12px text (for ⌘keys or counts)
455
+ - `__item-icon` — 16px, color `#99A1B7` (faint), becomes `#50627E` on item hover
456
+ - `__shortcut` — right-aligned 12px text (for ⌘keys or counts)
453
457
  - `__divider` — 1px `#E4E8EF` separator
454
458
  - **Item states:**
455
459
  - Default → hover `bg: #F5F5F5`
456
460
  - `.is-active` → `bg: #EFF6FF; color: #24AFE8` (primary tint + primary text)
457
461
  - `:focus-visible` → outline 2px primary inset
458
- - `.danger` → red text, red-tint hover
462
+ - `__item--danger` → red text, red-tint hover
459
463
  - `.is-disabled` / `[aria-disabled="true"]` → `#AEBCCF` color, pointer-events none
460
- - **Reference:** `components.html#dropdown` · **Portal source:** `.dropdown-menu.open` in `_custom.scss` · **Decision:** 1:1 portal match for shadow/radius/colors. **Adapted:** uses flat `<div>` structure instead of Bootstrap 3 `<ul><li><a>` (modern, framework-agnostic).
464
+ - **Deprecated aliases (still work, remove in v1.0):** the pre-0.4 flat names `.hf-dropdown-header`, `.hf-dropdown-item`, `.hf-dropdown-item__icon`, `.hf-dropdown-item__shortcut`, `.hf-dropdown-divider`, `.hf-dropdown-item.danger`.
465
+ - **Reference:** `components.html#dropdown` · **Portal source:** `.dropdown-menu.open` in `_custom.scss` · **Decision:** 1:1 portal match for shadow/radius/colors. **Adapted:** uses flat `<div>` structure instead of Bootstrap 3 `<ul><li><a>` (modern, framework-agnostic); renamed to BEM children of `.hf-dropdown-menu` in v0.4 for consistency with `.hf-modal__*` / `.hf-alert__*`.
466
+
467
+ ### `.hf-btn` — Buttons (v0.4+)
468
+ - **Anatomy:** `inline-flex` center, height 40px, padding `0 20px`, 6px radius, 15px/600, `border: 1px solid transparent`, 200ms transitions. `:focus-visible` → 2px accent outline (offset 2px). `:disabled` / `.is-disabled` → opacity .6, not-allowed.
469
+ - **Variants:**
470
+ - `--primary` — accent bg, AA dark text `--color-hf-on-accent` (`#0B2F46`, 5.55:1 — founder decision D1; white failed AA); hover `--color-hf-accent-hover` (`#149AD1`)
471
+ - `--danger` — `--color-hf-status-error-strong` (`#D9534F`) bg; hover `--color-hf-status-error-strong-hover` (`#C9302C`)
472
+ - `--outline-primary` — white bg, accent text + border; hover bg-muted
473
+ - `--outline-default` — white bg, steel text, neutral border, `--shadow-hf-outline`; hover bg-muted
474
+ - `--cancel` — transparent, muted text; hover bg-muted
475
+ - **Sizes/shapes:** `--sm` (32px / 14px), `--icon` (square — width = height), `--block` (full width)
476
+ - **Density (v0.5):** height follows the `[data-density]` scope — desktop 40 / `comfortable` 44 / `touch` 48px (vars `--hf-btn-h` / `-sm`). Set `data-density="touch"` on a container/`<html>` for POS/mobile; see `ADOPTION.md`.
477
+ - **Reference:** `components.html#buttons` · **Portal source:** `.btn-primary` / `.btn-hf-outline-*` / `.btn-cancel` / `.btn-delete` · **Decision:** consolidates 25+ portal button recipes into one base + modifiers. Compact portal-only families (reset-filters, icon-square) stay Tailwind-utility recipes until promoted.
478
+
479
+ ### `.hf-input` / `.hf-textarea` / `.hf-select` — Form controls (v0.4+)
480
+ - **Anatomy:** width 100%, height 39px, padding `0 12px`, 6px radius, 1px `--color-hf-input-border` (`#DBDFE9`), 14px text. Placeholder `--color-hf-fg-faint`.
481
+ - **Focus:** accent border + 3px `--color-hf-accent-subtle` ring (accessible — portal had no ring).
482
+ - **Invalid (v0.6):** a red border + red focus ring fires from any of three equivalent triggers — inside an `.hf-form-field--error` wrapper, the standalone `[aria-invalid="true"]` attribute, or the alias class `.hf-input--error` / `.hf-textarea--error` / `.hf-select--error`. The standalone forms let framework form wrappers (Angular) flag a control without the `.hf-form-field` wrapper.
483
+ - **Disabled:** bg-muted, faint text, not-allowed.
484
+ - **Textarea:** auto height, min-height 80px, vertical resize.
485
+ - **Select:** `appearance:none` + wrapper `.hf-select-wrap` draws a token-colored caret (clip-path triangle, same technique as `.hf-pill--dd` — no data-URI, so no bare hex).
486
+ - **Reference:** `components.html#inputs` · **Portal source:** `.form-control` · **Decision:** 1:1 geometry; adds the accessible focus ring the portal lacks.
487
+
488
+ ### `.hf-switch` — Toggle (v0.4+)
489
+ - **Anatomy:** styled `<input type="checkbox">`, 40×22 track (999px radius), 18px white knob with `--shadow-hf-hover`. Off = `--color-hf-border`; `:checked` = accent bg + knob slides 18px right. `:focus-visible` outline; `:disabled` opacity .5.
490
+ - **Reference:** `components.html#inputs` · **Decision:** new lightweight token-driven primitive (portal shipped only a Bootstrap-Switch).
491
+
492
+ ### `.hf-form-field` — Field wrapper (v0.4+)
493
+ - **Anatomy:** `flex column gap 6px`. `__label` (14px/500), `__label--required::after` red `*`, `__hint` (13px subtle), `__error` (13px error).
494
+ - **State:** `--error` colours the contained `.hf-input` / `.hf-textarea` / `.hf-select` border + focus ring in `--color-hf-status-error`.
495
+ - **Reference:** `components.html#inputs` · **Decision:** new primitive — standardises label + control + hint/error so consumers stop re-inventing field structure.
496
+
497
+ ### `.hf-card` — Card (v0.4+)
498
+ - **Anatomy:** white bg, 1px `--color-hf-border-subtle`, 12px radius, `--shadow-hf-card`. `__header` (20px, bottom border) + `__title` (18px/600) + `__description` (14px muted) + `__body` (20px) + `__footer` (20px, top border, right-aligned).
499
+ - **Variant:** `--flat` removes the shadow.
500
+ - **Reference:** `components.html#cards` · **Portal source:** `.dashboard-card` · **Decision:** elevated card; for the lighter look use the bordered pattern `bg-white rounded-lg border p-5`.
501
+
502
+ ### `.hf-drawer` — Side-panel (v0.4+)
503
+ - **Anatomy:** `position:fixed; inset:0; z-index:10051` (= `zIndex.modalDialog`), flex justify-end. `__backdrop` (`rgba(0,0,0,.4)`), `__panel` (max-width 480px, full height, `--shadow-hf-modal`, slide-in keyframe). `--left` slides from the left. `__header` / `__title` / `__close` / `__body` (scroll) / `__footer` mirror `.hf-modal`.
504
+ - **Reference:** `components.html#layout` · **Decision:** new primitive — consolidates the repeated backdrop + sliding-panel pattern (reservation / room / guest drawers).
505
+
506
+ ### `.hf-table` — Table (v0.6+)
507
+ - **Anatomy:** style a plain `<table class="hf-table">` with element selectors — `thead` gets `--color-hf-bg-section` bg; `th` are uppercase 13px/500 `--color-hf-tab-fg-inactive` (steel) with `padding: 12px 20px`; `td` get a 1px `--color-hf-border-subtle` top divider (the first body row has none). Row hover = `--color-hf-menu-bg-hover` (the legacy portal `$table__hover`).
508
+ - **Helpers:** `.hf-table__num` on a th/td right-aligns (numeric / action columns). `--static` disables the row hover (summary tables).
509
+ - **Usage:** wrap in `bg-white rounded-lg border overflow-hidden` for the card look. An empty result renders `.hf-empty--row` inside a `<td colspan>` cell.
510
+ - **Reference:** `components.html#table` · **Decision:** new primitive — the thead-bg + divider + hover recipe was hand-rolled on every dashboard page; no new token (reuses existing semantic + component-tier tokens).
511
+
512
+ ### `.hf-empty` — Empty state (v0.6+)
513
+ - **Anatomy:** centered flex column — `__icon` (64px disc, `--color-hf-bg-muted` bg, faint icon) + `__title` (18px/600) + `__text` (14px muted, max-w 28rem) + `__action` (button row).
514
+ - **Modifiers:** `--compact` (in-card — 40px transparent icon, tighter padding, 14px title) · `--row` (inside an `.hf-table` `<td colspan>` — single centered line + optional secondary action).
515
+ - **Usage rule:** empty states are for when the request **succeeds with no results**; use `.skeleton` while the request is **in flight** — they are not interchangeable (documented in the CSS).
516
+ - **Reference:** `components.html#empty` · **Decision:** new primitive — promotes the three demo recipes (page / in-card / in-table) into shipped CSS; no new token.
461
517
 
462
518
  ### `.skeleton` / `.hf-spin` — Loading primitives
463
519
  - **Skeleton:** linear-gradient shimmer animation, 1.4s infinite, 4px radius. Use as block element with width/height utilities (e.g. `<div class="skeleton h-3 w-3/4">`)
464
520
  - **Spinner:** `@keyframes hf-spin` rotate animation. Apply `.hf-spin` to an SVG.
521
+ - **Reduced motion (v0.6):** a `@media (prefers-reduced-motion: reduce)` block stops the infinite shimmer + spin (skeleton keeps a flat muted block) and neutralizes the drawer slide-in.
465
522
  - **Reference:** `components.html#empty` · **Decision:** New primitives — portal has no equivalent.
466
523
 
467
524
  ---
468
525
 
469
526
  ## 6. Forms
470
527
 
528
+ > **Design-system API (v0.4+):** these ship as `.hf-input` / `.hf-textarea` / `.hf-select` + `.hf-form-field` + `.hf-switch` in `pre-built/components.css` (see §5.1). Use those in new code; the portal-observed specs below are the source. The DS focus ring (accent border + 3px `--color-hf-accent-subtle`) is the accessible variant.
529
+
471
530
  - **Inputs (Text / Select):** Height `39px`, Padding `6px 12px`, Radius `6px`, Border `1px solid #d1d6dd`. Placeholder `#AEBCCF`. **Focus (portal-exact):** border darkens only to `#B2BAC4`, **no ring**. **Focus (accessible, recommended for new projects):** add `box-shadow: 0 0 0 2px rgba(36,175,232,0.2)` for WCAG compliance.
472
531
  - **Field Anatomy:**
473
532
  1. Label (`14px / 500`, color `#2B2B2B`, margin-bottom `8px`).
@@ -4,29 +4,30 @@
4
4
 
5
5
  ## Project: {{PROJECT_NAME}}
6
6
 
7
- This project follows the **HotelFriend Design System v0.1.0**. The design system is the SSOT for all UI work — tokens, components, states, spacing, typography. Do NOT introduce off-system colors, sizes, or one-off shadows.
7
+ This project follows the **HotelFriend Design System** (@hotelfriendag/design-tokens — version per the installed package, see CHANGELOG.md). The design system is the SSOT for all UI work — tokens, components, states, spacing, typography. Do NOT introduce off-system colors, sizes, or one-off shadows.
8
8
 
9
9
  ## Where the design system lives in this repo
10
10
 
11
- - **`docs/portable-design/components.html`** **PRIMARY visual reference** (open in browser). Canonical rendering of every component. When in doubt, look here.
12
- - `docs/portable-design/UI_DESIGN.md` — narrative SSOT (read this first for context, anatomy, decision rationale)
13
- - `docs/portable-design/tokens.figma.json`atomic tokens (Tokens Studio format)
14
- - `docs/portable-design/states-canonical.json` — canonical interactive states for 24 components (modal, alert, pagination, dropdown items, checkboxes etc.)
15
- - `docs/portable-design/pre-built/`:
16
- - `tailwind.css` (Tailwind v4, recommended) — `@theme {}` token bindings
17
- - `tailwind.preset.js` (Tailwind v3 legacy)
18
- - `tokens.css` (vanilla CSS variables)
19
- - `_tokens.scss` (SCSS variables)
20
- - `tokens.ts` (TypeScript const)
21
- - `shadcn-tokens.css` (shadcn/ui contract)
22
- - **`components.css`** — `.hf-*` component primitives (extracted from components.html — import this in your CSS!)
23
- - `docs/portable-design/portal-audit.html` **archival** legacy portal snapshot. Do NOT copy patterns from here for new code.
11
+ Files are shipped in the `@hotelfriendag/design-tokens` npm package. After `pnpm add @hotelfriendag/design-tokens` they are available via subpath exports:
12
+
13
+ - **`node_modules/@hotelfriendag/design-tokens/components.html`****PRIMARY visual reference** (open in browser, or use the public URL https://unpkg.com/@hotelfriendag/design-tokens/components.html). Canonical rendering of every component. When in doubt, look here.
14
+ - `node_modules/@hotelfriendag/design-tokens/UI_DESIGN.md` — narrative SSOT (read this first for context, anatomy, decision rationale)
15
+ - `node_modules/@hotelfriendag/design-tokens/states-canonical.json` — canonical interactive states for 24 components (modal, alert, pagination, dropdown items, checkboxes etc.)
16
+ - Import paths (use `@hotelfriendag/design-tokens/<file>` in build tools):
17
+ - `@hotelfriendag/design-tokens/tailwind.css` (Tailwind v4, recommended) — `@theme {}` token bindings
18
+ - `@hotelfriendag/design-tokens/tokens.css` (vanilla CSS variables)
19
+ - `@hotelfriendag/design-tokens/components.css` `.hf-*` component primitives
20
+ - `@hotelfriendag/design-tokens/status.css` — `.status-{domain}-{state}` rules (generated from status-map.json)
21
+ - `@hotelfriendag/design-tokens/pre-built/_tokens.scss` (SCSS variables — Dart Sass: `@use '@hotelfriendag/design-tokens/pre-built/tokens' as ds`)
22
+ - `@hotelfriendag/design-tokens/tokens.ts` (TypeScript const)
23
+ - `@hotelfriendag/design-tokens/shadcn-tokens.css` (shadcn/ui contract)
24
+ - `portal-audit.html` — **archival** legacy portal snapshot. Do NOT copy patterns from here for new code.
24
25
 
25
26
  ## Hard rules — never violate these
26
27
 
27
- 1. **Colors:** Use only tokens defined in `pre-built/tailwind.css` / `pre-built/tokens.css`. Never hard-code hex codes. The primary brand color is `#24AFE8` (CSS var `--color-primary`, Tailwind `primary`). The four status colors are success `#59B59D`, warning `#FFBD5A`, error `#EA6565`, coral `#F87921`.
28
- 2. **Status badges:** Use `.hf-pill .status-{domain}-{state}`. Color is `var(--color-badge-{domain}-{state}-color)`, background is `var(--color-badge-{domain}-{state}-bg)` (15% alpha). Cancellation reasons (`canceled-by-hotel/guest/hf/pos`) all share the `.status-cancel` alias. Do NOT invent new status colors.
29
- 3. **Component primitives — reuse, never rebuild:** Before writing a modal, alert, dropdown, pagination, tabs, checkbox, or toggle, **check `pre-built/components.css` for an existing `.hf-*` primitive**. Use `.hf-modal`, `.hf-alert`, `.hf-pagination`, `.hf-dropdown-menu`, `.hf-check`, etc. with their BEM-style children (`__header`, `__body`, `__footer`, `__icon`, `__title`, `__close`, etc.) and modifiers (`--success`, `--tinted`, `--banner`, `--compact`, `--sm`). See `UI_DESIGN.md` §5.1 for the full list.
28
+ 1. **Colors:** Use only tokens defined in `@hotelfriendag/design-tokens/tailwind.css` / `@hotelfriendag/design-tokens/tokens.css`. Never hard-code hex codes. The primary brand color is `#24AFE8` (CSS var `--color-hf-accent`, SCSS `$colorAccent`, Tailwind `bg-hf-accent`). The four status colors are success `#59B59D`, warning `#FFBD5A`, error `#EA6565`, coral `#F87921`.
29
+ 2. **Status badges:** Use `.hf-pill .status-{domain}-{state}` (rules in `status.css`). Each class sets `color`/`background` from a semantic slot — `var(--color-hf-status-{role})` and `var(--color-hf-status-{role}-bg)` (15% alpha) — and the domain→role map lives in `status-map.json`. Cancellation reasons (`canceled-by-hotel/guest/hf/pos`) all share the `.status-cancel` alias. Do NOT invent new status colors.
30
+ 3. **Component primitives — reuse, never rebuild:** Before writing a button, input, modal, alert, dropdown, pagination, tabs, checkbox, toggle, card, or drawer, **check `@hotelfriendag/design-tokens/components.css` for an existing `.hf-*` primitive**. Available: `.hf-btn` (`--primary` / `--danger` / `--outline-primary` / `--outline-default` / `--cancel`, sizes `--sm` / `--icon`); `.hf-input` / `.hf-textarea` / `.hf-select` (wrap `<select>` in `.hf-select-wrap`); `.hf-switch`; `.hf-form-field` (`__label` / `__hint` / `__error`, state `--error`); `.hf-card`; `.hf-drawer`; `.hf-modal`; `.hf-alert`; `.hf-pagination`; `.hf-dropdown-menu`; `.hf-check` / `.hf-radio`; `.hf-pill`; `.hf-tab`; `.hf-toast` (`--success` / `--error` / `--warn` / `--info`); `.hf-table`; `.hf-empty` (`--compact` / `--row`); invalid state `[aria-invalid="true"]` / `.hf-input--error`. Use BEM-style children (`__header`, `__body`, `__footer`, `__title`, `__close`, `__item`, ) and modifiers (`--primary`, `--success`, `--tinted`, `--banner`, `--compact`, `--sm`). For dropdowns the canonical children are `.hf-dropdown-menu__header / __item / __item-icon / __shortcut / __divider` and modifier `.hf-dropdown-menu__item--danger` (the older flat `.hf-dropdown-{header|item|divider}` names still work as deprecated aliases). See `UI_DESIGN.md` §5.1 for the full list.
30
31
  4. **Typography:** Family is **Roboto**. Sizes from canonical scale only: 11/13/14/15/16/18/20/24/26/30 px. Weights only 400 / 500 / 600. Line-heights 1.2 (headings), 1.5 (body), 1 (chips).
31
32
  5. **Spacing:** Multiples of 4 only. Default section gap is 20px. Never write 7px / 13px / 18px — round to the scale. (Exception: portal-exact padding `7px 10px 9px` for `.hf-alert` `help-block` is documented in components.html.)
32
33
  6. **Border radius:** `6px` (buttons/inputs/checkboxes/modals/alerts), `8px` (pagination items), `9px` (dropdowns/toasts/pill-tabs), `12px` (large cards), `99px` (status pills are 6px square — pills are NOT round). Never invent intermediate values.
@@ -35,29 +36,50 @@ This project follows the **HotelFriend Design System v0.1.0**. The design system
35
36
  9. **Pagination active state:** Pagination active is a **subtle gray** (`bg: #E4E8EF; color: #252F4A`) — NOT primary blue. This is portal-exact and easy to get wrong. Use `.hf-pagination__item.is-active`.
36
37
  10. **Components:** Before creating a new component, check `components.html` for an existing equivalent (open in browser, search section IDs `#buttons`, `#inputs`, `#cards`, `#status`, `#tabs`, `#dropdown`, `#modal`, `#alerts`, `#pagination`, `#empty`, `#layout`). Reuse beats duplicate.
37
38
 
39
+ ## Accessibility — required markup
40
+
41
+ The design system ships CSS only; the **JS behaviors and ARIA wiring below are YOUR responsibility**
42
+ (focus trap, arrow-key navigation, Escape/dismissal, focus return, `aria-live`). Always supply the
43
+ required markup for these components:
44
+
45
+ - **Modal** — `role="dialog"` + `aria-modal="true"` + a labelled title (`aria-labelledby`) + a focus trap + Escape-to-close.
46
+ - **Drawer** — same as modal (dialog semantics, focus trap, Escape).
47
+ - **Tabs** — `role="tablist"` / `role="tab"` / `role="tabpanel"` + `aria-selected` + arrow-key navigation.
48
+ - **Dropdown menu** — `role="menu"` / `role="menuitem"` + arrow keys + Escape + focus return to the trigger on close.
49
+ - **Switch** — a native `<input type="checkbox">` (or `role="switch"`) with an associated `<label>`.
50
+ - **Checkbox / radio** — native `<input type="checkbox|radio">` (`.hf-check` / `.hf-radio` style native inputs).
51
+ - **Icon-only buttons** — `aria-label` (the icon is not an accessible name).
52
+ - **Pagination** — wrap in `<nav aria-label="Pagination">`.
53
+ - **Toasts** — render inside an `aria-live="polite"` region.
54
+ - **Tables** — `<th scope="col">` / `<th scope="row">` on header cells.
55
+
56
+ WCAG 2.1 **AA is the floor.** Keep `:focus-visible` outlines (the design system ships them — never
57
+ remove without a visual replacement) and don't rely on placeholder text to convey required
58
+ information (placeholder contrast is intentionally below AA).
59
+
38
60
  ## When the user asks for UI work
39
61
 
40
- 1. **Read `UI_DESIGN.md`** — start here for new sessions.
41
- 2. **Open `components.html`** in a browser — see the canonical rendering.
42
- 3. **Pick existing `.hf-*` primitives** from `pre-built/components.css` first. Use Tailwind utility classes only for layout (flex/grid/spacing) — not for component chrome (colors, shadows, borders).
43
- 4. **States matter:** every interactive element needs default + hover + focus + disabled + loading. See `states-canonical.json` for the exact declarations to mirror.
62
+ 1. **Read `UI_DESIGN.md`** (at `node_modules/@hotelfriendag/design-tokens/UI_DESIGN.md`) — start here for new sessions.
63
+ 2. **Open `components.html`** in a browser (`node_modules/@hotelfriendag/design-tokens/components.html`, or https://unpkg.com/@hotelfriendag/design-tokens/components.html) — see the canonical rendering.
64
+ 3. **Pick existing `.hf-*` primitives** from `@hotelfriendag/design-tokens/components.css` first. Use Tailwind utility classes only for layout (flex/grid/spacing) — not for component chrome (colors, shadows, borders).
65
+ 4. **States matter:** every interactive element needs default + hover + focus + disabled + loading. See `states-canonical.json` (`node_modules/@hotelfriendag/design-tokens/states-canonical.json`) for the exact declarations to mirror.
44
66
  5. **No "raw HTML" answers** — wrap markup in the documented component class system. If the design system lacks something, propose a NEW token or primitive in a comment, do not silently introduce one.
45
67
 
46
68
  ## When the user asks for "the same look as the portal"
47
69
 
48
- The visual reference is `components.html`. If the user asks "make a settings page like the portal", do this in order:
70
+ The visual reference is `components.html` (`node_modules/@hotelfriendag/design-tokens/components.html` or https://unpkg.com/@hotelfriendag/design-tokens/components.html). If the user asks "make a settings page like the portal", do this in order:
49
71
 
50
72
  1. Open `components.html`, find Page Layouts section (`#layout`). Pick the matching pattern (List / Detail / Form / Dashboard).
51
73
  2. Replicate the structure (Title bar → Tabs → Filters → Data grid → Footer pagination).
52
- 3. Use only `.btn-primary`, `.btn-outline-primary`, `.btn-cancel`, `.btn-danger` for buttons.
74
+ 3. Use `.hf-btn` + a variant (`.hf-btn--primary`, `.hf-btn--outline-primary`, `.hf-btn--cancel`, `.hf-btn--danger`) for buttons.
53
75
  4. Use `.hf-pill .status-{domain}-{state}` for status indicators.
54
- 5. Wrap data sections in `.bg-white rounded-lg border border-border p-5` (the bordered card pattern most common in portal).
76
+ 5. Wrap data sections in `.hf-card` (`__header` / `__body` / `__footer`), or the lighter bordered pattern `.bg-white rounded-lg border border-border p-5` — both common in the portal.
55
77
  6. Use `.hf-pagination` for pager.
56
78
  7. Use `.hf-modal` / `.hf-alert` / `.hf-dropdown-menu` for floating UI.
57
79
 
58
80
  ## Anti-patterns
59
81
 
60
- - ❌ `<button class="bg-blue-500 text-white px-4 py-2 rounded">Save</button>` (Tailwind native classes for what should be a `.btn.btn-primary`)
82
+ - ❌ `<button class="bg-blue-500 text-white px-4 py-2 rounded">Save</button>` (Tailwind native classes for what should be a `.hf-btn.hf-btn--primary`)
61
83
  - ❌ Inventing colors like `#1F8FCE` because "it's close to primary"
62
84
  - ❌ Border-radius `8px` because "it looks better than 6px" (8px is reserved for pagination items only)
63
85
  - ❌ Adding a new shadow `0 4px 12px rgba(...)` instead of `var(--shadow-hf-modal)`
@@ -67,7 +89,7 @@ The visual reference is `components.html`. If the user asks "make a settings pag
67
89
  - ❌ Pagination with `bg-hf-primary text-white` active — use `.hf-pagination__item.is-active` (subtle gray)
68
90
  - ❌ Using native `<input type="checkbox" class="accent-primary">` instead of `<input type="checkbox" class="hf-check">` — accent-color doesn't match portal's filled-blue-with-white-tick design
69
91
  - ❌ Mixing icon sets (Lucide + Font Awesome + Heroicons in the same screen)
70
- - ❌ Copying patterns from `portal-audit.html` — that's the legacy snapshot, NOT canonical reference
92
+ - ❌ Copying patterns from `portal-audit.html` (`node_modules/@hotelfriendag/design-tokens/portal-audit.html`) — that's the legacy snapshot, NOT canonical reference
71
93
 
72
94
  ## When in doubt
73
95
 
@@ -75,6 +97,6 @@ Ask the user. Better to pause for 5 seconds than ship a one-off color that drift
75
97
 
76
98
  ---
77
99
 
78
- **System version:** Design System v0.1.0
100
+ **System version:** @hotelfriendag/design-tokens version per the installed package (see CHANGELOG.md)
79
101
  **Maintained:** alongside `UI_DESIGN.md`
80
- **Refresh policy:** when `tokens.figma.json` changes, run `node generate-tokens.cjs --target=tailwind-v4 > pre-built/tailwind.css` (and repeat for other targets as needed). Script is in the same folder as `tokens.figma.json`. The `pre-built/components.css` file is hand-extracted from `components.html`'s `@layer components` block re-sync manually after component changes.
102
+ **Refresh policy:** when `tokens.figma.json` changes in the design-system repo, run `npm run build` (which runs `node generate-tokens.cjs` for all targets). The `pre-built/components.css` file is generated from `src/components.css` by `node generate-tokens.cjs --target=components-css`; hand-edits to `pre-built/` are wiped by the next build and CI fails on drift. Consumer repos update with `pnpm update @hotelfriendag/design-tokens`.