clairity.css 0.1.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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +27 -0
- data/.gitignore +66 -0
- data/CHANGELOG.md +423 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +373 -0
- data/README.md +170 -0
- data/Rakefile +12 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/clairity.css.gemspec +51 -0
- data/lib/assets/css/clairity/base.css +951 -0
- data/lib/assets/css/clairity/classes.css +264 -0
- data/lib/assets/css/clairity/components.css +946 -0
- data/lib/assets/css/clairity/cosmetic.css +157 -0
- data/lib/assets/css/clairity/functions.css +46 -0
- data/lib/assets/css/clairity/icons.css +44 -0
- data/lib/assets/css/clairity/normalize.css +239 -0
- data/lib/assets/css/clairity/palette.css +329 -0
- data/lib/assets/css/clairity/shims.css +20 -0
- data/lib/assets/css/clairity/states.css +756 -0
- data/lib/assets/css/clairity/utilities.css +68 -0
- data/lib/assets/css/clairity/variables.css +301 -0
- data/lib/assets/css/clairity.basic.css +28 -0
- data/lib/assets/css/clairity.classless.css +50 -0
- data/lib/assets/css/clairity.css +109 -0
- data/lib/assets/images/input-image.png +0 -0
- data/lib/assets/images/logo.png +0 -0
- data/lib/assets/images/profile-avatar.png +0 -0
- data/lib/assets/js/clairity.css.js +6 -0
- data/lib/assets/media/t-rex_roar.mp3 +0 -0
- data/lib/clairity.css/version.rb +5 -0
- data/lib/clairity.css.rb +10 -0
- data/sig/clairity.css.rbs +4 -0
- data/test/test_clairity_css.rb +17 -0
- data/test/test_helper.rb +5 -0
- metadata +128 -0
@@ -0,0 +1,756 @@
|
|
1
|
+
/******************************************************************************\
|
2
|
+
STATES - these are convenience styles that go beyond defining the base elements.
|
3
|
+
in general, these add richness to the UI/UX while still being minimal
|
4
|
+
and "classless", but not pseudo-classless.
|
5
|
+
|
6
|
+
regarding new selectors, :not() for a single element is well-supported
|
7
|
+
but not so for a list of selectors (until very recently). :has() is
|
8
|
+
only safari 15.4+ and chrome 105+, with firefox support on the way.
|
9
|
+
\******************************************************************************/
|
10
|
+
|
11
|
+
|
12
|
+
/* -----------------------------------------------------------------------------
|
13
|
+
// #root - global states - listed first so they can be overriden subsequently
|
14
|
+
// -------------------------------------------------------------------------- */
|
15
|
+
|
16
|
+
/* #:hover - default :hover border for form inputs and buttons */
|
17
|
+
:hover { border-color: var(--highlighted); } /* [0010] specificity */
|
18
|
+
|
19
|
+
/* #disabled #hover - must be >= specificity of equivablent non-disabled field*/
|
20
|
+
:is([disabled], :disabled):hover { /* [0020] specificity */
|
21
|
+
color: var(--inactive); /* for select options */
|
22
|
+
background-color: var(--bg-inactive); /* messes up unchecked radio */
|
23
|
+
border: var(--solid) var(--border); /* to overcome :hover */
|
24
|
+
cursor: not-allowed; /* TODO: shouldn't need this? */
|
25
|
+
--icon-color: unset; /* revert icon color to previous value */
|
26
|
+
}
|
27
|
+
|
28
|
+
/* #:focus - the focused pseudo-class, used by links, form fields, etc. */
|
29
|
+
:focus { }
|
30
|
+
/* #:focus-visible - applies to keyboard-focused buttons but not mouse-focused.
|
31
|
+
note safari only started supporting this with 15.4 - [0010] specificity */
|
32
|
+
:focus-visible {
|
33
|
+
outline: var(--dashed) var(--emphasized);
|
34
|
+
outline-offset: var(--thick);
|
35
|
+
}
|
36
|
+
|
37
|
+
/* #:active - actuated element, typically also has :focus, useful for keyboard
|
38
|
+
users and accessibility in general */
|
39
|
+
:active { } /* TODO: consider low-vision/keyboard users */
|
40
|
+
|
41
|
+
/* #:empty - has no children and no content other than certain whitespace */
|
42
|
+
:empty { }
|
43
|
+
|
44
|
+
|
45
|
+
/* -----------------------------------------------------------------------------
|
46
|
+
// #sectioning - block type elements meant to hold other content elements
|
47
|
+
// -------------------------------------------------------------------------- */
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
/* -----------------------------------------------------------------------------
|
52
|
+
// #headers - heading-related elements
|
53
|
+
// -------------------------------------------------------------------------- */
|
54
|
+
|
55
|
+
/*style header fragment no differently to headers- [0001] / [0011] specificity*/
|
56
|
+
:is(h1, h2, h3, h4, h5, h6) :where([href^="#"]),
|
57
|
+
:is(h1, h2, h3, h4, h5, h6) :where([href^="#"]):hover,
|
58
|
+
:is(h1, h2, h3, h4, h5, h6) :where([href^="#"]):visited {
|
59
|
+
color: unset;
|
60
|
+
font-weight: unset;
|
61
|
+
text-decoration: unset;
|
62
|
+
}
|
63
|
+
|
64
|
+
/* header fragment indicator - [0001] specificity */
|
65
|
+
:where(h1, h2, h3, h4, h5, h6) :where([href^="#"]:hover)::after {
|
66
|
+
/*display: inline-flex;*/
|
67
|
+
font-size: var(--smaller);
|
68
|
+
color: var(--inactive);
|
69
|
+
content: "#";
|
70
|
+
margin-left: var(--s);
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
/* -----------------------------------------------------------------------------
|
75
|
+
// #links - for linking to other documents
|
76
|
+
// -------------------------------------------------------------------------- */
|
77
|
+
|
78
|
+
/* placeholder links shouldn't be styled like links until an href is added */
|
79
|
+
:where(a, a:hover):not([href]) { /* [0011]/[0021] specificity */
|
80
|
+
color: inherit;
|
81
|
+
text-decoration: none;
|
82
|
+
}
|
83
|
+
|
84
|
+
/* #mailto #tel #sms #file #external #bookmark #download - special link icons -
|
85
|
+
this uses the svg mask technique to place & color icons on hover; as such, it
|
86
|
+
must be in a separate pseudo-element so masking doesn't bleed out to text */
|
87
|
+
/* #icons - set --icon to an svg from icons.css, set color with --icon-color */
|
88
|
+
[href^="tel:"] { --icon: var(--i-phone); }
|
89
|
+
[href^="sms:"] { --icon: var(--i-message-2); }
|
90
|
+
[href^="file:"] { --icon: var(--i-file); }
|
91
|
+
[download] { --icon: var(--i-file-download); }
|
92
|
+
[href^="mailto:"] { --icon: var(--i-mail); }
|
93
|
+
[rel~="external"] { --icon: var(--i-external-link); }
|
94
|
+
[rel~="bookmark"] { --icon: var(--i-bookmark); }
|
95
|
+
|
96
|
+
/* chrome still needs prefixed -webkit-mask as of 20221001 */
|
97
|
+
:where([href^="tel:"], [href^="sms:"], [href^="file:"], [download])::before,
|
98
|
+
:where([href^="mailto:"], [rel~="external"], [rel~="bookmark"])::after {
|
99
|
+
content: ''; /* without content, no icon appears */
|
100
|
+
-webkit-mask: var(--icon) no-repeat center / var(--field-icon);
|
101
|
+
mask: var(--icon) no-repeat center / var(--field-icon);
|
102
|
+
background-color: var(--icon-color);
|
103
|
+
padding: var(--inline-padding);
|
104
|
+
}
|
105
|
+
|
106
|
+
:where([href^="mailto:"], [rel~="external"], [rel~="bookmark"])::after {
|
107
|
+
margin-left: var(--xs);
|
108
|
+
padding-top: var(--xs);
|
109
|
+
}
|
110
|
+
|
111
|
+
a:where([href^="mailto:"], [href^="tel:"], [href^="sms:"], [href^="file:"],
|
112
|
+
[rel~="external"], [rel~="bookmark"], [download]):hover {
|
113
|
+
--icon-color: var(--active);
|
114
|
+
}
|
115
|
+
|
116
|
+
/* give followed fragment links some room at the top - [0010] specificity */
|
117
|
+
[id] { scroll-margin-top: var(--l); }
|
118
|
+
|
119
|
+
/* #:any-link matches both :link and :visited for anchors with an href; safari
|
120
|
+
support is currently lagging, so don't use for now */
|
121
|
+
:any-link { }
|
122
|
+
/* #:local-link - targets same-document links - currently unspported */
|
123
|
+
/*:local-link { }*/
|
124
|
+
|
125
|
+
|
126
|
+
/* -----------------------------------------------------------------------------
|
127
|
+
// #lists - ordered and unordered lists of items
|
128
|
+
// -------------------------------------------------------------------------- */
|
129
|
+
/* #ul #ol - removes vertical gaps for lists within lists - uses :where() to get
|
130
|
+
[0001] specificity rather than [0002] */
|
131
|
+
:where(ul, ol) :is(ul, ol) { margin: 0; }
|
132
|
+
|
133
|
+
|
134
|
+
/* -----------------------------------------------------------------------------
|
135
|
+
// #tables - for tabular data
|
136
|
+
// -------------------------------------------------------------------------- */
|
137
|
+
|
138
|
+
/* makes the first column sticky - [0012] specificity */
|
139
|
+
:not(tfoot) > tr th:first-of-type {
|
140
|
+
position: sticky; /* works best w/ <table> display ≅ block */
|
141
|
+
left: 0;
|
142
|
+
background-color: var(--background); /* is transparent otherwise */
|
143
|
+
}
|
144
|
+
/* makes the table header row sticky - [0002] specificity */
|
145
|
+
thead th {
|
146
|
+
position: sticky;
|
147
|
+
top: 0;
|
148
|
+
z-index: 1; /* puts header row on top of tbody rows, including
|
149
|
+
header column */
|
150
|
+
background-color: var(--background); /* is transparent otherwise */
|
151
|
+
}
|
152
|
+
/* sticks top left box on top of both header columns/rows - [0012] specificity*/
|
153
|
+
thead th:first-child { z-index: 2; }
|
154
|
+
|
155
|
+
/* zebra-striping of table rows - [0013] specificity */
|
156
|
+
tbody tr:nth-child(even), tbody tr:nth-child(even) th {
|
157
|
+
background-color: var(--bg-contrast);
|
158
|
+
}
|
159
|
+
/* this is more robust because it excludes hidden rows, but only safari and
|
160
|
+
chrome support this syntax - [0023] specificity */
|
161
|
+
@supports selector(:nth-child(even of :not([hidden]))) {
|
162
|
+
tbody tr:where(:nth-child( even of :not([hidden]) )),
|
163
|
+
tbody tr:where(:nth-child( even of :not([hidden]) )) th {
|
164
|
+
background-color: var(--bg-contrast);
|
165
|
+
}}
|
166
|
+
|
167
|
+
/* hover effect for table rows */
|
168
|
+
tbody tr:hover, tbody tr:hover th {
|
169
|
+
background-color: var(--contrast);
|
170
|
+
}
|
171
|
+
/* adds sticky borders below thead and above tfoot */
|
172
|
+
thead tr:last-of-type th {box-shadow: inset 0 -2px var(--secondary, --neutral);}
|
173
|
+
tfoot :where(td, th) {box-shadow: inset 0 2px var(--secondary, --neutral);}
|
174
|
+
|
175
|
+
|
176
|
+
/* -----------------------------------------------------------------------------
|
177
|
+
// #forms - for submitting data to the server
|
178
|
+
// -------------------------------------------------------------------------- */
|
179
|
+
/* #form #fieldset #focus-within - highlight whole forms & fieldsets when one of
|
180
|
+
its contained elements is focused - [0010] specificity */
|
181
|
+
:where(form, fieldset):focus-within {
|
182
|
+
/*background-color: var(--bg-contrast);*/
|
183
|
+
}
|
184
|
+
/* #legend #fieldset #hover - remove :hover effect on legend & fieldset by
|
185
|
+
setting the same border color - [0010] specificity */
|
186
|
+
:where(legend, fieldset):hover {
|
187
|
+
border-color: var(--border);
|
188
|
+
}
|
189
|
+
|
190
|
+
/* #form #ul - since lists in forms typically list out form controls, remove
|
191
|
+
the bullet styling from them - [0002] specificity */
|
192
|
+
form ul { list-style: none; }
|
193
|
+
|
194
|
+
/* #form #section #p #div - subgrid is firefox 71+ & safari 16+ only 20221001 */
|
195
|
+
@supports (grid-template-columns: subgrid) {
|
196
|
+
/* :where() can be used here, since @supports is also a recent addition */
|
197
|
+
form :where(section, p, div) { /* [0001] specificity */
|
198
|
+
grid-column: full;
|
199
|
+
display: grid;
|
200
|
+
grid-template-columns: subgrid;
|
201
|
+
gap: inherit;
|
202
|
+
}}
|
203
|
+
/*form section :where(h1,h2,h3,h4,h5,h6) + p { display: block; }*/
|
204
|
+
|
205
|
+
/* chrome/edge doesn't support subgrid yet but does support :has() & :where() */
|
206
|
+
@supports selector(:has(*)) {
|
207
|
+
fieldset:where(:has(label, input, button, textarea, select, fieldset)) {
|
208
|
+
margin: 0;
|
209
|
+
display: grid;
|
210
|
+
grid-template-columns:[labels full-start] 3fr [elements] 7fr [full-end];
|
211
|
+
gap: var(--m);
|
212
|
+
}
|
213
|
+
fieldset:where(:has(p :is([type='checkbox'], [type='radio'])):not(:has(select,
|
214
|
+
textarea, [type="text"], [type="date"], [type="email"], [type="password"],
|
215
|
+
[type="datetime-local"], [type="search"]))) {
|
216
|
+
row-gap: 0;
|
217
|
+
}
|
218
|
+
/* since grid properties fall through p tags, assign margin-top to first child
|
219
|
+
elements to get a gap between paragraphs of different input types */
|
220
|
+
fieldset p:has([type='checkbox'], [type='radio']) + p:has([type='radio'],
|
221
|
+
[type='checkbox']) :is([type='radio'], [type='checkbox'],
|
222
|
+
label):first-of-type {
|
223
|
+
margin-top: var(--m);
|
224
|
+
}
|
225
|
+
/* chrome/edge doesn't support subgrid, so pierce the grid barrier instead */
|
226
|
+
fieldset :where(p:has(label, input, button, textarea, select, fieldset)) {
|
227
|
+
display: contents;
|
228
|
+
}
|
229
|
+
/* labels go on the labels gridline */
|
230
|
+
:where(fieldset :is(p, div)) label {
|
231
|
+
grid-column: labels;
|
232
|
+
}
|
233
|
+
/* lists that contain radios/checkboxes go on the elements gridline */
|
234
|
+
form:has(fieldset :where(select, textarea, [type="text"], [type="date"],
|
235
|
+
[type="email"], [type="password"], [type="datetime-local"],
|
236
|
+
[type="search"])) fieldset ul:has(li label :where([type='radio'],
|
237
|
+
[type='checkbox'])) {
|
238
|
+
grid-column: elements;
|
239
|
+
}
|
240
|
+
|
241
|
+
/* inputs on the elements gridline; pad right side away from fieldset line */
|
242
|
+
:where(fieldset) :where(p):has(input, button, textarea, select, fieldset)
|
243
|
+
:where(input:not(:is([type='checkbox'],[type='radio']), button,
|
244
|
+
textarea, select, fieldset)) {
|
245
|
+
grid-column: elements;
|
246
|
+
margin-right: var(--m);
|
247
|
+
}
|
248
|
+
/* for radios & checks, invert the placement of the input and label */
|
249
|
+
:where(fieldset) :where(p):has(label) :where([type='checkbox'],
|
250
|
+
[type='radio']):has(+ label) {
|
251
|
+
grid-column: labels;
|
252
|
+
align-self: center;
|
253
|
+
justify-self: end;
|
254
|
+
}
|
255
|
+
:where(fieldset p:has(label) :is([type='checkbox'],
|
256
|
+
[type='radio']):has(+ label)) + label {
|
257
|
+
grid-column: elements;
|
258
|
+
justify-self: start;
|
259
|
+
}
|
260
|
+
/* for paragraph tags that aren't form element containers, span the grid */
|
261
|
+
:where(fieldset) :where(p, div, h2, h3, h4, h5, h6):not(:has(label, input,
|
262
|
+
button, textarea, select, fieldset)) {
|
263
|
+
display: block;
|
264
|
+
grid-column: full;
|
265
|
+
}
|
266
|
+
}
|
267
|
+
/*form :where(fieldset, input, select, button, input[type="button"]) {
|
268
|
+
grid-column: elements;
|
269
|
+
}*/
|
270
|
+
|
271
|
+
fieldset > :last-child {
|
272
|
+
margin-bottom: 0;
|
273
|
+
}
|
274
|
+
|
275
|
+
|
276
|
+
/* -----------------------------------------------------------------------------
|
277
|
+
// #inputs - standard form input elements
|
278
|
+
// -------------------------------------------------------------------------- */
|
279
|
+
|
280
|
+
/* if [size] is set on an input, respect that choice - [0011] specificity */
|
281
|
+
input[size] {
|
282
|
+
width: fit-content;
|
283
|
+
min-width: unset;
|
284
|
+
max-width: unset;
|
285
|
+
}
|
286
|
+
|
287
|
+
/* #input #optgroup #option #textarea #select #read-only #focus - bg field icons
|
288
|
+
are Field system color by default, and --icon-color on focus/hover */
|
289
|
+
:where(input, optgroup, option, textarea, select):focus {
|
290
|
+
border-color: var(--highlighted);
|
291
|
+
--icon-color: var(--primary);
|
292
|
+
}
|
293
|
+
|
294
|
+
/* input images */
|
295
|
+
[type="date"], [type="time"], [type="datetime"], [type="datetime-local"],
|
296
|
+
[type="month"], [type="week"], [type="url"], [type="email"],
|
297
|
+
[type="number"], [type="tel"], [type="password"], [type="search"] {
|
298
|
+
padding-left: var(--height);
|
299
|
+
background-blend-mode: var(--icon-blend-mode);
|
300
|
+
}
|
301
|
+
[type="date"], [type="month"], [type="week"] {
|
302
|
+
background-image: var(--colorize-icon), var(--i-calendar); }
|
303
|
+
[type="datetime"], [type="datetime-local"], [type="time"] {
|
304
|
+
background-image: var(--colorize-icon), var(--i-calendar-time); }
|
305
|
+
[type="url"] { background-image: var(--colorize-icon), var(--i-browser); }
|
306
|
+
[type="email"] { background-image: var(--colorize-icon), var(--i-mail); }
|
307
|
+
[type="number"] { background-image: var(--colorize-icon), var(--i-numbers); }
|
308
|
+
[type="tel"] { background-image: var(--colorize-icon), var(--i-phone); }
|
309
|
+
[type="password"]{ background-image: var(--colorize-icon), var(--i-lock); }
|
310
|
+
[type="search"] { background-image: var(--colorize-icon), var(--i-search); }
|
311
|
+
|
312
|
+
/*@media (prefers-color-scheme: dark) {
|
313
|
+
[type="date"], [type="time"], [type="datetime"], [type="datetime-local"],
|
314
|
+
[type="week"], [type="month"], [type="url"], [type="email"],
|
315
|
+
[type="number"], [type="tel"], [type="password"], [type="search"] {
|
316
|
+
mask: var(--icon) no-repeat center left / var(--field-icon);
|
317
|
+
background-color: var(--icon-color);
|
318
|
+
mask-repeat: no-repeat;
|
319
|
+
background-image: unset;
|
320
|
+
}
|
321
|
+
[type="date"], [type="month"], [type="week"] { --icon: var(--i-calendar); }
|
322
|
+
[type="datetime"], [type="datetime-local"], [type="time"] {
|
323
|
+
--icon: var(--i-calendar-time); }
|
324
|
+
[type="url"] { --icon: var(--i-browser); }
|
325
|
+
[type="email"] { --icon: var(--i-mail); }
|
326
|
+
[type="number"] { --icon: var(--i-numbers); }
|
327
|
+
[type="tel"] { --icon: var(--i-phone); }
|
328
|
+
[type="password"]{ --icon: var(--i-lock); }
|
329
|
+
[type="search"] { --icon: var(--i-search); }
|
330
|
+
}*/
|
331
|
+
|
332
|
+
/* hide nav search's placeholder text if the background icon is present; opacity
|
333
|
+
is used here because other methods are disallowed on ::placeholder - [0001]*/
|
334
|
+
:where(nav) :where([type="search"])::placeholder { opacity: 0; }
|
335
|
+
|
336
|
+
:where(input, optgroup, option, textarea, select):required:is(:valid,:in-range){
|
337
|
+
border-color: var(--success-high, forestgreen);
|
338
|
+
}
|
339
|
+
:where(input, optgroup, option, textarea, select):required:is(:valid,
|
340
|
+
:in-range):hover, :where(input, optgroup, option, textarea,
|
341
|
+
select):required:is(:valid, :in-range):focus {
|
342
|
+
border-color: var(--success, limegreen);
|
343
|
+
}
|
344
|
+
/* remove borders on :read-only text and textarea fields */
|
345
|
+
:where(textarea, [type="text"], [type="date"], [type="time"], [type="datetime"],
|
346
|
+
[type="datetime-local"], [type="month"],[type="week"], [type="email"],
|
347
|
+
[type="password"], [type="url"], [type="tel"], [type="number"],
|
348
|
+
[type="search"]):read-only:enabled {
|
349
|
+
border-style: none;
|
350
|
+
}
|
351
|
+
|
352
|
+
/*:read-only text fields shouldn't look editable on hover - [0040] specificity*/
|
353
|
+
:where(textarea, input:not([type="radio"],[type="checkbox"],[type="button"],
|
354
|
+
[type="reset"],[type="submit"],[type="image"])):read-only:enabled:hover {
|
355
|
+
border-style: none;
|
356
|
+
border-color: var(--emphasized);
|
357
|
+
}
|
358
|
+
/* deemphasize :read-only text fields on :focus - [0030] specificity*/
|
359
|
+
:where(textarea, [type="text"], [type="date"], [type="time"], [type="datetime"],
|
360
|
+
[type="datetime-local"], [type="month"],[type="week"], [type="email"],
|
361
|
+
[type="password"], [type="url"], [type="tel"], [type="number"],
|
362
|
+
[type="search"]):read-only:focus {
|
363
|
+
border-style: none;
|
364
|
+
border-color: var(--deemphasized);
|
365
|
+
}
|
366
|
+
|
367
|
+
:where(textarea, [type="text"], [type="date"], [type="time"], [type="datetime"],
|
368
|
+
[type="datetime-local"], [type="month"],[type="week"], [type="email"],
|
369
|
+
[type="password"], [type="url"], [type="tel"], [type="number"],
|
370
|
+
[type="search"]):hover {
|
371
|
+
--icon-color: var(--primary);
|
372
|
+
}
|
373
|
+
|
374
|
+
/* #:invalid - :invalid pseudo-class for when a field fails validation */
|
375
|
+
/*:where(input, optgroup, option, textarea, select):required:empty,*/
|
376
|
+
:where(input, optgroup, option, textarea, select):is(:invalid, :out-of-range) {
|
377
|
+
border-color: var(--warning-high, gold);
|
378
|
+
}
|
379
|
+
/* #invalid:hover - for when an :invalid field is also :hover'ed */
|
380
|
+
/*:where(input, optgroup, option, textarea, select):required:empty:hover,*/
|
381
|
+
:where(input, optgroup, option, textarea, select):is(:invalid,
|
382
|
+
:out-of-range):hover, :where(input, optgroup, option, textarea,
|
383
|
+
select):is(:invalid, :out-of-range):focus {
|
384
|
+
border-color: var(--warning, khaki);
|
385
|
+
}
|
386
|
+
|
387
|
+
/* HACK: these two rules undo validation borders on initial load because
|
388
|
+
browsers other than firefox don't support :user-invalid */
|
389
|
+
/*:is(input, optgroup, option, textarea, select):optional:not(:valid,
|
390
|
+
[type="button"], [type="reset"]) {
|
391
|
+
border-color: var(--border);
|
392
|
+
}
|
393
|
+
:is(input, optgroup, option, textarea, select):optional:not(:valid, :disabled,
|
394
|
+
[type="button"], [type="reset"]):hover {
|
395
|
+
border-color: var(--highlighted);
|
396
|
+
}
|
397
|
+
*/
|
398
|
+
/* #:user-invalid - experimental - firefox only for now */
|
399
|
+
:where(input, optgroup, option, textarea, select):user-invalid {
|
400
|
+
border-color: var(--error-high, firebrick);
|
401
|
+
}
|
402
|
+
:where(input, optgroup, option, textarea, select):user-invalid:hover,
|
403
|
+
:where(input, optgroup, option, textarea, select):user-invalid:focus {
|
404
|
+
border-color: var(--error, red);
|
405
|
+
}
|
406
|
+
|
407
|
+
|
408
|
+
/* -----------------------------------------------------------------------------
|
409
|
+
// #radio #checkbox - radio buttons and checkboxes
|
410
|
+
// -------------------------------------------------------------------------- */
|
411
|
+
|
412
|
+
/* indeterminate checkboxes - activated only via js - [0010] specificity */
|
413
|
+
:where([type="checkbox"]):indeterminate {
|
414
|
+
border-color: var(--warning);
|
415
|
+
background-image: var(--colorize-icon), var(--i-dash);
|
416
|
+
background-blend-mode: var(--icon-blend-mode);
|
417
|
+
background-position: center;
|
418
|
+
background-size: 1.1em;
|
419
|
+
box-shadow: unset;
|
420
|
+
}
|
421
|
+
:where([type="checkbox"]):indeterminate:required { /* [0020] specificity */
|
422
|
+
border-color: var(--error);
|
423
|
+
}
|
424
|
+
:where([type="checkbox"]):indeterminate:hover {
|
425
|
+
--icon-color: var(--300);
|
426
|
+
}
|
427
|
+
|
428
|
+
/* #radio #checkbox #label #disabled #checked - these 3 rules style the disabled
|
429
|
+
radio/checkbox and its subsequent label - [0010] specificity using :where();
|
430
|
+
don't want first rule to be [0001] as regular label rules would override it*/
|
431
|
+
:where([type="radio"], [type="checkbox"]):disabled + :where(label) {
|
432
|
+
color: var(--inactive);
|
433
|
+
cursor: not-allowed;
|
434
|
+
}
|
435
|
+
/* needs to be >= specificity of global :disabled:hover rule to override it */
|
436
|
+
:is([type="radio"], [type="checkbox"]):disabled { /* [0020] specificity */
|
437
|
+
background-color: var(--unchecked); /* removes "check" */
|
438
|
+
border: var(--solid) var(--deemphasized); /* grey border */
|
439
|
+
box-shadow: var(--inset) var(--unchecked); /* inset bg */
|
440
|
+
}
|
441
|
+
:is([type="radio"]:checked, [type="checkbox"]:checked):disabled {
|
442
|
+
background-color: var(--deactivated); /* greyed-out "check" */
|
443
|
+
}
|
444
|
+
|
445
|
+
/* if radios/checkboxes are inline, insert a gap between them for readability */
|
446
|
+
:where([type="radio"] + label, [type="checkbox"] + label) +
|
447
|
+
:is([type="radio"], [type="checkbox"]) { /* [0010] specificity */
|
448
|
+
margin-left: var(--m);
|
449
|
+
}
|
450
|
+
|
451
|
+
/* inputs are (somewhat unspecified as) "replaced elements" & therefore can't
|
452
|
+
have ::before or ::after pseudo-elements on itself; use on label instead */
|
453
|
+
/* mark required fields' labels; labels usually follow checkboxes & radios */
|
454
|
+
/* :has() is only safari 16+ and chrome 105+ for now */
|
455
|
+
@supports selector(:has(*)) {
|
456
|
+
label:has(+ :is(input, select):not([type="checkbox"], [type="radio"]):required)::after,
|
457
|
+
input:where([type="checkbox"], [type="radio"]):required + label::after {
|
458
|
+
content: " *";
|
459
|
+
}
|
460
|
+
/* mark as successful the fieldset & legend borders for valid radio buttons */
|
461
|
+
/* :has() is safari only for now */
|
462
|
+
|
463
|
+
fieldset:has([type="radio"]:required:checked),
|
464
|
+
fieldset:has([type="radio"]:required:checked) legend {
|
465
|
+
border-color: var(--success, forestgreen);
|
466
|
+
}
|
467
|
+
}
|
468
|
+
|
469
|
+
[type="checkbox"]:required:user-invalid + label {
|
470
|
+
color: var(--error, red);
|
471
|
+
}
|
472
|
+
|
473
|
+
|
474
|
+
/* -----------------------------------------------------------------------------
|
475
|
+
// specialized inputs - #date #time #datetime-local #month #week #datetime
|
476
|
+
// #email #password #search #file #image #url #tel #color
|
477
|
+
// #range #number #hidden
|
478
|
+
// -------------------------------------------------------------------------- */
|
479
|
+
|
480
|
+
/* search decorations - [0011] specificity */
|
481
|
+
[type="search"]::-webkit-search-cancel-button,
|
482
|
+
[type="search"]::-webkit-search-decoration {
|
483
|
+
appearance: none; /* needed? */
|
484
|
+
}
|
485
|
+
|
486
|
+
|
487
|
+
/* -----------------------------------------------------------------------------
|
488
|
+
// #buttons - <button>s and <input>s of type=button, reset, and submit
|
489
|
+
// -------------------------------------------------------------------------- */
|
490
|
+
|
491
|
+
/* #button #submit #hover - [0010] specificity */
|
492
|
+
:where(button, [type="button"], [type="submit"]):hover,
|
493
|
+
::file-selector-button:hover {
|
494
|
+
background-color: var(--bg-highlight);
|
495
|
+
border-color: var(--foreground);
|
496
|
+
}
|
497
|
+
/* #reset #hover */
|
498
|
+
:where([type="reset"]):hover { /* [0010] specificity */
|
499
|
+
background-color: var(--contrast); /* TODO: make grey in dark mode */
|
500
|
+
}
|
501
|
+
|
502
|
+
/* #disabled & disabled #hover button states - needs >= specificity of
|
503
|
+
non-disabled versions */
|
504
|
+
:is(button, button:hover, [type="button"], [type="submit"]):disabled,
|
505
|
+
[type="file"]:disabled::file-selector-button { /* [0020] specificity */
|
506
|
+
color: var(--inactive);
|
507
|
+
background-color: var(--bg-inactive);
|
508
|
+
border-color: var(--deemphasized);
|
509
|
+
cursor: not-allowed;
|
510
|
+
}
|
511
|
+
|
512
|
+
[type="file"]:disabled {
|
513
|
+
background-color: unset;
|
514
|
+
border: unset;
|
515
|
+
cursor: not-allowed;
|
516
|
+
}
|
517
|
+
|
518
|
+
[type="reset"]:disabled {
|
519
|
+
color: var(--inactive);
|
520
|
+
background-color: var(--background);
|
521
|
+
border-color: var(--deemphasized);
|
522
|
+
cursor: not-allowed;
|
523
|
+
}
|
524
|
+
|
525
|
+
/* highlights the :focus style */
|
526
|
+
:where(button, [type="button"], [type="reset"], [type="submit"],
|
527
|
+
[type="image"]):not(:disabled):active {
|
528
|
+
outline: var(--dashed) var(--bg-highlight);
|
529
|
+
outline-offset: var(--thick);
|
530
|
+
}
|
531
|
+
|
532
|
+
/* includes css from .tertiary.button - [00xx] specificity */
|
533
|
+
[role="alert"] button:empty {
|
534
|
+
color: currentColor; /* invert foreground/background */
|
535
|
+
background-color: unset; /* border is transparent */
|
536
|
+
padding-inline: var(--xxs);
|
537
|
+
float: right;
|
538
|
+
}
|
539
|
+
|
540
|
+
[role="alert"] button:empty::after {
|
541
|
+
content: '⨉';
|
542
|
+
font-weight: var(--boldest);
|
543
|
+
}
|
544
|
+
|
545
|
+
[role="alert"] button:empty:hover {
|
546
|
+
text-decoration: underline; /* TODO: make grey in dark mode */
|
547
|
+
border-color: var(--transparent);
|
548
|
+
}
|
549
|
+
|
550
|
+
/*form:has(button[aria-label="close" i]:empty) {
|
551
|
+
padding-inline: var(--xxs);
|
552
|
+
float: right;
|
553
|
+
}
|
554
|
+
*/
|
555
|
+
/* -----------------------------------------------------------------------------
|
556
|
+
// #selections - dropdown/multi-item single-/multiple-selection form controls
|
557
|
+
// -------------------------------------------------------------------------- */
|
558
|
+
|
559
|
+
/* #option #hover #disabled */
|
560
|
+
option:not(:disabled, [disabled]):hover { /* [0011] specificity */
|
561
|
+
color: var(--whitish);
|
562
|
+
background-color: var(--bg-highlight);
|
563
|
+
}
|
564
|
+
option:disabled, option:disabled:hover { /* [0011] / [0021] specificity */
|
565
|
+
border-color: var(--transparent);
|
566
|
+
background-color: unset;
|
567
|
+
}
|
568
|
+
|
569
|
+
/* #select #multiple #hover #active - dropdown selection form controls- [0011]*/
|
570
|
+
select:not(:disabled, [disabled]):hover, select:active { /* dropdown chevron */
|
571
|
+
background-image: url("data:image/svg+xml;utf8,<svg \
|
572
|
+
xmlns='http://www.w3.org/2000/svg' width='40' height='40'><path \
|
573
|
+
d='M0,10 20,30 40,10' fill='none' stroke='%23666' stroke-width='2'/></svg>");
|
574
|
+
}
|
575
|
+
/* multi-selects shouldn't have chevron - [0020] specificity */
|
576
|
+
[multiple]:hover, [multiple]:active, select[size]:hover, select[size]:active {
|
577
|
+
background-image: none;
|
578
|
+
}
|
579
|
+
/* #multiple #option #checked */
|
580
|
+
[multiple] option:disabled:checked {
|
581
|
+
background-color: var(--deactivated) linear-gradient(0deg,
|
582
|
+
var(--deactivated) 0%, var(--deactivated) 100%);
|
583
|
+
}
|
584
|
+
/* #multiple #focus #option #disabled #checked */
|
585
|
+
:where(select[size], [multiple]):focus option:not(:disabled):checked {
|
586
|
+
/* --checked may be invalid due to SelectedItem, so provide a fallback */
|
587
|
+
background-color: var(--checked, LinkText) linear-gradient(0deg,
|
588
|
+
var(--checked, LinkText) 0%, var(--checked, LinkText) 100%);
|
589
|
+
}
|
590
|
+
|
591
|
+
/* #label for taller #multiple #textarea should align top - firefox 106 still
|
592
|
+
doesn't properly support :has() with combinator selectors */
|
593
|
+
@supports selector(:has(*)) {
|
594
|
+
label:where(:has(+ [multiple],+ textarea,+ select[size])){/*[0001] specificity*/
|
595
|
+
vertical-align: top;
|
596
|
+
align-self: start;
|
597
|
+
}}
|
598
|
+
|
599
|
+
|
600
|
+
/* -----------------------------------------------------------------------------
|
601
|
+
// #outputs - informational output/display
|
602
|
+
// -------------------------------------------------------------------------- */
|
603
|
+
|
604
|
+
progress:hover { border-color: revert; }
|
605
|
+
|
606
|
+
/* specify theme colors for chrome - [0011] specificity */
|
607
|
+
:-moz-meter-optimum::-moz-meter-bar { background-color: var(--success);}
|
608
|
+
:-moz-meter-sub-optimum::-moz-meter-bar { background-color: var(--warning);}
|
609
|
+
:-moz-meter-sub-sub-optimum::-moz-meter-bar { background-color: var(--error); }
|
610
|
+
|
611
|
+
|
612
|
+
/* -----------------------------------------------------------------------------
|
613
|
+
// #block text elements - elements that contain text blocks
|
614
|
+
// -------------------------------------------------------------------------- */
|
615
|
+
|
616
|
+
blockquote::before { /* [0002] specificity is the least possible here */
|
617
|
+
content: open-quote;
|
618
|
+
color: var(--pale);
|
619
|
+
font-size: 5rem;
|
620
|
+
font-family: var(--serif);
|
621
|
+
left: var(--xs);
|
622
|
+
line-height: 1;
|
623
|
+
position: absolute;
|
624
|
+
top: 0;
|
625
|
+
z-index: -1;
|
626
|
+
}
|
627
|
+
blockquote > :first-child { /* [0011] specificity */
|
628
|
+
margin-top: 0;
|
629
|
+
text-indent: var(--m);
|
630
|
+
}
|
631
|
+
blockquote > :last-child { /* [0011] specificity */
|
632
|
+
margin-bottom: 0;
|
633
|
+
}
|
634
|
+
|
635
|
+
|
636
|
+
/* -----------------------------------------------------------------------------
|
637
|
+
// #inline elements - elements that occur principally within text blocks
|
638
|
+
// -------------------------------------------------------------------------- */
|
639
|
+
|
640
|
+
/* #q - inline quotations -note that you must add the closing smart quote as
|
641
|
+
<q>'s automatic quotes are added via pseudo elements */
|
642
|
+
q[cite]::after { content: "” (cf. " attr(cite) ") "; }
|
643
|
+
|
644
|
+
/* #kbd #hover - remove default :hover styles - [0011] specificity */
|
645
|
+
kbd:hover { border: var(--solid) var(--distinguished); }
|
646
|
+
|
647
|
+
/* #:dir, #:lang - linguistic pseudo-classes */
|
648
|
+
/*:dir { }*/ /* experimental */
|
649
|
+
/*:lang { }*/
|
650
|
+
|
651
|
+
|
652
|
+
/* -----------------------------------------------------------------------------
|
653
|
+
// #media / #embedded - elements that add multimedia to a page
|
654
|
+
// -------------------------------------------------------------------------- */
|
655
|
+
|
656
|
+
/* #figure */
|
657
|
+
figure:hover { border-color: var(--border); }
|
658
|
+
/* #figure #headings - #h1 #h2 #h3 #h4 #h5 #h6 ~*/
|
659
|
+
figure > :where(h1,h2,h3,h4,h5,h6):first-child { margin-top: 0; }
|
660
|
+
|
661
|
+
/* #blockquote inside #figure - safari only for now - :where is older than :has
|
662
|
+
so it can be used here to reduce specificity - [0001] specificity */
|
663
|
+
figure:where(:has(blockquote)) {
|
664
|
+
background-color: revert;
|
665
|
+
}
|
666
|
+
/* fix doubled margin of a blockquote in a figure - [0002] specificity */
|
667
|
+
figure > blockquote {
|
668
|
+
text-align: start;
|
669
|
+
margin: var(--ms);
|
670
|
+
padding: var(--ms);
|
671
|
+
max-width: calc(var(--line-length) - (var(--ms) * 2));
|
672
|
+
}
|
673
|
+
|
674
|
+
/* #svg - [0001] specificity */
|
675
|
+
svg:not(:where(:root)) {
|
676
|
+
width: var(--m);
|
677
|
+
/*max-width: 100%;*/
|
678
|
+
overflow: hidden;
|
679
|
+
}
|
680
|
+
|
681
|
+
/* #audio - audio w/p controls shouldn't take up space - [0011] specificity */
|
682
|
+
audio:not([controls]) {
|
683
|
+
display: none;
|
684
|
+
height: 0;
|
685
|
+
}
|
686
|
+
|
687
|
+
/* resource state pseudo-classes */
|
688
|
+
/*:playing { }*/
|
689
|
+
/*:paused { }*/
|
690
|
+
/*:fullscreen { }*/
|
691
|
+
/*:picture-in-picture { }*/
|
692
|
+
/* time-dimensional pseudo-classes */
|
693
|
+
/*:current { }*/ /* experimental */
|
694
|
+
/*:past { }*/ /* experimental */
|
695
|
+
/*:future { }*/ /* experimental */
|
696
|
+
|
697
|
+
|
698
|
+
/* -----------------------------------------------------------------------------
|
699
|
+
// #interactive - built-in interactive components
|
700
|
+
// -------------------------------------------------------------------------- */
|
701
|
+
|
702
|
+
/* #dialog - built-in modal overlay - comes with 'Esc' support with no js! */
|
703
|
+
/* wrap long dialogs in a container to scroll the body content sans button */
|
704
|
+
dialog > :where(div, section, article) {
|
705
|
+
overflow-y: auto; /* scrolls as necessary w/ height set */
|
706
|
+
max-height: calc(80vh * 0.8); /* container queries needed! */
|
707
|
+
}
|
708
|
+
|
709
|
+
dialog button {
|
710
|
+
margin-top: var(--ms);
|
711
|
+
}
|
712
|
+
|
713
|
+
dialog :where(h1, h2, h3, h4, h5, h6) {
|
714
|
+
border-bottom: var(--solid) var(--border);
|
715
|
+
border-radius: var(--radius) var(--radius) 0 0;
|
716
|
+
color: var(--distinct); /* differentiate dialog headings */
|
717
|
+
font-size: var(--large); /* dialogs typically only need 1 heading
|
718
|
+
level, so make it all the same size */
|
719
|
+
margin: 0 0 0 calc(-1 * var(--m)); /* negative left margin
|
720
|
+
lines up heading with paragraph text */
|
721
|
+
padding: 0 var(--m) var(--m); /* top padding provided by dialog
|
722
|
+
padding so remove it here */
|
723
|
+
}
|
724
|
+
|
725
|
+
/* #details - an accordion-like widget for question & answer style layouts */
|
726
|
+
/* #summary - the 'question' sub-element of details */
|
727
|
+
details:where([open]) summary {
|
728
|
+
border-bottom: var(--solid) var(--border);
|
729
|
+
}
|
730
|
+
|
731
|
+
details + details {
|
732
|
+
border-top: 0;
|
733
|
+
border-radius: 0;
|
734
|
+
}
|
735
|
+
|
736
|
+
details:first-of-type {
|
737
|
+
border-top-left-radius: var(--radius);
|
738
|
+
border-top-right-radius: var(--radius);
|
739
|
+
}
|
740
|
+
|
741
|
+
details:last-of-type {
|
742
|
+
border-bottom-left-radius: var(--radius);
|
743
|
+
border-bottom-right-radius: var(--radius);
|
744
|
+
}
|
745
|
+
|
746
|
+
|
747
|
+
/* -----------------------------------------------------------------------------
|
748
|
+
// #breaks - spacing elements
|
749
|
+
// -------------------------------------------------------------------------- */
|
750
|
+
|
751
|
+
|
752
|
+
/* -----------------------------------------------------------------------------
|
753
|
+
// #@page - fixed page-oriented class, usually for print layouts
|
754
|
+
// -------------------------------------------------------------------------- */
|
755
|
+
/* @page pseudo-classes */
|
756
|
+
/* @page :first, @page :left, @page :right, @page :blank { } */
|