@fracazo/design-system 0.2.0 → 0.2.1

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.
Files changed (3) hide show
  1. package/DESIGN.md +361 -8
  2. package/README.md +3 -2
  3. package/package.json +1 -1
package/DESIGN.md CHANGED
@@ -1,12 +1,365 @@
1
+ ---
2
+ name: fracazo-design-system
3
+ description: The written authority for products built on @fracazo/design-system. Judgment, composition and the rejection list for surfaces a parent under pressure will read; the stylesheet and the guardrails do the visual work.
4
+ version: 1.0.0
5
+ applies_to: any product that imports @fracazo/design-system/roles.css and one brand file
6
+ enforced_by: css/roles.css (brand contract, held by ds-check-brand), @fracazo/design-system/eslint, each product's design snapshot harness
7
+ ---
8
+
1
9
  # DESIGN.md
2
10
 
3
- The written authority for products built on `@fracazo/design-system`: what
4
- the reader's job is, how a page is composed, what the system refuses to
5
- ship. Authored in the next phase. Until then the machine-readable parts
6
- already exist and are enforced:
11
+ This file is for whoever builds a surface on the system, person or agent.
12
+ It says what the reader's job is, how a page is composed, what the system
13
+ refuses to ship, and where each brand differs. It is deliberately short.
14
+ The stylesheet carries the visual decisions; this file carries judgment,
15
+ and gains a rule only when the same correction has been made twice.
16
+
17
+ Read `css/roles.css` for the roles and the brand contract, the component
18
+ source in `src/ui/*` for each component's intent, and the product's own
19
+ CLAUDE.md for the rules that are local to it.
20
+
21
+ ## Who the reader is
22
+
23
+ Every product on this system is used by people close to a birth: a parent
24
+ in the third trimester on a phone, a partner at a bedside on hospital
25
+ wifi, someone reading at 3am with one hand. They are tired, sometimes
26
+ frightened, and they came for one answer. The surface earns trust by being
27
+ fast, calm and exact, the way a good midwife is. It never earns it with
28
+ decoration, hype or novelty.
29
+
30
+ Two products run on it today: BirthGuide (Australia) and birthplans.app
31
+ (English-speaking markets, primarily the US). Their chapters are at the
32
+ end. Everything before them applies to both, and to the next product.
33
+
34
+ ## Priority order
35
+
36
+ When rules conflict, the lower number wins.
37
+
38
+ 1. **Performance is the brand.** A fast page feels more premium than any
39
+ palette. If a feature cannot meet its surface's budget, cut or simplify
40
+ it until it can.
41
+ 2. **Preserve the reader's decision.** Facts, options and consequences stay
42
+ exact and complete. Copy claims only what the product delivers.
43
+ 3. **Respect the host and the contract.** Work inside the product's
44
+ framework, its brand file and this package's roles. Never fork a
45
+ component or invent a token locally; raise it in the system.
46
+ 4. **Keep the surface unmistakably the product's.** The brand file does
47
+ this. Do not add a second accent, a new typeface or a new radius to
48
+ make a page feel special.
49
+ 5. **Choose a specific composition.** Let the material set the layout.
50
+ Reject the template reflex (centred hero, three cards, metric boxes).
51
+
52
+ ## Two surface scopes
53
+
54
+ Name the scope before designing anything.
55
+
56
+ **Engagement surfaces** are the product itself: questionnaire, plan
57
+ editor, the published plan page, downloads, account. Failure mode: "it
58
+ did not load when I needed it." Strict budgets (cold start under 1.5s on
59
+ a three-year-old phone on slow 4G, first paint under 1s), no hero imagery,
60
+ no decorative motion, optimistic UI on every write, tap targets at least
61
+ 44px, inputs at least 16px so iOS never zooms.
62
+
63
+ **Conversion surfaces** are the landing pages, guides and articles a
64
+ reader sees while deciding. Failure mode: "it looked cheap, or it was slow
65
+ and search buried it." Core Web Vitals "Good" thresholds apply (LCP under
66
+ 2.5s, CLS under 0.1, INP under 200ms, first load under 1MB). Illustration,
67
+ one custom typeface and staged entrance motion are permitted within those
68
+ budgets. The hospital case still applies: a landing page opened on a ward
69
+ tour is still read under pressure.
70
+
71
+ ## Work in four passes
72
+
73
+ **Frame the reader's job.** Who is reading, what they must decide or do
74
+ on this surface, what the one strongest piece of evidence or the one
75
+ primary action is. Support two reading speeds: the scan (headings, the
76
+ key value, the primary button) and the careful read (the detail, the
77
+ caveat, the "if necessary" path).
78
+
79
+ **Choose the composition.** One focal relationship per screen. Prose for
80
+ reasoning, a list for parallel items, a table for exact comparison, a
81
+ card only when the surface is genuinely a unit the reader picks up. Cards
82
+ do not nest. Emphasis is spent once: a surface that carries a tinted
83
+ border, a heavy shadow, an uppercase chip and an accent button at the
84
+ same time is shouting, and the fix is always to remove signals, not to
85
+ add a stronger one.
86
+
87
+ **Apply the system.** Semantic utilities first, primitives where no
88
+ semantic role fits, tokens for everything that varies by theme, named
89
+ roles for every size and radius. The sections below say how.
90
+
91
+ **Inspect privately, then verify with tooling.** Read the page aloud;
92
+ if the rhythm is wrong, rewrite. Check both themes and a 360px viewport.
93
+ Then run the product's snapshot compare: a refactor must compare
94
+ identical, and an intentional change must show only the keys you meant
95
+ to move, with the count stated in the handback.
96
+
97
+ ## The visual system
98
+
99
+ ### Tokens, two tiers
100
+
101
+ Primitives hold literals and live in the brand file: `--band`, `--ink`,
102
+ `--line`, `--brand`, `--dark` and their families. Semantics are the roles
103
+ components consume: `--primary`, `--border`, `--muted-foreground`. Eight
104
+ semantics alias a primitive with `var()` in `roles.css`, once, because
105
+ they are identical in light and dark: foreground, card, popover, primary,
106
+ accent, ring and the card and popover foregrounds. Six diverge in dark
107
+ and hold brand-tuned literals in the brand file: secondary, muted, border,
108
+ input, muted-foreground, accent-foreground. Dark border and input as
109
+ translucent white hairlines is a design decision. Never "fix" a divergent
110
+ semantic by aliasing it; the dark rendering changes.
111
+
112
+ Every token has a utility (`bg-band`, `text-ink-3`, `shadow-card`,
113
+ `rounded-20`, `text-display`, `py-band`). The utility is the only way a
114
+ value reaches a component.
115
+
116
+ ### Colour
117
+
118
+ Semantic utilities first: `bg-background`, `bg-card`, `text-foreground`,
119
+ `text-muted-foreground`, `border-border`, `ring-ring`, `bg-primary`.
120
+ Primitives where no role fits:
121
+
122
+ - Alternating page bands: `bg-band`, then `bg-band-2` or `bg-surface-2`.
123
+ - Captions and hints on light surfaces: `text-ink-3`.
124
+ - Accent chips: `bg-chip-1-soft text-chip-1-ink`, `chip-2`, `chip-3`.
125
+ - The "new" or "note" pair: `bg-highlight text-highlight-ink`, with
126
+ `highlight-soft` and `highlight-deep` for two-tone stat pills.
127
+ - Preference state, and only preference state: `status-want`,
128
+ `status-ifnec`, `status-no`, each with a `-soft` wash. Colour carries
129
+ meaning here; it is never decoration.
130
+
131
+ Always-dark surfaces (footer, showcase bands) are `bg-dark` or `bg-dark-2`
132
+ and their text comes from the mode-constant on-dark ramp, `text-dark-ink`
133
+ down to `text-dark-faint-2`, with `text-dark-brand` for the one accent.
134
+ A theme-varying token such as `text-ink` on a dark band renders espresso
135
+ on espresso in light mode.
136
+
137
+ Design in the neutrals; the brand hue appears on the primary action,
138
+ links, the focus ring and small accents. Never use Tailwind's stock
139
+ palette (`amber-500`, `blue-600`) in product UI; it competes with the
140
+ brand and looks generic.
141
+
142
+ ### Radius
143
+
144
+ The ramp derives from the brand's `--radius`: `rounded-sm` through
145
+ `rounded-4xl` are calc offsets. One absolute value sits off the ramp,
146
+ `rounded-20`. There are no other radii. Where a rounded child's corners
147
+ co-locate with a rounded parent's, the outer radius is the inner radius
148
+ plus the padding, and the result is always a named token (`rounded-lg`
149
+ plus `p-4` gives `rounded-4xl`; `rounded-md` plus `p-3` gives
150
+ `rounded-20`; `rounded-sm` plus `p-4` gives `rounded-3xl`). Concentric
151
+ corners read as one shape; equal radii at different depths read as a
152
+ mistake. The rule applies where corners meet, not to every nested corner.
153
+
154
+ Text buttons are rounded rectangles on a size-scaled ladder (xs `sm`,
155
+ sm `md`, default `lg`, lg `xl`); icon-only buttons are circles.
156
+
157
+ ### Type
158
+
159
+ One sans family per product, set by the brand file's `@theme` block, and
160
+ one mono. Headings are weight 600, tracking -0.02em, `text-wrap: balance`;
161
+ paragraphs `text-wrap: pretty`. Numbers that line up in columns use
162
+ tabular numerals; headline figures stay proportional.
163
+
164
+ Fluid sizes are named roles, never `text-[clamp(...)]`: `text-display`
165
+ for the one display headline (line-height 0.9 travels with it),
166
+ `text-section-title` for section headings, `text-lede` for the lede
167
+ under them (leading is left to the use site). Landing sections keep their
168
+ vertical rhythm with `py-band` and `mt-band-gap`. A size the roles do not
169
+ cover is a token to add, not a literal to type; a deliberate one-off
170
+ carries an inline lint disable that states why.
171
+
172
+ Mono is for code, identifiers and the small uppercase kicker label with
173
+ wide tracking, and nothing else. All caps appears only on that label.
174
+
175
+ ### Surfaces and shadows
176
+
177
+ Depth comes from the shadow-border system (`shadow-card`,
178
+ `shadow-card-hover`, `shadow-card-selected`, which embeds the primary as
179
+ a ring) rather than from flat borders, and from the warm soft shadows on
180
+ landing cards (`shadow-warm-sm` to `-lg`; the brand file swaps them for
181
+ black-based shadows in dark, where light shadows vanish). `shadow-bar` is
182
+ the upward shadow under a floating bottom bar. Spacing separates before a
183
+ border does. No glass, no decorative gradients, no dark rounded frames
184
+ around content.
185
+
186
+ ### Motion
187
+
188
+ Default to stillness. Interactive state changes (hover, select, expand)
189
+ use CSS transitions, which retarget when interrupted mid-flight.
190
+ Keyframes are reserved for one-shot staged sequences such as a hero
191
+ entrance, gated to play once per visit so a reload does not replay them,
192
+ and collapsed to nothing under `prefers-reduced-motion`. Nothing the
193
+ reader needs is behind an animation. Ambient effects (glow blobs) must
194
+ not clip at a section edge; a hard horizontal seam where two sections
195
+ meet is a defect.
196
+
197
+ ### Imagery and icons
198
+
199
+ Engagement surfaces carry no photography and no decorative illustration;
200
+ icons are SVG (lucide). Conversion surfaces may use illustration over
201
+ photography, custom, editorial and restrained, never stock, never
202
+ photographs of pregnant women and babies, never carousels or autoplay.
203
+ Icons label actions when they make the action faster to recognise; they
204
+ never decorate a heading.
205
+
206
+ ### Accessibility
207
+
208
+ WCAG AA (4.5:1 for body text, 3:1 for large text and controls). Rose or
209
+ periwinkle text on a light surface uses the brand ink, not the brand.
210
+ Native controls where they exist; the date picker is the OS one. Focus is
211
+ shown with `focus-visible`, so a modal opened with the mouse does not
212
+ paint a ring on its close button while keyboard users still get one.
213
+ Source order is reading order; landmarks and heading levels are real;
214
+ 44px targets on coarse pointers.
215
+
216
+ ## Components
217
+
218
+ The package ships seventeen shadcn-based components under
219
+ `@fracazo/design-system/ui/*`. Each carries an intent block at the top of
220
+ its source (one line, then Use for, Avoid when, Variants); that block is
221
+ the contract and is updated whenever variants change. In brief:
222
+
223
+ - **Button** is the tappable action primitive. Default carries the one
224
+ primary action of a step; outline is the standard secondary; ghost is
225
+ back, cancel and edit-in-place; destructive is reserved for irreversible
226
+ acts. Default height 48px; sm in compact rows; lg only for
227
+ money-adjacent hero actions.
228
+ - **Input**, **Textarea**, **Label** and the **Form** glue serve
229
+ react-hook-form fields. Textarea auto-grows. A field outside RHF uses
230
+ Label plus Input directly.
231
+ - **Checkbox**, **RadioGroup** and **Select** are for dense or
232
+ utilitarian UI. Questionnaire answers use the house icon-card groups,
233
+ never bare radios or checkboxes.
234
+ - **Dialog** is a centred modal for a focused task (a preview, a
235
+ confirmation). **Sheet** is the mobile bottom sheet for supplementary
236
+ content; on desktop the same content renders as an aside. **Popover**
237
+ anchors transient UI to a control without moving focus.
238
+ - **Accordion** is progressive disclosure inside the app; a public FAQ
239
+ uses native details and summary so answers exist without JavaScript.
240
+ **Tabs** switch between peer views of equal weight; inactive panels
241
+ unmount, so never for content search engines must see.
242
+ - **Card** is the stock surface family for quick composition; the house
243
+ card (rounded-20, warm shadow) is composed from utilities.
244
+ - **Progress** is a flat determinate bar; brand-moment progress bars are
245
+ bespoke. **Calendar** exists for range selection; a single date uses a
246
+ native input. **SortableList** ranks a small capped set with drag,
247
+ touch and keyboard.
248
+
249
+ A component that must import app code (stores, data clients, routes)
250
+ does not belong in the package; it stays in the product, like
251
+ `place-autocomplete`. Dependencies point one way: the app consumes the
252
+ system, never the reverse.
253
+
254
+ ## Guardrails and verification
255
+
256
+ - **Brand contract.** `roles.css` lists every property a brand file must
257
+ define, light, dark and theme. `ds-check-brand` fails lint when a file
258
+ defines less or more. A new role is a minor version and a changelog
259
+ note; a contract change a brand must satisfy anew is a major version.
260
+ - **ESLint.** No raw colour in a `className` (hex, oklch, rgb, hsl,
261
+ including arbitrary utilities) and no `text-[clamp(...)]`. Renderers
262
+ that cannot use CSS variables (react-pdf, email HTML, OG images) are
263
+ exempt per product; nothing else is.
264
+ - **Zero visual change.** Token and component refactors are verified by
265
+ each product's snapshot harness, which rasterises resolved colours to
266
+ sRGB bytes and probes the utility layer and real pages, never by eye.
267
+ Snapshot before editing; compare after; clear the framework's build
268
+ cache after restructuring the CSS import graph. An intentional change
269
+ regenerates the committed baseline in the same branch and names its
270
+ delta.
271
+
272
+ ## Reject these reflexes
273
+
274
+ Each of these has been corrected more than once. Treat them as defects.
275
+
276
+ - A colour literal in a class, in any form, including a "temporary" one.
277
+ - A hand-authored light and dark pair (`bg-[...] dark:bg-[...]`). The
278
+ token owns both themes; write one class.
279
+ - Theme-varying text on an always-dark surface.
280
+ - A new radius literal, or equal radii on nested corners that meet.
281
+ - An arbitrary clamp() size where a named role exists, or a near-miss
282
+ clamp that drifts a few pixels from one.
283
+ - Four emphasis signals on one card. Remove until one remains.
284
+ - A focus ring painted for pointer users. Use `focus-visible`.
285
+ - A hard seam where an ambient glow is clipped at a section boundary.
286
+ - Tailwind's stock palette in product UI.
287
+ - Values invented in a story or a demo. Storybook and the showcase are
288
+ consumers; a missing value is a finding to raise.
289
+ - Em dashes, anywhere: copy, comments, commit messages, generated
290
+ documents. Use commas, colons, full stops, parentheses.
291
+ - Wellness-speak ("your journey", "mama", "you've got this"), filler
292
+ affirmations ("amazing", "incredible"), AI marketing words
293
+ ("seamless", "empower", "unlock", "cutting-edge", "revolutionise").
294
+ - All caps outside the micro kicker label, or Title Case in a heading,
295
+ button or label. The system is sentence case.
296
+ - Centred hero plus three cards, metric boxes, badges as metadata,
297
+ nested cards, decorative gradients, glass, stock imagery, carousels,
298
+ autoplay, a visible theme switcher.
299
+
300
+ Do not answer restraint with a sterile template. Restraint here is a
301
+ precise hierarchy, one warm accent used with intent, exact evidence and
302
+ strong alignment, on a page that loads before the reader's patience runs
303
+ out.
304
+
305
+ ## Brand: BirthGuide
306
+
307
+ Australian, trusted, quiet. A companion through pregnancy and the first
308
+ weeks, endorsed by midwives; it reads as something a midwife would
309
+ recommend without hesitation, never as a children's app. Australian
310
+ English throughout: caesarean, labour, antenatal, nappy. "Birth plan" for
311
+ search, "birth preferences" in clinical and community copy; both coexist.
312
+
313
+ - **Palette.** Cream page (`#FAF6EF`), espresso ink (`#2B2620`), rose
314
+ accent (`--brand` `#C2727A`, `--brand-ink` for text on light). Dark is
315
+ espresso surfaces with cream text and a lightened rose.
316
+ `--headline-accent` rides the brand ink in light and turns warm sand in
317
+ dark, because on the espresso page rose barely separates from body
318
+ text. `--dark-brand` (`#DE949A`) is the one non-grey in the on-dark
319
+ ramp, used for the footer heart. `chip-3` is the sister product's
320
+ periwinkle; its ink is that product's exact brand value and is not
321
+ nudged.
322
+ - **Type.** Hanken Grotesk via next/font (`--font-hanken`), Geist Mono,
323
+ both `display: optional` so text never blocks on a webfont.
324
+ - **Surfaces.** Landing page and guides to convert; questionnaire, plan
325
+ editor, published plan page and downloads to engage. Illustration is
326
+ welcome on the landing page and absent from the product.
327
+ - **Repo.** `~/Developer/birthguide`. Brand file
328
+ `src/system/brands/birthguide.css`; `public/brand.css` is the served
329
+ token API, generated by `ds-build-brand-css` and checked in lint. The
330
+ local Storybook is the component gallery for both products; it consumes
331
+ the package and defines nothing.
332
+
333
+ ## Brand: birthplans.app
334
+
335
+ The same warm system with the brand hue rotated to periwinkle, for
336
+ English-speaking markets, primarily the US. One output: the birth plan
337
+ PDF, paginated to match its live preview. US English throughout: cesarean,
338
+ labor, color, anesthesiologist, operating room; `nitrous oxide (gas and
339
+ air)`, `oxytocin drip (Pitocin)`. Same voice rules as BirthGuide.
340
+
341
+ - **Palette.** Cream and espresso as BirthGuide; `--brand` `#7480C6`
342
+ periwinkle, `--brand-ink` `#4A529E`, `--brand-soft` `#E8EAF8`; dark
343
+ brand at hue 276. `--headline-accent` is warm sand in both modes, and
344
+ the hero applies it only in dark. `accent-foreground` follows the brand
345
+ ink in light, the one semantic this product tuned. `chip-3` is BirthGuide's
346
+ rose, reasoned and unused. The status ramp is live in the questionnaire:
347
+ want, if necessary, don't want, each with a soft wash.
348
+ - **Type.** Hanken Grotesk and Geist Mono, as BirthGuide.
349
+ - **Guardrails.** The colour rule is on. The fluid-type rule is off until
350
+ a type-role pass converges the landing sections' fourteen clamp
351
+ literals onto the named roles; switch to `designSystemGuardrails()` when
352
+ it lands.
353
+ - **Repo.** `~/Developer/birthplans`. Brand file
354
+ `src/system/brands/birthplans.css`. No Storybook and no served
355
+ `brand.css`; BirthGuide's Storybook documents the shared components.
7
356
 
