fertig 1.1.0 → 3.0.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/CHANGELOG.md CHANGED
@@ -6,10 +6,111 @@ is the token names, the class names (the seven, plus the layout utilities),
6
6
  and the ARIA attributes the component layer reads — changes to any of those
7
7
  are breaking.
8
8
 
9
- ## [1.1.0] — 2026-08-31
9
+ ## [3.0.0] — 2026-08-31
10
+
11
+ The breaking change is one mechanical line: **every public token now carries a
12
+ `--fertig-` prefix, and the old names are gone** — no aliases, no shims. The
13
+ classes, the layout utilities and the ARIA attributes the component layer reads
14
+ are all untouched. The sheet does exactly what it did; the migration is a
15
+ rename.
16
+
17
+ `--fertig-a1` / `--fertig-a2` already carried the prefix for the reason the rest
18
+ receive it now: a short, unprefixed custom property is a bet you only lose
19
+ once. `--ac` collided with your own `--ac`; the prefix makes every token yours
20
+ to override, and no future rename is ever needed again.
21
+
22
+ **To migrate:** prefix whatever you override. `--w` → `--fertig-w`,
23
+ `--ac` / `--on-ac` → `--fertig-ac` / `--fertig-on-ac`, the radii
24
+ `--r` / `--rs` / `--rw` → `--fertig-*`, the surfaces (`--bg`, `--el`, `--face`,
25
+ `--fg`, `--mut`, `--bd`, `--tb`, `--nw`), the depth tokens (`--up`, `--up2`,
26
+ `--dn`, `--sh`, `--sh1`, `--sh2`, and the `@property` alphas `--a1` / `--a2`),
27
+ the motion tokens (`--ease`, `--ease-in`, `--ease-out`, `--dur-1`, `--dur-2`),
28
+ `--caps`, and the colour ramps (`--stone-*`, `--sage-*`, `--sky-*`, `--clay-*`,
29
+ `--plum-*`, `--gold-*`). The rule is one line: **any `--name` you set becomes
30
+ `--fertig-name`.** Nothing changed meaning.
10
31
 
