fertig 4.0.3 → 4.0.7
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 +40 -436
- package/fertig.css +226 -107
- package/fertig.min.css +1 -1
- package/package.json +10 -10
- package/CHANGELOG.md +0 -481
package/README.md
CHANGED
|
@@ -1,473 +1,77 @@
|
|
|
1
1
|
# fertig
|
|
2
2
|
|
|
3
|
-
[](https://bundlephobia.com/package/fertig)
|
|
4
|
+
[](https://bundlephobia.com/package/fertig)
|
|
5
5
|
|
|
6
|
-
A classless CSS
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
JavaScript switched off.
|
|
6
|
+
A classless CSS framework for finished interfaces. Link one file and write
|
|
7
|
+
semantic HTML; fertig styles the page, forms, tables, dialogs, popovers,
|
|
8
|
+
navigation, feedback, and common layout patterns.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
**41.1 KB raw · 9.3 KB gzipped · no build step · no dependencies · no JavaScript.**
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
### What makes it different
|
|
16
|
-
|
|
17
|
-
Surveyed across the eleven most-used classless stylesheets on 2026-08-31
|
|
18
|
-
([working notes](docs/classless-landscape-2026.md)):
|
|
19
|
-
|
|
20
|
-
- **Components addressed by ARIA.** Only one other classless sheet ships a
|
|
21
|
-
single ARIA-addressed component. None ships tabs, a segmented control, or an
|
|
22
|
-
anchored popover menu.
|
|
23
|
-
- **Behaviour without JavaScript.** Others style `<dialog>` and leave the open
|
|
24
|
-
and close to you. Here `popover`, `command` and anchor positioning do it —
|
|
25
|
-
[audited with scripting disabled](#components-that-work-without-javascript).
|
|
26
|
-
- **Modern CSS.** Zero of the eleven use `oklch()`, `light-dark()`, `@layer`,
|
|
27
|
-
`@property`, container queries or anchor positioning. This sheet uses all of
|
|
28
|
-
them.
|
|
29
|
-
- **Cascade-layered.** It lives in `@layer fertig`, so your own unlayered CSS
|
|
30
|
-
wins at any specificity and overriding never needs `!important`. No other
|
|
31
|
-
classless sheet does this.
|
|
32
|
-
- **RTL by construction.** Written in logical properties throughout, not
|
|
33
|
-
patched with `[dir=rtl]` overrides afterwards.
|
|
34
|
-
|
|
35
|
-
`fertig.min.css` is 8.6 KB gzipped. That includes semantic element styling,
|
|
36
|
-
the component layer, responsive utilities, light and dark themes, RTL support
|
|
37
|
-
and accessibility safeguards. Size is a constraint here, not the pitch.
|
|
38
|
-
|
|
39
|
-
## Components that work without JavaScript
|
|
40
|
-
|
|
41
|
-
Audited in a browser with scripting disabled:
|
|
42
|
-
|
|
43
|
-
| | |
|
|
44
|
-
|---|---|
|
|
45
|
-
| Popover menu, dialog, drawer, toast, tooltip, disclosure | **work with no script** |
|
|
46
|
-
| Tabs | **styled only** — selection, panels and arrow keys need a short script |
|
|
47
|
-
|
|
48
|
-
This gap is deliberate: `aria-selected` must be a real attribute, inactive tabs
|
|
49
|
-
need roving focus, and CSS cannot implement the arrow-key contract. The complete
|
|
50
|
-
enhancement is in the docs. `<details name>` remains an exclusive accordion,
|
|
51
|
-
not a tabs substitute.
|
|
52
|
-
|
|
53
|
-
```html
|
|
54
|
-
<link rel="stylesheet" href="fertig.css">
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
That is the whole integration. Write semantic HTML; it looks finished.
|
|
58
|
-
Open the [live site](https://moji2002.github.io/fertig/) for the full demo.
|
|
59
|
-
|
|
60
|
-
## Upgrading from 2.x
|
|
61
|
-
|
|
62
|
-
3.0 is breaking for one reason: **every public token now carries a `--fertig-`
|
|
63
|
-
prefix**, and the old names are gone — no aliases. The classes, the ARIA
|
|
64
|
-
attributes the components read, and the layout utilities are all unchanged.
|
|
65
|
-
The sheet's behaviour is the same; the migration is a rename.
|
|
66
|
-
|
|
67
|
-
If you override tokens, prefix what you set. The common ones:
|
|
68
|
-
|
|
69
|
-
| 2.x | 3.0 |
|
|
70
|
-
|---|---|
|
|
71
|
-
| `--ac` | `--fertig-ac` |
|
|
72
|
-
| `--on-ac` | `--fertig-on-ac` |
|
|
73
|
-
| `--w` / `--g` | `--fertig-w` / `--fertig-g` |
|
|
74
|
-
| `--r` / `--rs` / `--rw` | `--fertig-r` / `--fertig-rs` / `--fertig-rw` |
|
|
75
|
-
| `--bg` / `--el` / `--face` / `--fg` / `--mut` / `--bd` / `--tb` | `--fertig-*` (same short name) |
|
|
76
|
-
| `--up` / `--up2` / `--dn` / `--sh` / `--sh1` / `--sh2` | `--fertig-up` / `--fertig-up2` / `--fertig-dn` / `--fertig-sh` / `--fertig-sh1` / `--fertig-sh2` |
|
|
77
|
-
| fonts `--f` / `--fm`, page width `--w`, gutter `--g`, caps `--caps`, `--nw` | `--fertig-*` |
|
|
78
|
-
| color ramps `--stone-*` / `--sage-*` / `--sky-*` / `--clay-*` / `--plum-*` / `--gold-*` | `--fertig-*` (same stop) |
|
|
79
|
-
|
|
80
|
-
The rule is simple: **any `--name` you set becomes `--fertig-name`.** Nothing
|
|
81
|
-
changed meaning; the prefix exists so the sheet never collides with a custom
|
|
82
|
-
property of yours (it is the same reason `--fertig-a1` / `--fertig-a2` were
|
|
83
|
-
already prefixed — those are `@property` registrations, which are global).
|
|
84
|
-
|
|
85
|
-
The defaults now favor a platform-neutral interface:
|
|
86
|
-
|
|
87
|
-
- **Palette.** Accents and neutrals moved to a cooler voice — graphite
|
|
88
|
-
neutrals and a violet-blue accent — replacing the older indigo/warm scheme.
|
|
89
|
-
- **Geometry.** Buttons share the restrained control radius. Pill geometry is
|
|
90
|
-
reserved for badges, switches, meters and joined ends.
|
|
91
|
-
|
|
92
|
-
The docs site was also rebuilt and the two demo pages (`app.html` /
|
|
93
|
-
`app-invoice.html`) are gone. None of that touches the sheet.
|
|
94
|
-
|
|
95
|
-
## Install
|
|
12
|
+
## Use
|
|
96
13
|
|
|
97
14
|
```sh
|
|
98
15
|
npm install fertig
|
|
99
16
|
```
|
|
100
17
|
|
|
101
|
-
```html
|
|
102
|
-
<!-- or from a CDN, pinned -->
|
|
103
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fertig@4/fertig.min.css">
|
|
104
|
-
```
|
|
105
|
-
|
|
106
18
|
```css
|
|
107
|
-
/* or through a bundler */
|
|
108
19
|
@import "fertig";
|
|
109
20
|
```
|
|
110
21
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Opinions, so you don't have to have them: system type, one accent, one page
|
|
114
|
-
width and a clear content canvas. The structure is classic — a toolbar,
|
|
115
|
-
sunken fields and flat buttons — while the finish stays platform-neutral:
|
|
116
|
-
opaque surfaces, hairlines, restrained corners and a violet-blue focus ring
|
|
117
|
-
over cool graphite neutrals.
|
|
118
|
-
|
|
119
|
-
- **System type, set properly** — 16px on a 1.65 line height. Long-form copy
|
|
120
|
-
gets a roughly seventy-character measure with `.max-w-prose`. Code keeps a
|
|
121
|
-
monospace face, where it earns its place.
|
|
122
|
-
- **A centered content canvas.** `<nav>` becomes a full-bleed opaque toolbar;
|
|
123
|
-
`header`/`main`/`footer` form the page surface.
|
|
124
|
-
- **Depth from hairlines and soft shadows.** Cards sit above the page while
|
|
125
|
-
buttons stay flat and inputs and code sit below it. Shadow alphas step up in dark mode,
|
|
126
|
-
because a shadow on a dark background does nothing on its own.
|
|
127
|
-
- **Quiet text.** Links underline on hover rather than inverting mid-paragraph.
|
|
128
|
-
- **Comfortable foregrounds.** Light mode uses graphite rather than absolute
|
|
129
|
-
black; dark mode uses a softened near-white rather than full white.
|
|
130
|
-
- **Dark mode with no second stylesheet.** One `light-dark()` token block.
|
|
131
|
-
Add `data-theme="dark"` (or `"light"`) on `<html>` to override the OS.
|
|
132
|
-
|
|
133
|
-
## Layout
|
|
134
|
-
|
|
135
|
-
Direct children of `<body>` are full-width containers with responsive page
|
|
136
|
-
gutters:
|
|
22
|
+
Or use the CDN:
|
|
137
23
|
|
|
138
24
|
```html
|
|
139
|
-
<
|
|
140
|
-
<nav>…</nav> <!-- toolbar: full-bleed, contents on the page column -->
|
|
141
|
-
<header>…</header>
|
|
142
|
-
<main>…</main>
|
|
143
|
-
<footer>…</footer>
|
|
144
|
-
</body>
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
There is no default maximum width on body-level containers. Add
|
|
148
|
-
`.max-w-prose` to long-form content for a readable `38rem` line length, or use
|
|
149
|
-
the `max-w-*` utilities when a page needs a specific measure. The
|
|
150
|
-
`--fertig-g` gutter scales from `1.15rem` on narrow phones to `1.9rem` on
|
|
151
|
-
larger screens.
|
|
152
|
-
|
|
153
|
-
One wrapper is fine. React, Vue, Svelte and Next render into a mount node, so
|
|
154
|
-
the shell matches `body`'s children *or* the children of a single `#root`,
|
|
155
|
-
`#app`, `#__next`, or `[data-fertig]` wrapper:
|
|
156
|
-
|
|
157
|
-
```html
|
|
158
|
-
<body>
|
|
159
|
-
<div id="root">
|
|
160
|
-
<nav>…</nav>
|
|
161
|
-
<main>…</main>
|
|
162
|
-
</div>
|
|
163
|
-
</body>
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Deeper than that, or a differently named mount node, and you re-point the shell
|
|
167
|
-
yourself — it is the only place `body >` appears:
|
|
168
|
-
|
|
169
|
-
```css
|
|
170
|
-
#shell > *, #shell > header, #shell > main, #shell > footer { /* … */ }
|
|
25
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/fertig@4/fertig.min.css">
|
|
171
26
|
```
|
|
172
27
|
|
|
173
|
-
|
|
28
|
+
That is the complete setup. See the [documentation](https://moji2002.github.io/fertig/docs.html),
|
|
29
|
+
[component catalogue](https://moji2002.github.io/fertig/components.html), and
|
|
30
|
+
[copy-paste blocks](https://moji2002.github.io/fertig/blocks.html).
|
|
174
31
|
|
|
175
|
-
|
|
176
|
-
markup stays accessible by construction.
|
|
32
|
+
## What it includes
|
|
177
33
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
| Loading | `aria-busy="true"` (inline spinner) |
|
|
187
|
-
| Tooltip | `data-tooltip="…"` — decorative; put the same words in `aria-label` too |
|
|
188
|
-
| Button variants | `data-variant="ghost\|link"`, `data-size="sm\|lg"`, `data-icon`, `data-block` — on controls only, so they cannot catch a `<span data-size>` of yours |
|
|
189
|
-
| Card anatomy | `<header>` / `<footer>` inside `.card`; `<a class="card">` lifts on hover |
|
|
190
|
-
| Tones | `data-tone="ok\|warn\|err"` retints any component |
|
|
34
|
+
- Light and dark themes through `light-dark()`
|
|
35
|
+
- Accessible forms, input groups, one-time codes, listboxes, surfaces, cards,
|
|
36
|
+
items, empty states, tabs, menus, dialogs, tooltips, and more
|
|
37
|
+
- Native no-JavaScript behavior for popovers, dialogs, drawers, disclosures,
|
|
38
|
+
toasts, and tooltips
|
|
39
|
+
- Logical properties for RTL layouts
|
|
40
|
+
- OKLCH color ramps and WCAG AA semantic pairs
|
|
41
|
+
- A cascade layer, so unlayered project CSS overrides fertig cleanly
|
|
191
42
|
|
|
192
|
-
|
|
193
|
-
|
|
43
|
+
Seven component classes are optional: `.row`, `.grid`, `.card`, `.badge`,
|
|
44
|
+
`.muted`, `.center`, and `.wide`. Layout utilities are available when semantic
|
|
45
|
+
HTML alone cannot express the layout.
|
|
194
46
|
|
|
195
|
-
|
|
196
|
-
|---|---|
|
|
197
|
-
| Avatar | `data-avatar` on an `<img>` or on initials in a `<span>`; `="sm\|lg"` |
|
|
198
|
-
| Skeleton | `data-skeleton` on the element that is still loading |
|
|
199
|
-
| Sheet / drawer | `<dialog data-side="left\|right\|bottom">` — the same dialog, docked to an edge |
|
|
200
|
-
| Toast | `[popover][data-toast]` — non-modal, parked in a corner. Several at once: `<div popover="manual" data-toast-region>` with a `<div data-toast>` per message |
|
|
201
|
-
| List group | a plain `<menu>` outside a popover becomes a bordered list |
|
|
202
|
-
| Nav menu | a `<menu>` inside a `<nav>` is navigation instead — no rules, no bullets, `aria-current` marks the page |
|
|
203
|
-
| Sidebar layout | `data-layout="sidebar"` on a wrapper of two children; the first sticks |
|
|
204
|
-
| Carousel | `data-carousel` — scroll snapping, with real scroll buttons where they exist |
|
|
47
|
+
## Customize
|
|
205
48
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
`.row` `.grid` `.card` `.badge` `.muted` `.center` `.wide`
|
|
209
|
-
|
|
210
|
-
Plus `.primary` on a button — though `type="submit"` already gets it.
|
|
211
|
-
|
|
212
|
-
## Layout utilities
|
|
213
|
-
|
|
214
|
-
A compact set of predictable layout helpers covers common flex, grid,
|
|
215
|
-
alignment, gap, margin, width and visibility needs.
|
|
216
|
-
|
|
217
|
-
```
|
|
218
|
-
.flex .flex-col .flex-wrap .flex-1
|
|
219
|
-
.grid-cols-2 .grid-cols-3 .grid-cols-4
|
|
220
|
-
.items-start .items-center .items-end
|
|
221
|
-
.justify-start .justify-center .justify-end .justify-between
|
|
222
|
-
.gap-0 .gap-1 .gap-2 .gap-3 .gap-4 .gap-6 (0 · .25 · .5 · .75 · 1 · 1.5rem)
|
|
223
|
-
.mx-auto .ms-auto .me-auto .w-full .hidden
|
|
224
|
-
.max-w-xs .max-w-sm .max-w-md .max-w-lg .max-w-xl (20 · 24 · 28 · 32 · 36rem)
|
|
225
|
-
.max-w-2xl .max-w-3xl .max-w-4xl .max-w-5xl .max-w-6xl .max-w-7xl (42 · 48 · 56 · 64 · 72 · 80rem)
|
|
226
|
-
.max-w-full .max-w-none .max-w-prose
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Layout and width only. No colour or type scale, and no responsive
|
|
230
|
-
variants — those need a build step, and this file does not have one. The
|
|
231
|
-
margins are flow-relative (`ms`/`me`, not `ml`/`mr`), so a toolbar still lands
|
|
232
|
-
correctly in Persian or Arabic. `.grid` is fertig's own auto-fit grid;
|
|
233
|
-
adding `.grid-cols-3` pins it to three columns.
|
|
234
|
-
|
|
235
|
-
The `max-w-*` scale is complete rather than partial — a half-scale where
|
|
236
|
-
`.max-w-2xl` works and `.max-w-3xl` silently does not is worse than no scale
|
|
237
|
-
at all. They are `max-inline-size`, so they still mean "along the text"
|
|
238
|
-
in a vertical writing mode and can constrain a page container as readily as
|
|
239
|
-
anything inside one. `.max-w-prose` is a fixed `38rem` reading measure.
|
|
240
|
-
|
|
241
|
-
## Customising
|
|
242
|
-
|
|
243
|
-
Override the tokens you'd actually want to change:
|
|
49
|
+
Override tokens in your own CSS:
|
|
244
50
|
|
|
245
51
|
```css
|
|
246
52
|
:root {
|
|
247
|
-
--fertig-ac:
|
|
248
|
-
--fertig-
|
|
249
|
-
--fertig-
|
|
250
|
-
--fertig-r: 0px; /* control radius — go sharp */
|
|
251
|
-
--fertig-f: "Inter", system-ui, sans-serif; /* text font */
|
|
53
|
+
--fertig-ac: oklch(52% 0.18 270);
|
|
54
|
+
--fertig-r: 10px;
|
|
55
|
+
--fertig-w: 72rem;
|
|
252
56
|
}
|
|
253
57
|
```
|
|
254
58
|
|
|
255
|
-
The accent is the only colour most people touch. The default is blue sitting
|
|
256
|
-
on cool gray neutrals; the
|
|
257
|
-
`data-accent` attribute retints a whole region without touching tokens:
|
|
258
|
-
|
|
259
|
-
```html
|
|
260
|
-
<section data-accent="green">…</section> <!-- or blue, cyan, amber, red, violet, gray -->
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
The palette exposes `gray`, `blue`, `cyan`, `green`, `amber`, `red` and
|
|
264
|
-
`violet` ramps at `100`, `300`, `500`, `700` and `900`. For example,
|
|
265
|
-
`--fertig-violet-500` is the middle violet stop. The former
|
|
266
|
-
`stone/sage/sky/clay/plum/gold` ramps and named accents were removed rather
|
|
267
|
-
than retained as aliases.
|
|
268
|
-
|
|
269
|
-
Tones work the same way — `data-tone="ok|warn|err"` retints any component
|
|
270
|
-
without a round trip through the tokens:
|
|
271
|
-
|
|
272
|
-
```html
|
|
273
|
-
<button data-tone="err">Delete</button>
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
If you change `--fertig-ac`, check `--fertig-on-ac` too — that is the text
|
|
277
|
-
colour sitting on the accent, and a light accent needs dark text to stay
|
|
278
|
-
legible. `--fertig-up` / `--fertig-up2` / `--fertig-dn` are the elevation
|
|
279
|
-
scale, `--fertig-a1` / `--fertig-a2` the shadow alphas (these two are
|
|
280
|
-
`@property` registrations, and a registration is global — hence the prefix),
|
|
281
|
-
`--fertig-rw` the large-surface radius, and `--fertig-nw` the column the toolbar's
|
|
282
|
-
contents line up with. `--fertig-nw` follows `--fertig-w`, so nothing moves by
|
|
283
|
-
default; `<nav class="wide">` re-points it at the wide column, which is what an
|
|
284
|
-
app screen built on `.wide` wants so its wordmark doesn't float in the middle
|
|
285
|
-
of the viewport.
|
|
286
|
-
|
|
287
|
-
## Also handled
|
|
288
|
-
|
|
289
|
-
`:target`, external-link arrows, `user-invalid` fields, a CSS-drawn select
|
|
290
|
-
chevron, thin scrollbars, `sup`/`sub` that don't stretch lines, `q`/`dfn`/
|
|
291
|
-
`ins`/`del`/`address`/`meter`/`output`/`optgroup`/`hgroup`, print styles, and
|
|
292
|
-
`prefers-reduced-motion`.
|
|
293
|
-
|
|
294
|
-
## Right-to-left
|
|
295
|
-
|
|
296
|
-
Every inline-direction rule is flow-relative — `padding-inline-start`,
|
|
297
|
-
`border-inline-start`, `margin-inline-end`, `text-align: start`,
|
|
298
|
-
`border-start-end-radius` — so `dir="rtl"` mirrors the sheet with nothing to
|
|
299
|
-
configure:
|
|
300
|
-
|
|
301
|
-
```html
|
|
302
|
-
<html lang="fa" dir="rtl">
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
List markers, blockquote and `<aside>` accent bars, table gutters, breadcrumb
|
|
306
|
-
separators, the segmented control's rounded ends, the `<select>` chevron and
|
|
307
|
-
the switch knob all move to the correct side. Two of those cannot be expressed
|
|
308
|
-
logically — `translate` and a background position — and carry `:dir(rtl)`
|
|
309
|
-
rules instead. The layout utilities follow the same rule: `.ms-auto` and
|
|
310
|
-
`.me-auto` exist, `.ml-auto` deliberately does not.
|
|
311
|
-
|
|
312
|
-
Tested by rendering a Persian page in both directions, not by inspection.
|
|
313
|
-
|
|
314
|
-
## Accessibility
|
|
315
|
-
|
|
316
|
-
Every foreground/background pair meets WCAG 2.1 AA (>= 4.5:1) in **both**
|
|
317
|
-
themes, calculated from the computed token values:
|
|
318
|
-
|
|
319
|
-
| Pair | Light | Dark |
|
|
320
|
-
|---|---:|---:|
|
|
321
|
-
| Body text on content surface | 16.83 | 15.32 |
|
|
322
|
-
| Muted text on content surface | 6.92 | 7.15 |
|
|
323
|
-
| Links on content surface | 6.72 | 9.50 |
|
|
324
|
-
| Muted on controls | 6.57 | 6.45 |
|
|
325
|
-
| Muted on page ground | 5.89 | 7.88 |
|
|
326
|
-
| Text on accent | 6.16 | 11.22 |
|
|
327
|
-
| Tones (ok / warn / err) | 5.91–7.17 | 9.12–10.76 |
|
|
328
|
-
|
|
329
|
-
The tightest pair in the sheet is text on the amber fill in light mode, at
|
|
330
|
-
5.41:1.
|
|
331
|
-
|
|
332
|
-
Motion is gated behind `prefers-reduced-motion`, focus uses a visible ring at
|
|
333
|
-
`:focus-visible`, and the component layer is driven by the same ARIA attributes
|
|
334
|
-
assistive technology reads.
|
|
335
|
-
|
|
336
|
-
## Size
|
|
337
|
-
|
|
338
|
-
| File | Raw | Gzip |
|
|
339
|
-
|---|---:|---:|
|
|
340
|
-
| **fertig.min.css** | **36.7 KB** | **8.6 KB** |
|
|
341
|
-
|
|
342
|
-
Measured with `gzip -9`; KB = 1024 bytes.
|
|
343
|
-
|
|
344
|
-
## Overriding it
|
|
345
|
-
|
|
346
|
-
The sheet declares two layers, `@layer fertig, fertig-a11y` — the second holds
|
|
347
|
-
the forced-colors block so it can beat a `.card` shadow on layer order instead
|
|
348
|
-
of `!important`. Anything you write outside a layer beats both whatever the
|
|
349
|
-
specificity, so overriding is never a fight:
|
|
350
|
-
|
|
351
|
-
```css
|
|
352
|
-
button { background: hotpink } /* wins, no !important needed */
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
## Modern CSS, used deliberately
|
|
356
|
-
|
|
357
|
-
Everything past the floor sits behind `@supports` and is additive:
|
|
358
|
-
`contrast-color()` picks the light or dark text colour on your accent and a
|
|
359
|
-
small mix keeps it away from absolute black or white; anchor positioning
|
|
360
|
-
attaches popover menus to their button;
|
|
361
|
-
`::details-content` with `interpolate-size` animates disclosures open;
|
|
362
|
-
`field-sizing` grows textareas; `text-box: trim-both` sits headings on their
|
|
363
|
-
cap height; and
|
|
364
|
-
`appearance: base-select` styles the dropdown picker itself. Without any of it,
|
|
365
|
-
nothing breaks. Relative colour (`oklch(from …)`) is on that list too, for one
|
|
366
|
-
job only: lightening the filled button on hover in OKLCH. The shadow alphas
|
|
367
|
-
that used to need it are taken with `color-mix` against `transparent` instead —
|
|
368
|
-
pixel-identical, and three years older, which is what keeps Safari 17.5 inside
|
|
369
|
-
the floor. Two things are *not* on the list because they are part of the floor:
|
|
370
|
-
`@property`, which types the shadow alphas so elevation can transition, and
|
|
371
|
-
`container-type` on `.card`, which makes every card a container you can write
|
|
372
|
-
`@container` queries against.
|
|
373
|
-
|
|
374
|
-
It also answers to user and device preferences: `prefers-reduced-motion`,
|
|
375
|
-
`prefers-contrast`, `forced-colors`,
|
|
376
|
-
`pointer: coarse` (44px targets, WCAG 2.5.8), `env(safe-area-inset-*)`,
|
|
377
|
-
`update: fast` (e-ink never starts a transition), `inverted-colors` and
|
|
378
|
-
`color-gamut: p3`.
|
|
379
|
-
|
|
380
|
-
And it survives content it has never seen: a pasted URL wraps instead of
|
|
381
|
-
widening the page, tables scroll themselves on a phone, autofilled fields are
|
|
382
|
-
repainted so the browser yellow never shows, required and read-only fields
|
|
383
|
-
read correctly, numerals are slashed and tabular, and print gets real page
|
|
384
|
-
margins with no stranded lines.
|
|
385
|
-
|
|
386
|
-
## Browser support
|
|
387
|
-
|
|
388
|
-
The two-year window is on *browsers*, not on CSS. fertig runs in anything
|
|
389
|
-
released since August 2024. The CSS it uses is mostly older than that:
|
|
390
|
-
`@layer` has been cross-engine since 2022, `oklch()` and `color-mix()` since
|
|
391
|
-
2023, `light-dark()` since May 2024. The newest thing it *requires* is
|
|
392
|
-
`@starting-style`, cross-engine since August 2024 — nothing it needs shipped
|
|
393
|
-
inside the last two years.
|
|
394
|
-
|
|
395
|
-
It carries almost nothing for the browsers before that: no polyfills, no
|
|
396
|
-
fallback build, and no vendor prefixes beyond the three no engine has replaced
|
|
397
|
-
(`-webkit-text-size-adjust`, the `-webkit-text-fill-color` autofill repaint,
|
|
398
|
-
and the `progress` pseudo-elements). The one concession is a `@supports not
|
|
399
|
-
(color: light-dark(…))` block restating the palette in flat sRGB — without it
|
|
400
|
-
an engine that lacks `light-dark()` gets invalid colour tokens and lands
|
|
401
|
-
unreadable rather than plain.
|
|
402
|
-
|
|
403
|
-
| Engine | Minimum | Set by |
|
|
404
|
-
|---|---|---|
|
|
405
|
-
| Chrome / Edge | **123** | `light-dark()` |
|
|
406
|
-
| Safari | **17.5** | `light-dark()`, `@starting-style`, `text-wrap: balance` |
|
|
407
|
-
| Firefox | **129** | `@starting-style`, `transition-behavior` |
|
|
408
|
-
|
|
409
|
-
No component needs more than that floor — anything a component would *like* to
|
|
410
|
-
have it asks for through `@supports`. Anchor positioning, for instance, is
|
|
411
|
-
gated; without it `[popover]` menus centre on screen instead of sitting under
|
|
412
|
-
their button. Below the floor everything degrades to unstyled-but-readable
|
|
413
|
-
HTML.
|
|
414
|
-
|
|
415
|
-
Per-component and per-enhancement version tables, with the shipping dates
|
|
416
|
-
behind them, are in [`docs/browser-support-research.md`](docs/browser-support-research.md)
|
|
417
|
-
and on the [docs site](https://moji2002.github.io/fertig/docs.html#support).
|
|
418
|
-
|
|
419
59
|
## Development
|
|
420
60
|
|
|
421
61
|
```sh
|
|
422
|
-
npm
|
|
423
|
-
npm run
|
|
424
|
-
npm run build
|
|
425
|
-
npm
|
|
426
|
-
npm run site
|
|
427
|
-
npm run
|
|
62
|
+
npm install
|
|
63
|
+
npm run hooks:install # enable automatic patch versioning for commits
|
|
64
|
+
npm run build # regenerate fertig.min.css
|
|
65
|
+
npm test # library and site tests
|
|
66
|
+
npm run site # Astro production build
|
|
67
|
+
npm run site:serve # Astro development server
|
|
68
|
+
npm run check # complete release check
|
|
428
69
|
```
|
|
429
70
|
|
|
430
|
-
The
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
package contents. If a generated file or size claim is stale, run the command
|
|
434
|
-
named by the failure, review the diff, and commit it deliberately.
|
|
435
|
-
Tracked changes must be fully staged, preventing an unstaged fix from hiding a
|
|
436
|
-
broken staged snapshot during validation.
|
|
437
|
-
|
|
438
|
-
## Builds
|
|
439
|
-
|
|
440
|
-
| Build | Raw | Gzip | |
|
|
441
|
-
|---|---:|---:|---|
|
|
442
|
-
| `fertig.min.css` | 36.7 KB | 8.6 KB | everything |
|
|
443
|
-
|
|
444
|
-
## Files
|
|
445
|
-
|
|
446
|
-
The sheet:
|
|
447
|
-
|
|
448
|
-
- `fertig.css` — source, commented
|
|
449
|
-
- `fertig.min.css` — minified, what you ship
|
|
450
|
-
- `build.js` — minifier and size report
|
|
451
|
-
|
|
452
|
-
The site (Eleventy, output to `dist/`):
|
|
453
|
-
|
|
454
|
-
- `src/_includes/layout.njk` — shared document shell and navigation
|
|
455
|
-
- `src/_includes/{page-header,footer}.njk` — shared inner-page header and footer
|
|
456
|
-
- `src/_data/site.js` — version pulled from `package.json` (the one source of truth)
|
|
457
|
-
- `src/{index,docs,components,blocks}.njk` — the only authored page sources
|
|
458
|
-
- `site.css`, `site.js`, `icons.svg`, `favicon.svg` — the site's own chrome,
|
|
459
|
-
none of it part of the sheet
|
|
460
|
-
- `dist/` — the built site that GitHub Pages deploys
|
|
461
|
-
|
|
462
|
-
Everything else:
|
|
463
|
-
|
|
464
|
-
- `llms.txt` — machine-readable summary for LLMs, per llmstxt.org
|
|
465
|
-
- `CHANGELOG.md` — what changed and why, per release
|
|
466
|
-
- `docs/classless-css-research.md` — the research behind the decisions
|
|
467
|
-
|
|
468
|
-
The sheet itself ships without the site — the npm package holds the two CSS
|
|
469
|
-
files plus the README and licence, nothing else.
|
|
71
|
+
The package publishes only `fertig.css`, `fertig.min.css`, this README, and the
|
|
72
|
+
MIT license. The Astro site lives in `src/pages`, with its shared shell in
|
|
73
|
+
`src/layouts` and `src/components`.
|
|
470
74
|
|
|
471
75
|
## License
|
|
472
76
|
|
|
473
|
-
MIT
|
|
77
|
+
MIT
|