8
- - The brand contract at the top of `css/roles.css`, held by `ds-check-brand`.
9
- - The two ESLint guardrails in `@fracazo/design-system/eslint`.
357
+ ## Maintaining this file
10
358
 
11
- Prose rules will be added here only when a correction recurs. The
12
- stylesheet does the visual work; this file exists for judgment.
359
+ Add a prose rule here only when the same correction has been made twice;
360
+ once is a review comment. Prefer moving a rule into the stylesheet or the
361
+ guardrails over writing it down: a token, a lint rule or a contract entry
362
+ enforces itself. Brand chapters state what differs and nothing that the
363
+ core already says. Semantic versioning by hand: a clarification is a
364
+ patch, a new rule a minor, a change to the priority order or the surface
365
+ scopes a major.
package/README.md CHANGED
@@ -19,10 +19,11 @@ product from.
19
19
  | `@fracazo/design-system` and `./ui/*` | `cn` and seventeen shadcn-based components (button, card, dialog, form, select, sortable-list and the rest), each with intent JSDoc: use for, avoid when, variants |
20
20
  | `@fracazo/design-system/eslint` | Two guardrails: no raw colours and no arbitrary fluid type sizes in a `className` |
21
21
  | `demo/index.html` | A showcase page that renders the roles in both modes off a served `/brand.css` |
22
- | `DESIGN.md` | The written authority: judgment, composition, the rejection list. Authored next |
22
+ | `DESIGN.md` | The written authority: who the reader is, the priority order, how a page is composed, the rejection list, one short chapter per brand |
23
23
 
24
24
  Brand files live in each product repo, not here. The contract is what keeps
25
- them honest.
25
+ them honest. A new product starts from the `design-system-starter` template:
26
+ Next 16, Tailwind v4, this package, a blank brand file and the guardrails on.
26
27
 
27
28
  ## Consume it
28
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fracazo/design-system",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Roles, brand contract, guardrails and shadcn-based components for a warm, evidence-led product design system. Each product supplies a brand file; the system stays the same.",
5
5
  "license": "MIT",
6
6
  "author": "Alex Fracazo",