11
- A visual refresh and a component-first repositioning. No API breaks — the same
12
- tokens, classes and attributes — but the default look changes visibly.
32
+ ### Changed
33
+
34
+ - **The palette moves to a cooler voice.** Neutrals are a cool graphite
35
+ (hue ≈ 258) with a faint violet cast, replacing the warm stone; the accent is
36
+ a violet-blue (hue 266), replacing indigo. Measured: the accent clears 6.21:1
37
+ as link text on paper and 6.39:1 for a white label on the fill in light,
38
+ 8.62:1 in dark — both sit inside sRGB. The
39
+ `@supports not (color: light-dark(…))` flat-sRGB fallback is restated to match,
40
+ and every pair still clears WCAG AA in both schemes.
41
+ - **Buttons are fully rounded.** The shared button rule takes a new
42
+ `--fertig-rp` (pill) token instead of the control radius. It matches the
43
+ badge, the switch and the progress bar, which were already pills. Set
44
+ `--fertig-rp: var(--fertig-r)` for square-shouldered buttons.
45
+ - **One radius, and the rest derived from it.** `--fertig-r` (12px) is the base;
46
+ `--fertig-rs` is `calc(var(--fertig-r) * .5)` and `--fertig-rw` is
47
+ `calc(var(--fertig-r) * 1.33)`, so setting the base on `:root` rescales the
48
+ whole sheet instead of desynchronising it. Windows — cards, dialogs, popover
49
+ menus, the select picker, `pre`, `details`, `fieldset`, `aside`, toasts —
50
+ now take `--fertig-rw` rather than the control radius, and full-bleed card
51
+ media follows the card it bleeds into.
52
+ - **Badges are uppercase.** They already used the caps *size*; they never got
53
+ the transform that `h5`, `h6` and `th` get.
54
+ - **The dialog lost its banded rules.** No hairline under the header or over
55
+ the footer by default; the separation is spacing. The rule now arrives as
56
+ scroll state — a scroll-driven animation fades it in under the header once the
57
+ body has scrolled and out of the footer at the end — so a dialog that fits
58
+ shows no rules at all. Title takes `--fertig-f`, the interface face, at
59
+ 600/1.1rem: a monospaced heading font reads as a terminal on a dialog.
60
+ - **The `err` tone is red, not clay.** `[data-tone=err]` borrowed
61
+ `--fertig-clay-700`, which at 50% lightness reads brown on a destructive
62
+ button. Now `oklch(52% .16 27)` / `oklch(74% .13 25)` — 5.97:1 with the white
63
+ label, and `--fertig-on-ac` still derives correctly in both themes.
64
+ - **The select picker animates open and closed**, with `display` and `overlay`
65
+ transitioned discretely so the close is not a pop.
66
+
67
+ ### Fixed
68
+
69
+ - **`accent-color` and `caret-color` ignored every local accent.** Both were
70
+ declared once on `:root`. They are inherited properties, so they resolved
71
+ there against the root accent and every descendant inherited that *computed
72
+ colour* — a `[data-accent]` or `[data-tone]` subtree repainted its buttons and
73
+ links but left its checkboxes, radios, ranges and caret on the root's tint.
74
+ Both are now re-declared on the elements that consume them.
75
+ - **The segmented control showed a vertical scrollbar.** `overflow-x: auto`
76
+ makes `overflow-y` compute to `auto` as well, and Safari paints a scrollbar
77
+ for a sub-pixel of overflow. The block axis is now clipped (`overflow: auto
78
+ clip`), and group children take their focus ring inside the edge
79
+ (`outline-offset: -3px`) so the clip cannot slice it.
80
+ - **The `<select>` marker was a different shape per engine.** Engines without
81
+ `appearance: base-select` fell back to a solid filled triangle while the
82
+ styleable picker drew a thin chevron. The fallback now draws the same
83
+ stroked chevron.
84
+ - **The docs site is built with Eleventy.** The pages moved out of hand-edited
85
+ HTML at the repo root into `src/` templates that build to `dist/`, with the
86
+ version flowing from `package.json` as the single source of truth. This is how
87
+ the sheet's own site is maintained; it is not part of the npm package, which
88
+ ships the two CSS files plus the README and licence.
89
+ - **The demo template pages are gone.** `app.html`, `app-invoice.html` and the
90
+ whole `templates/` set (`dashboard`, `article`, `pricing`, `signin`) are
91
+ removed from the site and repo; the docs no longer point at them.
92
+
93
+ ## [2.0.0] — 2026-08-31
94
+
95
+ ### Breaking
96
+
97
+ - **`--a1` / `--a2` are now `--fertig-a1` / `--fertig-a2`, with no alias.**
98
+ These are `@property` registrations, and a registration is global — it cannot
99
+ be layered, scoped or overridden. A two-character name meant a consumer's own
100
+ `--a1` was silently retyped to `<number>`, which is a bug that only a rename
101
+ can fix.
102
+
103
+ **To migrate:** if you set `--a1` or `--a2`, rename them. Nothing else
104
+ changes — the values mean the same thing and the elevation scale is
105
+ untouched. If you never set them, there is nothing to do.
106
+
107
+ Every other token name is unchanged. They are ordinary custom properties,
108
+ they are the documented API, and they are deliberately short.
109
+
110
+ A visual refresh, an audit pass, and a component-first repositioning. One token
111
+ rename aside, the surface is the same — the same classes and attributes — but
112
+ the default look changes visibly, so treat this as a redesign rather than a
113
+ drop-in bump.
13
114
 
14
115
  ### Added
15
116
 
@@ -116,21 +217,7 @@ purpose.
116
217
 
117
218
  ### Changed
118
219
 
