@codenhub/styles 0.4.0 → 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 +7 -7
- package/dist/aesthetics/chunky-tile.css +1 -1
- package/dist/aesthetics/cyber.css +1 -1
- package/dist/aesthetics/glass.css +1 -1
- package/dist/aesthetics/index.css +1 -1
- package/dist/aesthetics/neobrutalism.css +1 -1
- package/dist/aesthetics/pixel.css +1 -1
- package/dist/aesthetics/sketch.css +1 -0
- package/dist/components.css +1 -1
- package/dist/index.css +1 -1
- package/dist/native.css +1 -1
- package/dist/palette.css +1 -1
- package/dist/theme.css +1 -1
- package/dist/tw/aesthetics/chunky-tile.css +144 -128
- package/dist/tw/aesthetics/cyber.css +138 -96
- package/dist/tw/aesthetics/glass.css +332 -66
- package/dist/tw/aesthetics/index.css +3 -1
- package/dist/tw/aesthetics/neobrutalism.css +113 -54
- package/dist/tw/aesthetics/pixel.css +137 -87
- package/dist/tw/aesthetics/sketch.css +333 -0
- package/dist/tw/box.css +139 -52
- package/dist/tw/components/button.css +67 -28
- package/dist/tw/components/feedback.css +85 -67
- package/dist/tw/components/form.css +300 -230
- package/dist/tw/components/index.css +17 -9
- package/dist/tw/components/loader.css +38 -30
- package/dist/tw/components/surface.css +89 -129
- package/dist/tw/components/tooltip.css +91 -47
- package/dist/tw/corner.css +26 -0
- package/dist/tw/elevation.css +19 -28
- package/dist/tw/index.css +15 -19
- package/dist/tw/intent.css +47 -32
- package/dist/tw/native.css +58 -42
- package/dist/tw/palette.css +455 -446
- package/dist/tw/presentation.css +20 -29
- package/dist/tw/reset.css +21 -28
- package/dist/tw/surface.css +61 -0
- package/dist/tw/theme.css +156 -153
- package/dist/tw/utilities/content.css +283 -205
- package/dist/tw/utilities/layout.css +17 -27
- package/docs/accessibility.md +63 -46
- package/docs/changelog/0.5.0.md +38 -0
- package/docs/changelog/0.6.0.md +52 -0
- package/docs/changelog/index.md +2 -0
- package/docs/concepts.md +6 -6
- package/docs/index.md +12 -3
- package/docs/integrating/nextjs.md +1 -1
- package/docs/integrating/tailwind.md +14 -6
- package/docs/setup.md +28 -8
- package/docs/usage/aesthetics.md +88 -18
- package/docs/usage/buttons.md +14 -14
- package/docs/usage/composing.md +12 -12
- package/docs/usage/content-and-layout.md +25 -24
- package/docs/usage/customizing.md +92 -55
- package/docs/usage/feedback.md +5 -3
- package/docs/usage/forms.md +61 -45
- package/docs/usage/theming.md +41 -42
- package/docs/usage/tooltips.md +37 -17
- package/llms-full.txt +590 -304
- package/llms.txt +6 -0
- package/package.json +15 -5
|
@@ -24,55 +24,21 @@
|
|
|
24
24
|
--_d-fg-on-fill: 0%;
|
|
25
25
|
--_d-border: 100%;
|
|
26
26
|
|
|
27
|
-
/*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
`.soft`
|
|
31
|
-
|
|
32
|
-
classed.
|
|
27
|
+
/* 6% is a quiet wash, and it is the cap rather than a presentation: `.soft`
|
|
28
|
+
names the tint it wants and takes it whole below, so what this governs is
|
|
29
|
+
`.solid` and the container that cascades one onto a field nobody classed. A
|
|
30
|
+
cap equal to `.soft`'s 12% would hand `.solid` and `.soft` the same tint
|
|
31
|
+
through `min()`.
|
|
33
32
|
|
|
34
33
|
Named rather than written into the `min()`, because the soft hover below is
|
|
35
34
|
derived from it and the two must not drift apart. Typed text is
|
|
36
35
|
`--intent-color` over a wash of its own tone, past 10:1 in both themes at
|
|
37
|
-
any value this cap is likely to take
|
|
38
|
-
|
|
36
|
+
any value this cap is likely to take.
|
|
37
|
+
|
|
38
|
+
The fill and the ink are `box`'s own, unrestated: its `--_capped` reads
|
|
39
|
+
this cap, so the plate and the ink ramp stay in step at every bound,
|
|
40
|
+
including the checked state below that lifts it. */
|
|
39
41
|
--_fill-cap: 6%;
|
|
40
|
-
/* The floor is zero everywhere except a checked toggle, which needs a ground
|
|
41
|
-
under its mark. A container's `.ghost` would otherwise leave a checked
|
|
42
|
-
checkbox as a tick floating on the page -- our own cascade producing the
|
|
43
|
-
broken result, which is the test a bound has to pass. */
|
|
44
|
-
--_fill-floor: 0%;
|
|
45
|
-
--_fill: calc(
|
|
46
|
-
clamp(
|
|
47
|
-
var(--_fill-floor),
|
|
48
|
-
min(var(--ui-fill, var(--_d-fill)), var(--_fill-cap), var(--intent-fill-max, 100%)),
|
|
49
|
-
100%
|
|
50
|
-
) *
|
|
51
|
-
var(--ui-bg-alpha, 1)
|
|
52
|
-
);
|
|
53
|
-
/* The ink follows *this* control's fill, not `box`'s. `box` ramps against
|
|
54
|
-
`--_capped`, which knows `--intent-fill-max` but not `--_fill-cap`, so on a
|
|
55
|
-
control it would read a fill far higher than the one actually painted: a
|
|
56
|
-
`.primary.solid` checkbox has `--ui-fill: 100%` and an unbounded intent, so
|
|
57
|
-
`box` would call for the contrast tone while the plate sits at 20%. White
|
|
58
|
-
tick on light grey.
|
|
59
|
-
|
|
60
|
-
Ramping against `--_fill` keeps the two in step at every bound, including
|
|
61
|
-
the checked state that lifts them.
|
|
62
|
-
|
|
63
|
-
`--_fg-on-fill-floor` is the seam `:checked` pins instead of touching
|
|
64
|
-
`--ui-fg-on-fill` directly. `.ghost`/`.soft` are unlayered CSS (P3, and see
|
|
65
|
-
model.md's `--_fill-cap` history) and `text-control` compiles into
|
|
66
|
-
Tailwind's `utilities` layer, so a layered `:checked` writing
|
|
67
|
-
`--ui-fg-on-fill` the way it writes `--_fill-cap`/`--_fill-floor` would
|
|
68
|
-
lose to `.ghost`'s unlayered `--ui-fg-on-fill: 0%` regardless of
|
|
69
|
-
specificity -- the same trap `--_fill-cap` exists to avoid on the fill
|
|
70
|
-
itself. A private floor `.ghost`/`.soft` never touch has nothing
|
|
71
|
-
unlayered contesting it. */
|
|
72
|
-
--_on-fill: max(
|
|
73
|
-
var(--_fg-on-fill-floor, 0%),
|
|
74
|
-
min(var(--ui-fg-on-fill, var(--_d-fg-on-fill)), max(0%, calc(var(--_fill) * 2 - 100%)))
|
|
75
|
-
);
|
|
76
42
|
/* The resting line is a fraction of the intent rather than all of it, and the
|
|
77
43
|
fraction is what makes the pointer visible. Drawn at full strength a control
|
|
78
44
|
already sits at its intent's darkest, and hover moves it to a neighbouring
|
|
@@ -86,15 +52,16 @@
|
|
|
86
52
|
back to 100% -- its line is a silhouette rather than a field's edge, and
|
|
87
53
|
there is nothing else marking an unchecked box. */
|
|
88
54
|
--_line-rest: 60%;
|
|
89
|
-
/* Two seams rather than one composed result. `--_line-tone` is what colour
|
|
90
|
-
line is, and the states below move it; `--_line` folds in
|
|
91
|
-
the
|
|
92
|
-
restate
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
55
|
+
/* Two seams rather than one composed result. `--_line-tone` is what colour
|
|
56
|
+
the line is, and the states below move it; `--_line` folds in the fill,
|
|
57
|
+
walking the line toward the intent colour by the fill amount, so a state
|
|
58
|
+
never has to restate that blend to keep it. It is a field's own form of P3
|
|
59
|
+
rather than `box`'s: a field keeps its line whatever it is filled with, so
|
|
60
|
+
the line walks toward the fill's colour instead of fading out into it.
|
|
61
|
+
|
|
62
|
+
A single `--_line` that states overwrote would lose the blend along with
|
|
63
|
+
the tone, because replacing a composed result drops every term it was
|
|
64
|
+
composed from -- the same failure as the edge below. */
|
|
98
65
|
--_line-tone: color-mix(in oklab, var(--intent-border) var(--_line-rest), transparent);
|
|
99
66
|
--_line: color-mix(in oklab, var(--intent-color) var(--_fill), var(--_line-tone));
|
|
100
67
|
/* The edge floor, as an input to `box` rather than a rewrite of its result.
|
|
@@ -106,17 +73,18 @@
|
|
|
106
73
|
An element's own `.edgeless` lowers the floor -- see the rule below the
|
|
107
74
|
utility -- which is a consumer naming what they want rather than a cascade
|
|
108
75
|
reaching something nobody classed. `.checkbox` and `.radio` are left off
|
|
109
|
-
that rule and keep the floor absolute: they have nothing outside their
|
|
110
|
-
so removing it removes the control.
|
|
111
|
-
|
|
112
|
-
This used to be `--_edge: color-mix(...)` written after `@apply box`, which
|
|
113
|
-
dropped `--ui-border` out of the composition entirely. `.edged` and
|
|
114
|
-
`.edgeless` were both inert on all six text controls, and `--_d-border` was
|
|
115
|
-
declared and never read on each of them. */
|
|
76
|
+
that rule and keep the floor absolute: they have nothing outside their
|
|
77
|
+
line, so removing it removes the control. */
|
|
116
78
|
--_edge-floor: 100%;
|
|
117
79
|
|
|
118
80
|
@apply w-full appearance-none;
|
|
119
81
|
|
|
82
|
+
/* The surrounding font rather than the user agent's smaller field one, on
|
|
83
|
+
every entry: `/components` carries no reset to say so. */
|
|
84
|
+
font-family: inherit;
|
|
85
|
+
font-size: inherit;
|
|
86
|
+
line-height: inherit;
|
|
87
|
+
|
|
120
88
|
&::placeholder {
|
|
121
89
|
color: var(--color-text-secondary);
|
|
122
90
|
}
|
|
@@ -133,148 +101,169 @@
|
|
|
133
101
|
--_line-tone: var(--intent-color);
|
|
134
102
|
}
|
|
135
103
|
|
|
136
|
-
/*
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
and
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
the
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
`
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
than `--ui-fg-on-fill` directly (see that seam, above): the other half of
|
|
159
|
-
what a fill class asks for is how much contrast ink it wants
|
|
160
|
-
(`--ui-fg-on-fill: 0%` is `.ghost`/`.soft` declaring "none, my plate stays
|
|
161
|
-
quiet"), and pinning the fill without pinning this left a checked
|
|
162
|
-
`.ghost`/`.soft` toggle with an opaque plate and a mark that still read as
|
|
163
|
-
though the plate were quiet -- both derived from the same intent tone,
|
|
164
|
-
close enough to disappear into each other. `--_on-fill` ramps against the
|
|
165
|
-
*composed* `--_fill`, so once that is pinned to 100% the ink has to reach
|
|
166
|
-
full contrast too, or the ramp's own ceiling has nothing to reach. */
|
|
104
|
+
/* S1, P6: `:checked` is a state, so it re-declares the presentation inputs --
|
|
105
|
+
a full fill and full contrast ink -- and lifts the cap that would bound
|
|
106
|
+
them, and `box` composes the rest. It does not write a result: pinning
|
|
107
|
+
`--_fill` and `--_fg` directly would make a checked toggle ignore its own
|
|
108
|
+
aesthetic and elevation along with presentation. It sits in `utilities` at
|
|
109
|
+
0-2-0, above the fill classes in `components`, so a checked toggle renders
|
|
110
|
+
one plate whatever presentation it or a container asks for, and a
|
|
111
|
+
consumer's utility loses to it because state wins over every choice
|
|
112
|
+
(model.md#precedence). A toggle is a void `<input>`, so the inputs it
|
|
113
|
+
writes reach nothing but its own mark.
|
|
114
|
+
|
|
115
|
+
The checked fill is 100% whatever the presentation, so a checkbox, a radio,
|
|
116
|
+
and a switch each have one "on" identity; presentation separates the
|
|
117
|
+
*unchecked* plate instead (see `checkbox`/`radio`/`switch` below). It lives
|
|
118
|
+
here, not on the toggles, because a text input is never `:checked`.
|
|
119
|
+
|
|
120
|
+
The ink is declared alongside the fill: `--ui-fg-on-fill: 0%` is
|
|
121
|
+
`.ghost`/`.soft` declaring "my plate stays quiet", and a checked toggle
|
|
122
|
+
with an opaque plate and an ink that still expects a quiet one loses its
|
|
123
|
+
mark into the plate. `box` ramps the ink against the bounded fill before
|
|
124
|
+
`--ui-bg-alpha` thins it, so a checked toggle under `.glass` keeps full
|
|
125
|
+
contrast ink on its translucent plate. */
|
|
167
126
|
&:checked {
|
|
127
|
+
--ui-fill: 100%;
|
|
128
|
+
--ui-fg-on-fill: 100%;
|
|
168
129
|
--_fill-cap: 100%;
|
|
169
|
-
--_fill-floor: 100%;
|
|
170
|
-
--_fg-on-fill-floor: 100%;
|
|
171
130
|
}
|
|
131
|
+
}
|
|
172
132
|
|
|
173
|
-
|
|
133
|
+
/* The browser's own field decorations -- the date picker button, a number's
|
|
134
|
+
spin buttons, a search field's clear and magnifier -- stay by default,
|
|
135
|
+
because each one is also behaviour: hiding the picker button hides the only
|
|
136
|
+
pointer route to the picker. `.unadorned` removes them for a consumer who
|
|
137
|
+
draws their own adornments in an `.input-group` and wires the behaviour back
|
|
138
|
+
(`showPicker()`, `stepUp()`/`stepDown()`, clearing the value). On the
|
|
139
|
+
control, or on the group to reach the control inside.
|
|
140
|
+
|
|
141
|
+
Firefox exposes no pseudo-element for its date picker button, so that one
|
|
142
|
+
stays there; its number spinners go with `appearance: textfield`. */
|
|
143
|
+
@utility unadorned {
|
|
174
144
|
&::-webkit-search-decoration,
|
|
175
145
|
&::-webkit-search-cancel-button,
|
|
176
146
|
&::-webkit-search-results-button,
|
|
177
147
|
&::-webkit-search-results-decoration,
|
|
178
148
|
&::-webkit-calendar-picker-indicator,
|
|
179
149
|
&::-webkit-inner-spin-button,
|
|
180
|
-
&::-webkit-
|
|
150
|
+
&::-webkit-outer-spin-button,
|
|
151
|
+
&::-webkit-clear-button,
|
|
152
|
+
& > input::-webkit-search-decoration,
|
|
153
|
+
& > input::-webkit-search-cancel-button,
|
|
154
|
+
& > input::-webkit-search-results-button,
|
|
155
|
+
& > input::-webkit-search-results-decoration,
|
|
156
|
+
& > input::-webkit-calendar-picker-indicator,
|
|
157
|
+
& > input::-webkit-inner-spin-button,
|
|
158
|
+
& > input::-webkit-outer-spin-button,
|
|
159
|
+
& > input::-webkit-clear-button {
|
|
181
160
|
-webkit-appearance: none;
|
|
182
|
-
display: none
|
|
183
|
-
opacity: 0;
|
|
184
|
-
pointer-events: none;
|
|
185
|
-
width: 0;
|
|
186
|
-
height: 0;
|
|
161
|
+
display: none;
|
|
187
162
|
}
|
|
188
|
-
}
|
|
189
163
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
quieter box, measured and inverted on `.ipt`, `.checkbox` and `.radio`.
|
|
195
|
-
20% clears `.soft`'s 12% and matches the plate a `.solid` badge draws.
|
|
196
|
-
|
|
197
|
-
`.switch` is deliberately absent: its track caps at 40% in its own utility,
|
|
198
|
-
and an unlayered rule here would overrule that. See model.md#the-bounds-that-survive-and-the-test-for-keeping-one. */
|
|
199
|
-
:is(.text-control, .ipt, .textarea, .select, .input-group).soft {
|
|
200
|
-
--_fill-cap: 12%;
|
|
164
|
+
&[type="number"],
|
|
165
|
+
& > input[type="number"] {
|
|
166
|
+
appearance: textfield;
|
|
167
|
+
}
|
|
201
168
|
}
|
|
202
169
|
|
|
203
|
-
:
|
|
204
|
-
|
|
205
|
-
|
|
170
|
+
/* The component seams: rules that exist to beat their own component's
|
|
171
|
+
`@utility`, placed in `utilities` and winning there on specificity, each at
|
|
172
|
+
least 0-2-0 against the component's 0-1-0. They write only package privates
|
|
173
|
+
(`--_fill-cap`, `--_edge-floor`) or a state's intent slots, so nothing a
|
|
174
|
+
consumer writes competes with them. See
|
|
175
|
+
docs/internal/model.md#cascade-layers. */
|
|
176
|
+
@layer utilities {
|
|
177
|
+
/* A fill class on the element names its own cap; the 6% cascade cap in the
|
|
178
|
+
utility is for a container reaching a field nobody classed. Both raise
|
|
179
|
+
`--_fill-cap` rather than writing `--_fill` (P6), which is also what keeps
|
|
180
|
+
`.soft` < `.solid`: written as a rewrite, the louder name renders the
|
|
181
|
+
quieter box. 20% clears `.soft`'s 12% and matches the plate a `.solid`
|
|
182
|
+
badge draws.
|
|
183
|
+
|
|
184
|
+
`.switch` is deliberately absent: its track caps at 40% in its own utility,
|
|
185
|
+
and a 0-2-0 rule here would overrule that. See
|
|
186
|
+
model.md#the-bounds-that-survive-and-the-test-for-keeping-one. */
|
|
187
|
+
:is(.text-control, .ipt, .textarea, .select, .input-group).soft {
|
|
188
|
+
--_fill-cap: 12%;
|
|
189
|
+
}
|
|
206
190
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
`.text-control` composition does check itself. Declared where the intents
|
|
211
|
-
are (0-2-0, unlayered): inside `text-control` `.neutral` would overrule it. */
|
|
212
|
-
:is(.text-control, input):checked {
|
|
213
|
-
--intent-fill-max: 100%;
|
|
214
|
-
}
|
|
191
|
+
:is(.text-control, .ipt, .textarea, .select, .input-group).solid {
|
|
192
|
+
--_fill-cap: 20%;
|
|
193
|
+
}
|
|
215
194
|
|
|
216
|
-
/*
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
:is(.checkbox, .radio, .switch) {
|
|
226
|
-
--intent-fill-max: 100%;
|
|
227
|
-
}
|
|
195
|
+
/* A checked text-control fills with its own intent, so neutral has to drop its
|
|
196
|
+
20% cap here. No toggle needs this arm any more (see below), but a text
|
|
197
|
+
input is never `:checked` in practice either -- kept for whatever custom
|
|
198
|
+
`.text-control` composition does check itself. A plain rule at 0-2-0 in
|
|
199
|
+
`utilities`, above every intent class in `components`, so `.neutral` on the
|
|
200
|
+
element cannot put the cap back. */
|
|
201
|
+
:is(.text-control, input):checked {
|
|
202
|
+
--intent-fill-max: 100%;
|
|
203
|
+
}
|
|
228
204
|
|
|
229
|
-
/* The
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
205
|
+
/* The three toggles read their own component cap (`--_fill-cap`, below) as the
|
|
206
|
+
ceiling regardless of intent, checked or not -- not just at `:checked`, the
|
|
207
|
+
way the arm above still works for a hypothetical checked text-control. A
|
|
208
|
+
neutral fill stops at 20% everywhere else because it is the page's own ink,
|
|
209
|
+
and a full one reads as a slab on a badge or a card. That reasoning does not
|
|
210
|
+
hold for a toggle at rest: a neutral checkbox is the common case, most
|
|
211
|
+
controls carry no intent class at all, and it has to look like a present,
|
|
212
|
+
interactive box rather than a washed-out one just because nothing colored
|
|
213
|
+
it. Declared where the intents are, for the reason given above. */
|
|
214
|
+
:is(.checkbox, .radio, .switch) {
|
|
215
|
+
--intent-fill-max: 100%;
|
|
216
|
+
}
|
|
238
217
|
|
|
239
|
-
/*
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
specificity than the rule above (three classes, not an `:is()` of one)
|
|
249
|
-
wins regardless of source order. */
|
|
250
|
-
.switch.ghost.edgeless {
|
|
251
|
-
--_edge-floor: 100%;
|
|
252
|
-
}
|
|
218
|
+
/* The element's own `.edgeless` lowers the floor `text-control` raises -- a
|
|
219
|
+
class on the element is a consumer's request, a class on a container is a
|
|
220
|
+
cascade. `.checkbox` and `.radio` are absent: an unchecked one is a
|
|
221
|
+
transparent square/circle with nothing but its line, so the floor is
|
|
222
|
+
absolute and `.edgeless` on them is published unsupported. `.input-group`
|
|
223
|
+
is on it because the group is the field boundary. */
|
|
224
|
+
:is(.text-control, .ipt, .textarea, .select, .switch, .input-group).edgeless {
|
|
225
|
+
--_edge-floor: 0%;
|
|
226
|
+
}
|
|
253
227
|
|
|
254
|
-
/*
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
228
|
+
/* `.switch`'s one exception to the opt-out above: `.ghost` already asks for
|
|
229
|
+
zero fill, so a `.switch.ghost.edgeless` track would have neither a line
|
|
230
|
+
nor a plate and disappear -- nothing would mark its bounds but the floating
|
|
231
|
+
knob. Every other fill on this component has something to paint
|
|
232
|
+
(`.soft`/`.solid`/default all rest above 0%), so only this pairing needs
|
|
233
|
+
the floor held up. `.switch.ghost.edgeless` therefore renders identically
|
|
234
|
+
to `.switch.ghost.edged`, and is published unsupported as a distinct look.
|
|
235
|
+
Higher specificity than the rule above (three classes, not an `:is()` of
|
|
236
|
+
one) wins regardless of source order. */
|
|
237
|
+
.switch.ghost.edgeless {
|
|
238
|
+
--_edge-floor: 100%;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* An invalid control is destructive whatever else it was asked to be. It sits
|
|
242
|
+
in `utilities`, above every intent class and both intent resets in
|
|
243
|
+
`components`, and `:is()` gives it 0-2-0 so it also outranks the control's
|
|
244
|
+
own utility; a consumer's utility setting an intent slot on an invalid
|
|
245
|
+
field loses to it too, because state wins over every choice
|
|
246
|
+
(model.md#precedence). `--intent-hover` is redirected with the others so
|
|
247
|
+
the line does not go grey under the pointer. The `:has()` arm bridges an
|
|
248
|
+
inner control's state to an `.input-group`. */
|
|
249
|
+
:is(
|
|
250
|
+
.text-control,
|
|
251
|
+
.ipt,
|
|
252
|
+
.textarea,
|
|
253
|
+
.select,
|
|
254
|
+
.checkbox,
|
|
255
|
+
.radio,
|
|
256
|
+
.switch,
|
|
257
|
+
.input-group,
|
|
258
|
+
input,
|
|
259
|
+
textarea,
|
|
260
|
+
select
|
|
261
|
+
)[aria-invalid="true"],
|
|
262
|
+
.input-group:has(> :is(.ipt, .textarea, .select, .text-control, input, textarea, select)[aria-invalid="true"]) {
|
|
263
|
+
--intent-color: var(--color-destructive);
|
|
264
|
+
--intent-hover: var(--color-destructive-hover);
|
|
265
|
+
--intent-border: var(--color-destructive);
|
|
266
|
+
}
|
|
278
267
|
}
|
|
279
268
|
|
|
280
269
|
/* A ghost, edged, unelevated text control. */
|
|
@@ -309,14 +298,16 @@
|
|
|
309
298
|
letter-spacing: 0.025em;
|
|
310
299
|
}
|
|
311
300
|
|
|
301
|
+
/* A hint wraps, so it takes `text-sm`'s leading like the package's other 14px
|
|
302
|
+
text rather than a line height equal to its size, where wrapped lines touch. */
|
|
312
303
|
@utility hint {
|
|
304
|
+
@apply text-sm;
|
|
305
|
+
|
|
313
306
|
color: var(--color-text-secondary);
|
|
314
|
-
font-size: 0.875rem;
|
|
315
307
|
font-weight: 400;
|
|
316
308
|
letter-spacing: 0.05em;
|
|
317
|
-
line-height: 100%;
|
|
318
309
|
|
|
319
|
-
&.
|
|
310
|
+
&.destructive {
|
|
320
311
|
color: var(--intent-color);
|
|
321
312
|
}
|
|
322
313
|
}
|
|
@@ -342,7 +333,7 @@
|
|
|
342
333
|
gradients rather than masked, because `appearance: none` removes the platform
|
|
343
334
|
arrow and a background image is the only thing a replaced element can paint. */
|
|
344
335
|
@utility select {
|
|
345
|
-
@apply text-control h-(--control-height,2.5rem) py-2
|
|
336
|
+
@apply text-control h-(--control-height,2.5rem) py-2 ps-3 pe-10;
|
|
346
337
|
|
|
347
338
|
--_d-fill: 0%;
|
|
348
339
|
--_d-fg-on-fill: 0%;
|
|
@@ -363,6 +354,35 @@
|
|
|
363
354
|
calc(100% - 0.6875rem) center;
|
|
364
355
|
background-repeat: no-repeat;
|
|
365
356
|
background-size: 0.3125rem 0.3125rem;
|
|
357
|
+
|
|
358
|
+
/* The chevron sits at the inline end, like the padding kept for it, so a
|
|
359
|
+
right-to-left select draws it at the left. `background-position` has no
|
|
360
|
+
logical form, so the mirror is named here. */
|
|
361
|
+
&:dir(rtl) {
|
|
362
|
+
background-position:
|
|
363
|
+
0.6875rem center,
|
|
364
|
+
1rem center;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* The open list is painted by the browser, over the select's own background
|
|
368
|
+
when it has one. A ghost select has none, so Chromium falls back to its
|
|
369
|
+
default popup -- white on Windows -- while the options inherit the select's
|
|
370
|
+
text, near-white in the dark theme: white on white. The options take the
|
|
371
|
+
page's ground and text instead, which follow the theme. */
|
|
372
|
+
& option,
|
|
373
|
+
& optgroup {
|
|
374
|
+
background-color: var(--color-background);
|
|
375
|
+
color: var(--color-text);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/* Several rows showing is a list box, not a drop-down: no chevron, the height
|
|
379
|
+
of its rows, and the chevron's room given back. `--control-height` stays
|
|
380
|
+
the floor. */
|
|
381
|
+
&:is([multiple], [size]:not([size="1"])) {
|
|
382
|
+
height: auto;
|
|
383
|
+
padding-inline: 0.75rem;
|
|
384
|
+
background-image: none;
|
|
385
|
+
}
|
|
366
386
|
}
|
|
367
387
|
|
|
368
388
|
/* A wrapper that owns the field box so a control can carry adornments beside it
|
|
@@ -416,7 +436,7 @@
|
|
|
416
436
|
--_line-tone: var(--intent-color);
|
|
417
437
|
|
|
418
438
|
outline: var(--focus-ring-width) solid var(--focus-ring);
|
|
419
|
-
outline-offset: var(--focus-ring-offset);
|
|
439
|
+
outline-offset: var(--focus-ring-offset, calc(-1 * var(--ui-border-width, var(--border-width))));
|
|
420
440
|
box-shadow: var(--_focus-stack, var(--_shadow));
|
|
421
441
|
}
|
|
422
442
|
|
|
@@ -462,19 +482,14 @@
|
|
|
462
482
|
/* The three toggles compose `text-control` for its bounds, not its chrome: the
|
|
463
483
|
edge floor keeps a container's `.edgeless` from erasing the closed silhouette
|
|
464
484
|
(WCAG 1.4.11), and the checked state that pins the fill (see `text-control`'s
|
|
465
|
-
`:checked` above) lives there. They raise the fill cap to 40
|
|
485
|
+
`:checked` above) lives there. They raise the fill cap to 40%: the 6% cap
|
|
466
486
|
keeps typed text legible and a toggle has none, and 40% is where a resting
|
|
467
487
|
`.ghost` (0%), `.soft`/default (12%), and `.solid` (the cap itself) read as
|
|
468
|
-
three distinct plates rather than three shades of
|
|
469
|
-
|
|
470
|
-
`.soft`
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
asks for -- a consumer still reaches that plate by writing `.solid` itself.
|
|
474
|
-
`.ghost` used to be unsupported here -- a checked one used to be a mark on
|
|
475
|
-
nothing -- and is not any more, now that checked reads the same plate
|
|
476
|
-
whatever the presentation.
|
|
477
|
-
See model.md#the-bounds-that-survive-and-the-test-for-keeping-one. */
|
|
488
|
+
three distinct plates rather than three shades of one wash. Unclassed rests
|
|
489
|
+
at `.soft` rather than at the cap: `--_d-fill`/`--_d-fg-on-fill` below name
|
|
490
|
+
the same pair `.soft` does, so a bare `<input class="checkbox">` reads as the
|
|
491
|
+
quiet tint, and a consumer reaches the louder plate by writing `.solid`. See
|
|
492
|
+
model.md#the-bounds-that-survive-and-the-test-for-keeping-one. */
|
|
478
493
|
@utility checkbox {
|
|
479
494
|
@apply text-control inline-grid size-4 cursor-pointer place-content-center;
|
|
480
495
|
|
|
@@ -485,12 +500,12 @@
|
|
|
485
500
|
--_d-border: 100%;
|
|
486
501
|
--_d-elevation: 0;
|
|
487
502
|
|
|
488
|
-
/*
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
503
|
+
/* A chip keeps a small default even under a large control corner, but an
|
|
504
|
+
explicit tight radius is a complete shape, including elliptical corners. */
|
|
505
|
+
border-radius: var(
|
|
506
|
+
--ui-radius-tight,
|
|
507
|
+
min(var(--ui-radius, var(--ui-corner, var(--radius-small))), var(--radius-small))
|
|
508
|
+
);
|
|
494
509
|
|
|
495
510
|
/* Chip geometry: the tight silhouette, and an inset edge no thicker than the
|
|
496
511
|
line the aesthetic draws elsewhere. A 16px box cannot spend four pixels a
|
|
@@ -506,7 +521,7 @@
|
|
|
506
521
|
rather than as an `outline`, so a clipping aesthetic clips it with
|
|
507
522
|
everything else and its own inset focus layer still paints over the top. */
|
|
508
523
|
&:focus-visible {
|
|
509
|
-
--_shadow: var(--_shadow-parts), 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
524
|
+
--_shadow: var(--_shadow-parts) var(--_halo-layer,), 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
510
525
|
}
|
|
511
526
|
|
|
512
527
|
&::after {
|
|
@@ -526,12 +541,39 @@
|
|
|
526
541
|
&:checked::after {
|
|
527
542
|
@apply opacity-100;
|
|
528
543
|
}
|
|
544
|
+
|
|
545
|
+
/* Mixed -- part of a group checked -- takes the checked plate, the same
|
|
546
|
+
inputs `text-control`'s `:checked` writes, and a dash for its mark. After
|
|
547
|
+
the checked mark, so a box that is both draws the dash, as the browser's
|
|
548
|
+
own checkbox does. */
|
|
549
|
+
&:indeterminate {
|
|
550
|
+
--ui-fill: 100%;
|
|
551
|
+
--ui-fg-on-fill: 100%;
|
|
552
|
+
--_fill-cap: 100%;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
&:indeterminate::after {
|
|
556
|
+
@apply opacity-100;
|
|
557
|
+
|
|
558
|
+
mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round'><line x1='5' y1='12' x2='19' y2='12'/></svg>");
|
|
559
|
+
-webkit-mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='3' stroke-linecap='round'><line x1='5' y1='12' x2='19' y2='12'/></svg>");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/* The mark appears without moving under reduced motion, for the entries
|
|
563
|
+
without the reset; see `box.css`. */
|
|
564
|
+
@media (prefers-reduced-motion: reduce) {
|
|
565
|
+
&::after {
|
|
566
|
+
transition-duration: 0.01ms;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
529
569
|
}
|
|
530
570
|
|
|
531
571
|
/* The same control with a round silhouette and a dot for its mark, and the
|
|
532
572
|
same fill cap raise given on `checkbox`. */
|
|
533
573
|
@utility radio {
|
|
534
|
-
@apply text-control inline-grid size-4 cursor-pointer place-content-center
|
|
574
|
+
@apply text-control inline-grid size-4 cursor-pointer place-content-center;
|
|
575
|
+
|
|
576
|
+
border-radius: var(--ui-radius-pill, calc(infinity * 1px));
|
|
535
577
|
|
|
536
578
|
--_d-fill: 12%;
|
|
537
579
|
--_d-fg-on-fill: 0%;
|
|
@@ -550,7 +592,7 @@
|
|
|
550
592
|
|
|
551
593
|
/* The ring, for the reason given on `checkbox`. */
|
|
552
594
|
&:focus-visible {
|
|
553
|
-
--_shadow: var(--_shadow-parts), 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
595
|
+
--_shadow: var(--_shadow-parts) var(--_halo-layer,), 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
554
596
|
}
|
|
555
597
|
|
|
556
598
|
&::after {
|
|
@@ -565,13 +607,12 @@
|
|
|
565
607
|
}
|
|
566
608
|
|
|
567
609
|
/* A checked radio thickens its ring and takes the intent whole on the line.
|
|
568
|
-
The fill is pinned by `text-control`'s `:checked
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
floor still draws the ring under a container's `.edgeless`. */
|
|
610
|
+
The fill is pinned by `text-control`'s `:checked`, so every checked radio
|
|
611
|
+
is a filled circle whatever fill class it carries. The dot takes `box`'s
|
|
612
|
+
composed foreground, which walks to the contrast tone as the (always full)
|
|
613
|
+
plate fills, so it stays readable. `--_line-tone` rather than `--_edge`, so
|
|
614
|
+
`text-control`'s edge floor still draws the ring under a container's
|
|
615
|
+
`.edgeless`. */
|
|
575
616
|
&:checked {
|
|
576
617
|
--_line-tone: var(--intent-color);
|
|
577
618
|
|
|
@@ -584,6 +625,14 @@
|
|
|
584
625
|
&:checked::after {
|
|
585
626
|
@apply scale-100 opacity-100;
|
|
586
627
|
}
|
|
628
|
+
|
|
629
|
+
/* The mark appears without moving under reduced motion, for the entries
|
|
630
|
+
without the reset; see `box.css`. */
|
|
631
|
+
@media (prefers-reduced-motion: reduce) {
|
|
632
|
+
&::after {
|
|
633
|
+
transition-duration: 0.01ms;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
587
636
|
}
|
|
588
637
|
|
|
589
638
|
/* A2: every dimension derives from the border width `box` resolved, so the knob
|
|
@@ -597,24 +646,30 @@
|
|
|
597
646
|
--_d-border: 100%;
|
|
598
647
|
--_d-elevation: 0;
|
|
599
648
|
|
|
600
|
-
/* 40%, measured: at `text-control`'s 6% the three resting fills
|
|
601
|
-
two looks
|
|
602
|
-
the one place a fill class
|
|
649
|
+
/* 40%, measured: at `text-control`'s 6% the three resting fills collapse to
|
|
650
|
+
two looks, so the switch would have to vary its *line* per fill class --
|
|
651
|
+
the one place a fill class would decide an edge. 40% separates all three
|
|
603
652
|
(29/67/144 sRGB steps on the light page, against ~20 for a visible one)
|
|
604
653
|
without reaching a checked track's pinned 100%, so the knob's position
|
|
605
|
-
still says which
|
|
606
|
-
raise their own cap to the same value for the same reason
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
not the 20% ceiling a badge or a card stays under. */
|
|
654
|
+
still says which; `axes.spec.ts` asserts all three. `checkbox`/`radio`
|
|
655
|
+
raise their own cap to the same value for the same reason. A cap rather
|
|
656
|
+
than a resting value, because it answers the cascade case;
|
|
657
|
+
`--intent-fill-max` (declared where the intents are, above) lifts to 100%
|
|
658
|
+
for all three toggles so a neutral one reads this cap, not the 20% ceiling
|
|
659
|
+
a badge or a card stays under. */
|
|
612
660
|
--_fill-cap: 40%;
|
|
613
661
|
|
|
614
662
|
--switch-height: 1.25rem;
|
|
615
663
|
--switch-width: 2.5rem;
|
|
616
664
|
--switch-gap: 1px;
|
|
617
|
-
|
|
665
|
+
/* The edge an aesthetic draws as an inset ring rather than as a border
|
|
666
|
+
(`.pixel`), present only while `--ui-shadow-edge` is declared -- the idiom
|
|
667
|
+
`box` uses. That ring paints under the knob, so a knob sized from the
|
|
668
|
+
border alone covered it: under pixel the border is zero, the knob met the
|
|
669
|
+
edge, and an unchecked and a checked switch both read as two halves. */
|
|
670
|
+
--_switch-ring: var(--ui-shadow-edge) var(--_ss);
|
|
671
|
+
--_switch-offset: calc(var(--_switch-ring, 0px) + var(--switch-gap));
|
|
672
|
+
--switch-inset: calc(var(--_width) + var(--_switch-offset));
|
|
618
673
|
--switch-knob: calc(var(--switch-height) - var(--switch-inset) * 2);
|
|
619
674
|
--switch-travel: calc(var(--switch-width) - var(--switch-inset) * 2 - var(--switch-knob));
|
|
620
675
|
|
|
@@ -623,7 +678,7 @@
|
|
|
623
678
|
/* Pill by default; a squaring aesthetic reaches the track and the knob
|
|
624
679
|
together, and an aesthetic with a pill corner of its own reaches both
|
|
625
680
|
through that first. */
|
|
626
|
-
border-radius: var(--ui-radius-pill, var(--ui-radius, calc(infinity * 1px)));
|
|
681
|
+
border-radius: var(--ui-radius-pill, var(--ui-radius, var(--ui-corner, calc(infinity * 1px))));
|
|
627
682
|
|
|
628
683
|
/* Chip geometry, for the reason given on `checkbox`: a 20px track has no more
|
|
629
684
|
room for a thick inset edge than a 16px box does. */
|
|
@@ -633,7 +688,7 @@
|
|
|
633
688
|
|
|
634
689
|
/* The ring, for the reason given on `checkbox`. */
|
|
635
690
|
&:focus-visible {
|
|
636
|
-
--_shadow: var(--_shadow-parts), 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
691
|
+
--_shadow: var(--_shadow-parts) var(--_halo-layer,), 0 0 0 var(--focus-ring-width) var(--focus-ring);
|
|
637
692
|
}
|
|
638
693
|
|
|
639
694
|
&::after {
|
|
@@ -644,15 +699,15 @@
|
|
|
644
699
|
tone as the label beside it. Checked, it takes the mark colour like the
|
|
645
700
|
other two toggles. */
|
|
646
701
|
background-color: var(--color-text-secondary);
|
|
647
|
-
border-radius: var(--ui-radius-pill, var(--ui-radius, calc(infinity * 1px)));
|
|
702
|
+
border-radius: var(--ui-radius-pill, var(--ui-radius, var(--ui-corner, calc(infinity * 1px))));
|
|
648
703
|
/* A pseudo-element is not a box: the knob takes the track's corner shape
|
|
649
704
|
itself, so the two stay the same shape. */
|
|
650
705
|
corner-shape: var(--ui-corner-shape, round);
|
|
651
706
|
content: "";
|
|
652
707
|
height: var(--switch-knob);
|
|
653
708
|
width: var(--switch-knob);
|
|
654
|
-
top: var(--
|
|
655
|
-
inset-inline-start: var(--
|
|
709
|
+
top: var(--_switch-offset);
|
|
710
|
+
inset-inline-start: var(--_switch-offset);
|
|
656
711
|
}
|
|
657
712
|
|
|
658
713
|
&:checked::after {
|
|
@@ -666,6 +721,14 @@
|
|
|
666
721
|
translate: calc(-1 * var(--switch-travel));
|
|
667
722
|
}
|
|
668
723
|
}
|
|
724
|
+
|
|
725
|
+
/* The mark appears without moving under reduced motion, for the entries
|
|
726
|
+
without the reset; see `box.css`. */
|
|
727
|
+
@media (prefers-reduced-motion: reduce) {
|
|
728
|
+
&::after {
|
|
729
|
+
transition-duration: 0.01ms;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
669
732
|
}
|
|
670
733
|
|
|
671
734
|
@media (forced-colors: active) {
|
|
@@ -679,7 +742,14 @@
|
|
|
679
742
|
transition: none;
|
|
680
743
|
}
|
|
681
744
|
|
|
745
|
+
/* The switch knob is the one mark visible while unchecked, and with forced
|
|
746
|
+
colours off it would keep the package grey on a system Canvas. */
|
|
747
|
+
.switch::after {
|
|
748
|
+
background-color: CanvasText;
|
|
749
|
+
}
|
|
750
|
+
|
|
682
751
|
.checkbox:checked,
|
|
752
|
+
.checkbox:indeterminate,
|
|
683
753
|
.radio:checked,
|
|
684
754
|
.switch:checked {
|
|
685
755
|
background-color: Highlight;
|