119
- - **`--a1` / `--a2` are now `--fertig-a1` / `--fertig-a2`.** These are
120
- `@property` registrations, and a registration is global — it cannot be
121
- layered, scoped or overridden. Two-character names meant a consumer's own
122
- `--a1` was silently retyped to `<number>`. The other token names are
123
- deliberately short and are unchanged; they are ordinary custom properties and
124
- the documented API.
125
-
126
- **This stays a minor release**: the prefixed tokens read the bare ones as a
127
- fallback (`--fertig-a1: var(--a1, .08)`), so a sheet that set `--a1` on
128
- `:root` — the documented way — keeps working exactly as it did, verified in a
129
- browser. A *scoped* `--a1` further down the tree does not reach the shadows,
130
- but it never did: `--sh1` has always been computed on `:root`, so the
131
- fallback restores the capability that existed, not less. `--a1` itself is no
132
- longer registered, which is the whole point — nothing of yours gets retyped.
133
- Prefer the prefixed names in new code.
220
+ - `--a1` / `--a2` renamed — see **Breaking** above.
134
221
 
135
222
  ## [1.0.3] — 2026-08-30
136
223
 
@@ -259,10 +346,11 @@ layer is `@layer fertig` — a dot in a layer name would declare a sub-layer.
259
346
 
260
347
  ### Browser support
261
348
 
262
- - Two-year support policy: the sheet targets browsers from the last two years
263
- and carries nothing for the ones before them. It ships no vendor prefixes
264
- except the three no engine has replaced (`-webkit-text-size-adjust`, the
265
- autofill repaint, and the `progress` pseudo-elements).
349
+ - Two-year support policy: the sheet targets browsers from the last two years.
350
+ The only thing carried for the ones before them is the flat-sRGB palette
351
+ above ("a floor under the floor"). It ships no vendor prefixes except the
352
+ three no engine has replaced (`-webkit-text-size-adjust`, the autofill
353
+ repaint, and the `progress` pseudo-elements).
266
354
  - The sheet is wrapped in `@layer fertig`, so unlayered author CSS overrides it
267
355
  at any specificity. There is no `!important` anywhere in it: inside a cascade
268
356
  layer an important declaration outranks your own unlayered CSS, which would
package/README.md CHANGED
@@ -7,7 +7,7 @@ switched off.
7
7
 
8
8
  Link it, write ordinary HTML, and the page is finished.
9
9
 
10
- **32.8 KB raw · 8.4 KB gzipped · no build step · no dependencies · no JavaScript.**
10
+ **36.8 KB raw · 8.8 KB gzipped · no build step · no dependencies · no JavaScript.**
11
11
 
12
12
  ### What makes it different
13
13
 
@@ -32,7 +32,7 @@ Surveyed across the eleven most-used classless stylesheets on 2026-08-31
32
32
  It is *not* the smallest — Concrete.css is 1.2 KB gzipped, and most of the
33
33
  field is a fraction of this because it styles elements and stops there. Against
34
34
  the two classless sheets that also ship a component layer, fertig is the
35
- lightest: 8.4 KB against matcha's 8.6 KB and Pico classless at 10.2 KB, and the
35
+ lightest: 8.8 KB against matcha's 8.8 KB and Pico classless at 10.2 KB, and the
36
36
  only one of the three whose components work without JavaScript. Size is a
37
37
  constraint here, not the pitch.
38
38
 
@@ -56,6 +56,41 @@ attributes. The snippet is in the docs.
56
56
  That is the whole integration. Write semantic HTML; it looks finished.
57
57
  Open `index.html` for the full demo.
58
58
 
59
+ ## Upgrading from 2.x
60
+
61
+ 3.0 is breaking for one reason: **every public token now carries a `--fertig-`
62
+ prefix**, and the old names are gone — no aliases. The classes, the ARIA
63
+ attributes the components read, and the layout utilities are all unchanged.
64
+ The sheet's behaviour is the same; the migration is a rename.
65
+
66
+ If you override tokens, prefix what you set. The common ones:
67
+
68
+ | 2.x | 3.0 |
69
+ |---|---|
70
+ | `--ac` | `--fertig-ac` |
71
+ | `--on-ac` | `--fertig-on-ac` |
72
+ | `--w` | `--fertig-w` |
73
+ | `--r` / `--rs` / `--rw` | `--fertig-r` / `--fertig-rs` / `--fertig-rw` |
74
+ | `--bg` / `--el` / `--face` / `--fg` / `--mut` / `--bd` / `--tb` | `--fertig-*` (same short name) |
75
+ | `--up` / `--up2` / `--dn` / `--sh` / `--sh1` / `--sh2` | `--fertig-up` / `--fertig-up2` / `--fertig-dn` / `--fertig-sh1` / `--fertig-sh{2}` |
76
+ | fonts `--f` / `--fm`, measure `--w`, caps `--caps`, `--nw` | `--fertig-*` |
77
+ | color ramps `--stone-*` / `--sage-*` / `--sky-*` / `--clay-*` / `--plum-*` / `--gold-*` | `--fertig-*` (same stop) |
78
+
79
+ The rule is simple: **any `--name` you set becomes `--fertig-name`.** Nothing
80
+ changed meaning; the prefix exists so the sheet never collides with a custom
81
+ property of yours (it is the same reason `--fertig-a1` / `--fertig-a2` were
82
+ already prefixed — those are `@property` registrations, which are global).
83
+
84
+ Two defaults change visually, so pin `fertig@2` if you want the old look:
85
+
86
+ - **Palette.** Accents and neutrals moved to a cooler voice — graphite
87
+ neutrals and a violet-blue accent — replacing the older indigo/warm scheme.
88
+ - **Buttons.** Fully rounded (`border-radius: 999px`) instead of the moderate
89
+ radius.
90
+
91
+ The docs site was also rebuilt and the two demo pages (`app.html` /
92
+ `app-invoice.html`) are gone. None of that touches the sheet.
93
+
59
94
  ## Install
60
95
 
61
96
  ```sh
@@ -64,7 +99,7 @@ npm install fertig
64
99
 
65
100
  ```html
66
101
  <!-- or from a CDN, pinned -->
67
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fertig@1/fertig.min.css">
102
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fertig@3/fertig.min.css">
68
103
  ```
69
104
 
70
105
  ```css
@@ -77,8 +112,9 @@ npm install fertig
77
112
  Opinions, so you don't have to have them: system type, one accent, one
78
113
  measure, a window on a desktop. The structure is classic — a title bar, sunken
79
114
  fields, raised buttons — and the finish is macOS: vibrancy, hairlines, soft
80
- radii, the blue focus ring. None of it is a period costume, and none of it is
81
- a picture of an OS: it is all CSS that every current engine already ships.
115
+ radii, a violet-blue focus ring over cool graphite neutrals. None of it is a
116
+ period costume, and none of it is a picture of an OS: it is all CSS that every
117
+ current engine already ships.
82
118
 
83
119
  - **System type, set properly** — 16px on a 1.65 line height across roughly
84
120
  seventy characters. Code keeps a monospace face, where it earns its place.
@@ -195,26 +231,42 @@ one. `.max-w-prose` is that measure, as a class.
195
231
 
196
232
  ## Customising
197
233
 
198
- Override the four tokens you'd actually want to change:
234
+ Override the tokens you'd actually want to change:
199
235
 
200
236
  ```css
201
237
  :root {
202
- --ac: light-dark(#c2410c, #fb923c); /* accent */
203
- --w: 48rem; /* measure */
204
- --r: 0px; /* control radius — go sharp */
205
- --f: "Inter", system-ui, sans-serif; /* text font */
238
+ --fertig-ac: light-dark(oklch(50% .22 266), oklch(78% .13 266)); /* violet-blue accent */
239
+ --fertig-w: 48rem; /* measure */
240
+ --fertig-r: 0px; /* control radius — go sharp */
241
+ --fertig-f: "Inter", system-ui, sans-serif; /* text font */
206
242
  }
207
243
  ```
208
244
 
209
- If you change `--ac`, check `--on-ac` too — that is the text colour sitting on
210
- the accent, and a light accent needs dark text to stay legible.
211
- `--up` / `--up2` / `--dn` are the elevation scale, `--fertig-a1` /
212
- `--fertig-a2` the shadow alphas (these two are `@property` registrations, and
213
- a registration is global — hence the prefix), `--rw` the window radius, and `--nw` the column the toolbar's contents
214
- line up with. `--nw` follows `--w`, so nothing moves by default; `<nav
215
- class="wide">` re-points it at the wide column, which is what an app screen
216
- built on `.wide` wants so its wordmark doesn't float in the middle of the
217
- viewport.
245
+ The accent is the only colour most people touch. In 3.0 the default is a
246
+ violet-blue (`hue 266`) sitting on cool graphite neutrals (`hue 258`); the
247
+ `data-accent` attribute retints a whole region without touching tokens:
248
+
249
+ ```html
250
+ <section data-accent="sage">…</section> <!-- or sky, clay, plum, gold, slate -->
251
+ ```
252
+
253
+ Tones work the same way — `data-tone="ok|warn|err"` retints any component
254
+ without a round trip through the tokens:
255
+
256
+ ```html
257
+ <button data-tone="err">Delete</button>
258
+ ```
259
+
260
+ If you change `--fertig-ac`, check `--fertig-on-ac` too — that is the text
261
+ colour sitting on the accent, and a light accent needs dark text to stay
262
+ legible. `--fertig-up` / `--fertig-up2` / `--fertig-dn` are the elevation
263
+ scale, `--fertig-a1` / `--fertig-a2` the shadow alphas (these two are
264
+ `@property` registrations, and a registration is global — hence the prefix),
265
+ `--fertig-rw` the window radius, and `--fertig-nw` the column the toolbar's
266
+ contents line up with. `--fertig-nw` follows `--fertig-w`, so nothing moves by
267
+ default; `<nav class="wide">` re-points it at the wide column, which is what an
268
+ app screen built on `.wide` wants so its wordmark doesn't float in the middle
269
+ of the viewport.
218
270
 
219
271
  ## Also handled
220
272
 
@@ -268,7 +320,7 @@ assistive technology reads.
268
320
 
269
321
  | Sheet | Raw | Gzip |
270
322
  |---|---:|---:|
271
- | **fertig** | **32.8 KB** | **8.4 KB** |
323
+ | **fertig** | **36.8 KB** | **8.8 KB** |
272
324
  | Pico 2.1.1 classless | 69.4 KB | 10.1 KB |
273
325
 
274
326
  Measured with `gzip -9`, KB = 1024 bytes for every row. Most of the gap is Pico's full
@@ -319,12 +371,19 @@ margins with no stranded lines.
319
371
  ## Browser support
320
372
 
321
373
  The two-year window is on *browsers*, not on CSS. fertig runs in anything
322
- released since August 2024 and carries nothing for the ones before it — no
323
- vendor prefixes, no polyfills, no fallback build. The CSS it uses is mostly
324
- older than that: `@layer` has been cross-engine since 2022, `oklch()` and
325
- `color-mix()` since 2023, `light-dark()` since May 2024. The newest thing it
326
- *requires* is `@starting-style`, cross-engine since August 2024 — nothing it
327
- needs shipped inside the last two years.
374
+ released since August 2024. The CSS it uses is mostly older than that:
375
+ `@layer` has been cross-engine since 2022, `oklch()` and `color-mix()` since
376
+ 2023, `light-dark()` since May 2024. The newest thing it *requires* is
377
+ `@starting-style`, cross-engine since August 2024 — nothing it needs shipped
378
+ inside the last two years.
379
+
380
+ It carries almost nothing for the browsers before that: no polyfills, no
381
+ fallback build, and no vendor prefixes beyond the three no engine has replaced
382
+ (`-webkit-text-size-adjust`, the `-webkit-text-fill-color` autofill repaint,
383
+ and the `progress` pseudo-elements). The one concession is a `@supports not
384
+ (color: light-dark(…))` block restating the palette in flat sRGB — without it
385
+ an engine that lacks `light-dark()` gets invalid colour tokens and lands
386
+ unreadable rather than plain.
328
387
 
329
388
  | Engine | Minimum | Set by |
330
389
  |---|---|---|
@@ -345,19 +404,17 @@ and on the [docs site](https://moji2002.github.io/fertig/docs.html#support).
345
404
  ## Development
346
405
 
347
406
  ```sh
348
- npm run dev # serve on :8899
349
- npm run build # regenerate fertig.min.css and print sizes
407
+ npm run build # regenerate fertig.min.css and print sizes
408
+ npm run site # build the site into dist/ with Eleventy
409
+ npm run site:serve # build + live-reload on :8080 (the Eleventy dev server)
410
+ npm run sizes # sync the size claims in README + site copy
350
411
  ```
351
412
 
352
413
  ## Builds
353
414
 
354
415
  | Build | Raw | Gzip | |
355
416
  |---|---:|---:|---|
356
- | `fertig.min.css` | 32.8 KB | 8.4 KB | everything |
357
- | `fertig.core.min.css` | 25.1 KB | 6.9 KB | without the ARIA component layer |
358
-
359
- Dropping the component layer saves 1.4 KB gzipped — worth knowing, rarely worth
360
- doing. The weight is in the element coverage and forms, not the components.
417
+ | `fertig.min.css` | 36.8 KB | 8.8 KB | everything |
361
418
 
362
419
  ## Files
363
420
 
@@ -365,21 +422,19 @@ The sheet:
365
422
 
366
423
  - `fertig.css` — source, commented
367
424
  - `fertig.min.css` — minified, what you ship
368
- - `fertig.core.min.css` — the same without the ARIA component layer
369
425
  - `build.js` — minifier and size report
370
426
 
371
- The site:
427
+ The site (Eleventy, output to `dist/`):
372
428
 
373
- - `index.html` — landing page, with a live customiser that writes the token
374
- block for you
375
- - `docs.html` — documentation
376
- - `blocks.html` — ready-to-use markup blocks (16 of them); the code shown is
377
- generated from the live preview, so it cannot drift
378
- - `app.html`, `app-invoice.html` — full product screens built with no page CSS
379
- at all
380
- - `templates/` — four whole pages: `dashboard`, `article`, `pricing`, `signin`
429
+ - `src/_includes/layout.njk` — shared head + nav
430
+ - `src/_data/site.js` — version pulled from `package.json` (the one source of truth)
431
+ - `src/{index,docs,blocks}.njk` — page templates, generated from the root
432
+ HTML by `tools/build-src.py`
433
+ - `index.html`, `docs.html`, `blocks.html` — the authored source for each page
434
+ (the live customiser on the landing page writes the token block for you)
381
435
  - `site.css`, `site.js`, `icons.svg`, `favicon.svg` — the site's own chrome,
382
436
  none of it part of the sheet
437
+ - `dist/` — the built site that GitHub Pages deploys
383
438
 
384
439
  Everything else:
385
440
 
@@ -387,6 +442,9 @@ Everything else:
387
442
  - `CHANGELOG.md` — what changed and why, per release
388
443
  - `docs/classless-css-research.md` — the research behind the decisions
389
444
 
445
+ The sheet itself ships without the site — the npm package holds the two CSS
446
+ files plus the README and licence, nothing else.
447
+
390
448
  ## License
391
449
 
392
450
  MIT © Mojtaba Beheshti