@daydreamui/good-css 0.0.0-stage → 0.3.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 +77 -2
- package/dist/index.js +2 -0
- package/icon.svg +8 -0
- package/knowledge/INDEX.md +67 -0
- package/knowledge/compatibility.md +33 -0
- package/knowledge/examples/container-queries-with-container-units.md +150 -0
- package/knowledge/examples/content-grid-with-breakouts.md +161 -0
- package/knowledge/examples/intrinsic-grid.md +150 -0
- package/knowledge/examples/sidebar-that-wraps-on-its-own.md +156 -0
- package/knowledge/examples/subgrid-rows-shared-across-cards.md +154 -0
- package/knowledge/procedures.md +37 -0
- package/knowledge/recipes/accordion-that-animates-its-height.md +59 -0
- package/knowledge/recipes/anchor-targets-that-clear-a-sticky-header.md +45 -0
- package/knowledge/recipes/carousel-on-native-scroll.md +87 -0
- package/knowledge/recipes/concentric-nested-radius.md +45 -0
- package/knowledge/recipes/container-queries-with-container-units.md +52 -0
- package/knowledge/recipes/content-clear-of-the-notch.md +45 -0
- package/knowledge/recipes/content-grid-with-breakouts.md +69 -0
- package/knowledge/recipes/cross-document-view-transitions.md +39 -0
- package/knowledge/recipes/enter-and-exit-transitions-from-display-none.md +87 -0
- package/knowledge/recipes/fluid-sizes-with-clamp.md +46 -0
- package/knowledge/recipes/form-feedback-with-user-invalid.md +45 -0
- package/knowledge/recipes/has-for-parent-and-page-state.md +46 -0
- package/knowledge/recipes/hit-area-larger-than-the-visual.md +47 -0
- package/knowledge/recipes/hover-styles-only-where-hover-exists.md +43 -0
- package/knowledge/recipes/icon-sized-by-the-text-beside-it.md +69 -0
- package/knowledge/recipes/image-box-that-holds-any-upload.md +67 -0
- package/knowledge/recipes/indicator-that-slides-to-the-active-item.md +80 -0
- package/knowledge/recipes/intrinsic-grid.md +41 -0
- package/knowledge/recipes/label-centered-on-its-letters-with-text-box.md +59 -0
- package/knowledge/recipes/logical-properties.md +48 -0
- package/knowledge/recipes/long-text-that-wraps-truncates-or-clamps.md +62 -0
- package/knowledge/recipes/motion-tokens.md +46 -0
- package/knowledge/recipes/no-rubber-band-bounce-on-desktop.md +47 -0
- package/knowledge/recipes/oklch-color.md +41 -0
- package/knowledge/recipes/one-fluid-scale-for-type-and-space.md +85 -0
- package/knowledge/recipes/one-focus-ring-with-focus-visible.md +44 -0
- package/knowledge/recipes/one-set-of-color-tokens-for-light-and-dark.md +53 -0
- package/knowledge/recipes/opt-in-motion.md +43 -0
- package/knowledge/recipes/overflow-clip-over-hidden.md +41 -0
- package/knowledge/recipes/popover-anchored-to-its-trigger.md +57 -0
- package/knowledge/recipes/press-feedback.md +43 -0
- package/knowledge/recipes/push-one-item-away-with-an-auto-margin.md +70 -0
- package/knowledge/recipes/reveal-with-clip-path.md +61 -0
- package/knowledge/recipes/safe-alignment.md +41 -0
- package/knowledge/recipes/scroll-area-between-a-fixed-header-and-footer.md +67 -0
- package/knowledge/recipes/section-spacing-that-depends-on-its-neighbors.md +54 -0
- package/knowledge/recipes/shadow-change-that-fades-and-does-not-repaint.md +80 -0
- package/knowledge/recipes/sidebar-that-wraps-on-its-own.md +56 -0
- package/knowledge/recipes/space-between-siblings-set-by-the-parent.md +59 -0
- package/knowledge/recipes/stack-layers-with-grid.md +43 -0
- package/knowledge/recipes/styles-that-apply-only-when-a-scroller-overflows.md +99 -0
- package/knowledge/recipes/subgrid-rows-shared-across-cards.md +51 -0
- package/knowledge/recipes/tabular-numbers.md +40 -0
- package/knowledge/recipes/textarea-that-grows-with-its-content.md +73 -0
- package/knowledge/recipes/the-reset.md +96 -0
- package/knowledge/recipes/transition-a-custom-property-with-property.md +70 -0
- package/knowledge/recipes/whole-card-clickable-from-one-link.md +71 -0
- package/knowledge/upstream-guidance.md +72 -0
- package/manifest.json +12 -0
- package/package.json +15 -4
- package/playgrounds/base.css +57 -0
- package/playgrounds/container-queries-with-container-units.css +10 -0
- package/playgrounds/container-queries-with-container-units.html +13 -0
- package/playgrounds/content-grid-with-breakouts.css +21 -0
- package/playgrounds/content-grid-with-breakouts.html +13 -0
- package/playgrounds/fixtures.json +32 -0
- package/playgrounds/intrinsic-grid.css +5 -0
- package/playgrounds/intrinsic-grid.html +19 -0
- package/playgrounds/sidebar-that-wraps-on-its-own.css +16 -0
- package/playgrounds/sidebar-that-wraps-on-its-own.html +13 -0
- package/playgrounds/subgrid-rows-shared-across-cards.css +12 -0
- package/playgrounds/subgrid-rows-shared-across-cards.html +13 -0
- package/upstream/LICENSE +21 -0
- package/upstream/lock.json +7 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-example-sidebar-that-wraps-on-its-own"
|
|
3
|
+
title: "Wrapping sidebar playground"
|
|
4
|
+
tier: "example"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "sidebar"
|
|
8
|
+
- "wraps"
|
|
9
|
+
- "own"
|
|
10
|
+
summary: "Resize from 960 to 320px. The sidebar and article wrap into separate rows. Adjust flex-basis and min-inline-size to move the wrap point."
|
|
11
|
+
intent: "Wrapping sidebar"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Wrapping sidebar
|
|
16
|
+
|
|
17
|
+
Resize from 960 to 320px. The sidebar and article wrap into separate rows. Adjust flex-basis and min-inline-size to move the wrap point.
|
|
18
|
+
|
|
19
|
+
Read [the recipe](../recipes/sidebar-that-wraps-on-its-own.md) for its prerequisites and credits.
|
|
20
|
+
The page uses the pinned recipe verbatim, with a separate teaching fixture.
|
|
21
|
+
|
|
22
|
+
## daydream.json
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"version": 9,
|
|
27
|
+
"pages": [
|
|
28
|
+
{
|
|
29
|
+
"path": "sidebar-that-wraps-on-its-own.html"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"canvases": [
|
|
33
|
+
{
|
|
34
|
+
"id": "main",
|
|
35
|
+
"name": "Playground",
|
|
36
|
+
"items": [
|
|
37
|
+
{
|
|
38
|
+
"id": "sidebar-that-wraps-on-its-own",
|
|
39
|
+
"kind": "daydream.viewport",
|
|
40
|
+
"position": {
|
|
41
|
+
"x": 0,
|
|
42
|
+
"y": 0
|
|
43
|
+
},
|
|
44
|
+
"frame": {
|
|
45
|
+
"width": 960,
|
|
46
|
+
"height": 720
|
|
47
|
+
},
|
|
48
|
+
"payload": {
|
|
49
|
+
"page": "sidebar-that-wraps-on-its-own.html"
|
|
50
|
+
},
|
|
51
|
+
"meta": {
|
|
52
|
+
"title": "Wrapping sidebar",
|
|
53
|
+
"sourceUrl": "https://good-css.com/specimen/sidebar-that-wraps-on-its-own"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## sidebar-that-wraps-on-its-own.html
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<!doctype html>
|
|
66
|
+
<html lang="en">
|
|
67
|
+
<head>
|
|
68
|
+
<meta charset="utf-8">
|
|
69
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
70
|
+
<title>Wrapping sidebar</title>
|
|
71
|
+
<link rel="stylesheet" href="sidebar-that-wraps-on-its-own.css">
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
<h1>Wrapping sidebar</h1><div class="with-sidebar"><aside class="sidebar">Sidebar with an ideal width</aside><main class="main"><h2>Article</h2><p>The main content keeps at least half the available row and sends the sidebar onto its own row when space runs out.</p></main></div>
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## sidebar-that-wraps-on-its-own.css
|
|
80
|
+
|
|
81
|
+
```css
|
|
82
|
+
/* Teaching fixture only; recipe declarations live in the other sheet. */
|
|
83
|
+
* {
|
|
84
|
+
box-sizing: border-box;
|
|
85
|
+
}
|
|
86
|
+
html {
|
|
87
|
+
background: #f5f4ef;
|
|
88
|
+
color: #26352d;
|
|
89
|
+
font:
|
|
90
|
+
16px/1.5 system-ui,
|
|
91
|
+
sans-serif;
|
|
92
|
+
}
|
|
93
|
+
body {
|
|
94
|
+
margin: 0;
|
|
95
|
+
padding: 1.5rem;
|
|
96
|
+
}
|
|
97
|
+
h1,
|
|
98
|
+
h2,
|
|
99
|
+
p {
|
|
100
|
+
margin: 0;
|
|
101
|
+
}
|
|
102
|
+
h1 {
|
|
103
|
+
font-size: 1.5rem;
|
|
104
|
+
margin-block-end: 1.5rem;
|
|
105
|
+
}
|
|
106
|
+
h2 {
|
|
107
|
+
font-size: 1.2rem;
|
|
108
|
+
}
|
|
109
|
+
article,
|
|
110
|
+
aside,
|
|
111
|
+
.main,
|
|
112
|
+
.panel {
|
|
113
|
+
padding: 1rem;
|
|
114
|
+
background: #e0e8df;
|
|
115
|
+
}
|
|
116
|
+
section > h2 {
|
|
117
|
+
padding: 1rem;
|
|
118
|
+
background: #d2decd;
|
|
119
|
+
}
|
|
120
|
+
footer {
|
|
121
|
+
padding: 0.5rem;
|
|
122
|
+
background: #c9d8c5;
|
|
123
|
+
}
|
|
124
|
+
.slot {
|
|
125
|
+
margin-block: 1rem;
|
|
126
|
+
}
|
|
127
|
+
.card {
|
|
128
|
+
overflow-wrap: anywhere;
|
|
129
|
+
}
|
|
130
|
+
.card > * {
|
|
131
|
+
margin: 0;
|
|
132
|
+
}
|
|
133
|
+
.cards h2 {
|
|
134
|
+
background: #c9d8c5;
|
|
135
|
+
}
|
|
136
|
+
.cards p {
|
|
137
|
+
background: #eaf0e7;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.with-sidebar {
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-wrap: wrap;
|
|
143
|
+
gap: 1rem;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.sidebar {
|
|
147
|
+
flex-basis: 20rem;
|
|
148
|
+
flex-grow: 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.main {
|
|
152
|
+
flex-basis: 0;
|
|
153
|
+
flex-grow: 999;
|
|
154
|
+
min-inline-size: 50%;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-example-subgrid-rows-shared-across-cards"
|
|
3
|
+
title: "Aligned card rows playground"
|
|
4
|
+
tier: "example"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "subgrid"
|
|
8
|
+
- "rows"
|
|
9
|
+
- "shared"
|
|
10
|
+
- "across"
|
|
11
|
+
- "cards"
|
|
12
|
+
summary: "At 960px, card headings, descriptions and footers share rows despite different text lengths. At 320px cards form a single column. Edit a heading to see its neighbors respond."
|
|
13
|
+
intent: "Aligned card rows"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Aligned card rows
|
|
18
|
+
|
|
19
|
+
At 960px, card headings, descriptions and footers share rows despite different text lengths. At 320px cards form a single column. Edit a heading to see its neighbors respond.
|
|
20
|
+
|
|
21
|
+
Read [the recipe](../recipes/subgrid-rows-shared-across-cards.md) for its prerequisites and credits.
|
|
22
|
+
The page uses the pinned recipe verbatim, with a separate teaching fixture.
|
|
23
|
+
|
|
24
|
+
## daydream.json
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"version": 9,
|
|
29
|
+
"pages": [
|
|
30
|
+
{
|
|
31
|
+
"path": "subgrid-rows-shared-across-cards.html"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"canvases": [
|
|
35
|
+
{
|
|
36
|
+
"id": "main",
|
|
37
|
+
"name": "Playground",
|
|
38
|
+
"items": [
|
|
39
|
+
{
|
|
40
|
+
"id": "subgrid-rows-shared-across-cards",
|
|
41
|
+
"kind": "daydream.viewport",
|
|
42
|
+
"position": {
|
|
43
|
+
"x": 0,
|
|
44
|
+
"y": 0
|
|
45
|
+
},
|
|
46
|
+
"frame": {
|
|
47
|
+
"width": 960,
|
|
48
|
+
"height": 720
|
|
49
|
+
},
|
|
50
|
+
"payload": {
|
|
51
|
+
"page": "subgrid-rows-shared-across-cards.html"
|
|
52
|
+
},
|
|
53
|
+
"meta": {
|
|
54
|
+
"title": "Aligned card rows",
|
|
55
|
+
"sourceUrl": "https://good-css.com/specimen/subgrid-rows-shared-across-cards"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## subgrid-rows-shared-across-cards.html
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<!doctype html>
|
|
68
|
+
<html lang="en">
|
|
69
|
+
<head>
|
|
70
|
+
<meta charset="utf-8">
|
|
71
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
72
|
+
<title>Aligned card rows</title>
|
|
73
|
+
<link rel="stylesheet" href="subgrid-rows-shared-across-cards.css">
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<h1>Aligned card rows</h1><main class="cards"><article class="card"><h2>Short heading</h2><p>A brief summary.</p><footer>Details</footer></article><article class="card"><h2>A heading long enough to wrap onto several lines</h2><p>More content, with a longer description to make the row heights visibly shared across neighboring cards.</p><footer>Details</footer></article><article class="card"><h2>Third card</h2><p>Another summary.</p><footer>Details</footer></article></main>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## subgrid-rows-shared-across-cards.css
|
|
82
|
+
|
|
83
|
+
```css
|
|
84
|
+
/* Teaching fixture only; recipe declarations live in the other sheet. */
|
|
85
|
+
* {
|
|
86
|
+
box-sizing: border-box;
|
|
87
|
+
}
|
|
88
|
+
html {
|
|
89
|
+
background: #f5f4ef;
|
|
90
|
+
color: #26352d;
|
|
91
|
+
font:
|
|
92
|
+
16px/1.5 system-ui,
|
|
93
|
+
sans-serif;
|
|
94
|
+
}
|
|
95
|
+
body {
|
|
96
|
+
margin: 0;
|
|
97
|
+
padding: 1.5rem;
|
|
98
|
+
}
|
|
99
|
+
h1,
|
|
100
|
+
h2,
|
|
101
|
+
p {
|
|
102
|
+
margin: 0;
|
|
103
|
+
}
|
|
104
|
+
h1 {
|
|
105
|
+
font-size: 1.5rem;
|
|
106
|
+
margin-block-end: 1.5rem;
|
|
107
|
+
}
|
|
108
|
+
h2 {
|
|
109
|
+
font-size: 1.2rem;
|
|
110
|
+
}
|
|
111
|
+
article,
|
|
112
|
+
aside,
|
|
113
|
+
.main,
|
|
114
|
+
.panel {
|
|
115
|
+
padding: 1rem;
|
|
116
|
+
background: #e0e8df;
|
|
117
|
+
}
|
|
118
|
+
section > h2 {
|
|
119
|
+
padding: 1rem;
|
|
120
|
+
background: #d2decd;
|
|
121
|
+
}
|
|
122
|
+
footer {
|
|
123
|
+
padding: 0.5rem;
|
|
124
|
+
background: #c9d8c5;
|
|
125
|
+
}
|
|
126
|
+
.slot {
|
|
127
|
+
margin-block: 1rem;
|
|
128
|
+
}
|
|
129
|
+
.card {
|
|
130
|
+
overflow-wrap: anywhere;
|
|
131
|
+
}
|
|
132
|
+
.card > * {
|
|
133
|
+
margin: 0;
|
|
134
|
+
}
|
|
135
|
+
.cards h2 {
|
|
136
|
+
background: #c9d8c5;
|
|
137
|
+
}
|
|
138
|
+
.cards p {
|
|
139
|
+
background: #eaf0e7;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.cards {
|
|
143
|
+
display: grid;
|
|
144
|
+
gap: 1.5rem;
|
|
145
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.card {
|
|
149
|
+
display: grid;
|
|
150
|
+
grid-row: span 3;
|
|
151
|
+
grid-template-rows: subgrid;
|
|
152
|
+
row-gap: 0.5rem;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: good-css-procedures
|
|
3
|
+
title: Choosing a Good CSS technique
|
|
4
|
+
tier: procedure
|
|
5
|
+
tags: [css, authoring, good-css]
|
|
6
|
+
summary: Select a technique, retain its prerequisites, and check its behavior in the intended environment.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Choosing a technique
|
|
10
|
+
|
|
11
|
+
Use `knowledge_search` with the behavior you need: cards that wrap, aligned
|
|
12
|
+
card footers, a sidebar, a focus ring, a disclosure, or fluid type. Read the
|
|
13
|
+
matching `daydream.good-css/recipes/` entries with `knowledge_read`, including
|
|
14
|
+
their Rules and Support sections. `knowledge_bundle` retrieves worked
|
|
15
|
+
examples; recipes are references and need the search/read step.
|
|
16
|
+
|
|
17
|
+
Prefer a technique that adapts to its content and available space. Keep the
|
|
18
|
+
project's existing visual language and naming. Upstream recommendations are
|
|
19
|
+
starting points: the user's design, target browsers, and the component's
|
|
20
|
+
actual constraints decide which declarations belong. For upstream scope and
|
|
21
|
+
intentionally excluded techniques, read `daydream.good-css/upstream-guidance.md`. Existing pages need
|
|
22
|
+
local edits; a recipe's reset is not permission to restyle the whole project.
|
|
23
|
+
|
|
24
|
+
For layout work, the examples pair a complete HTML/CSS page with a resize
|
|
25
|
+
experiment. To explore all playgrounds, copy the plugin's `playgrounds/`
|
|
26
|
+
folder into a separate project directory and open that copy in Daydream;
|
|
27
|
+
editing inside the installed plugin changes its trust hash. Use the recipe's prerequisites to decide which elements
|
|
28
|
+
establish layout and containment; preserve the conditions that make it work.
|
|
29
|
+
|
|
30
|
+
When using container units, popovers, dialogs, motion, or viewport-dependent
|
|
31
|
+
behavior, read `daydream.good-css/compatibility.md`. Verify the intended states,
|
|
32
|
+
content lengths, and browser targets as well as the static layout. A diagnostic
|
|
33
|
+
about this browser's support may describe an intentional fallback. Explain it
|
|
34
|
+
in context; retain declarations needed by other browsers or interaction states.
|
|
35
|
+
|
|
36
|
+
Upstream source and credits accompany each recipe. `upstream/lock.json` pins
|
|
37
|
+
what ships; all guidance is served from the local installation.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-accordion-that-animates-its-height"
|
|
3
|
+
title: "Accordion that animates its height"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "show"
|
|
8
|
+
- "hide"
|
|
9
|
+
- "accordion"
|
|
10
|
+
- "animates"
|
|
11
|
+
- "height"
|
|
12
|
+
summary: "Use it for FAQs and any other disclosure. `<details>` already gives you the toggle, keyboard support and find-in-page. These rules animate the open and the close with no JavaScript."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
16
|
+
# Accordion that animates its height
|
|
17
|
+
|
|
18
|
+
Use it for FAQs and any other disclosure. `<details>` already gives you the toggle, keyboard support and find-in-page. These rules animate the open and the close with no JavaScript.
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
:root { interpolate-size: allow-keywords; }
|
|
22
|
+
|
|
23
|
+
details::details-content {
|
|
24
|
+
height: 0;
|
|
25
|
+
overflow: clip;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
details[open]::details-content { height: auto; }
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
31
|
+
details::details-content {
|
|
32
|
+
transition:
|
|
33
|
+
height 0.3s var(--ease-out, ease-out),
|
|
34
|
+
content-visibility 0.3s allow-discrete;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Why it works:
|
|
40
|
+
|
|
41
|
+
- `::details-content` is the box the browser wraps around everything in `<details>` except the `<summary>`.
|
|
42
|
+
- `interpolate-size: allow-keywords` lets a transition run between a length and `auto`. It inherits, so one declaration on `:root` covers the page.
|
|
43
|
+
- A closed `<details>` hides its content with `content-visibility`. Transitioning that property with `allow-discrete` keeps the content rendered until the height reaches zero. Leave it out and the close snaps.
|
|
44
|
+
|
|
45
|
+
Rules:
|
|
46
|
+
|
|
47
|
+
- Put padding on an element inside, never on `::details-content`. Padding there stays visible when the accordion is closed.
|
|
48
|
+
- Give sibling `<details>` the same `name` attribute to keep one open at a time.
|
|
49
|
+
- Hide the marker with `summary { list-style: none; }` plus `summary::-webkit-details-marker { display: none; }` for Safari.
|
|
50
|
+
- Write no fallback. Safari and Firefox do not support `interpolate-size` yet, so they open and close without the animation.
|
|
51
|
+
|
|
52
|
+
Support: `::details-content` in Chrome 131, Safari 18.4, Firefox 143. `interpolate-size` in Chrome 129 only.
|
|
53
|
+
|
|
54
|
+
- Borrowed from: Bramus Van Damme, [Animate to height: auto](https://developer.chrome.com/docs/css-ui/animate-to-height-auto)
|
|
55
|
+
- Docs: [MDN: ::details-content](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::details-content) · [MDN: interpolate-size](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/interpolate-size) · [MDN: transition-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/transition-behavior)
|
|
56
|
+
|
|
57
|
+
Source: [Good CSS](https://good-css.com/specimen/accordion-that-animates-its-height), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#accordion-that-animates-its-height).
|
|
58
|
+
|
|
59
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-anchor-targets-that-clear-a-sticky-header"
|
|
3
|
+
title: "Anchor targets that clear a sticky header"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "scroll"
|
|
8
|
+
- "viewport"
|
|
9
|
+
- "anchor"
|
|
10
|
+
- "targets"
|
|
11
|
+
- "clear"
|
|
12
|
+
- "sticky"
|
|
13
|
+
- "header"
|
|
14
|
+
summary: "Use it on any page with in-page links and a fixed or sticky header."
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
18
|
+
# Anchor targets that clear a sticky header
|
|
19
|
+
|
|
20
|
+
Use it on any page with in-page links and a fixed or sticky header.
|
|
21
|
+
|
|
22
|
+
```css
|
|
23
|
+
html { scroll-padding-block-start: 5rem; }
|
|
24
|
+
|
|
25
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
26
|
+
html { scroll-behavior: smooth; }
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Why it works:
|
|
31
|
+
|
|
32
|
+
- The browser offsets every scroll to an anchor by the padding, so the target lands below the header. No script calculates offsets.
|
|
33
|
+
|
|
34
|
+
Rules:
|
|
35
|
+
|
|
36
|
+
- Keep the offset outside the motion query, so people who ask for reduced motion still get it.
|
|
37
|
+
- Tie the value to the header's height. Share one custom property between the two.
|
|
38
|
+
|
|
39
|
+
Support: Chrome 69, Firefox 68, Safari 14.1.
|
|
40
|
+
|
|
41
|
+
- Borrowed from: Kevin Powell, [Stop fixed navigations covering content on scroll](https://www.youtube.com/watch?v=iGUSTyG-CYw), 2022-06-02, and [Use these CSS features instead of JavaScript](https://www.youtube.com/watch?v=qu1jE41O_8o), 2026-09-23
|
|
42
|
+
|
|
43
|
+
Source: [Good CSS](https://good-css.com/specimen/anchor-targets-that-clear-a-sticky-header), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#anchor-targets-that-clear-a-sticky-header).
|
|
44
|
+
|
|
45
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-carousel-on-native-scroll"
|
|
3
|
+
title: "Carousel on native scroll"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "scroll"
|
|
8
|
+
- "viewport"
|
|
9
|
+
- "carousel"
|
|
10
|
+
- "native"
|
|
11
|
+
summary: "Use it for any horizontal row of cards. The carousel is a real scroll container, so swipe, trackpad, keyboard and momentum all work before any script loads."
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
15
|
+
# Carousel on native scroll
|
|
16
|
+
|
|
17
|
+
Use it for any horizontal row of cards. The carousel is a real scroll container, so swipe, trackpad, keyboard and momentum all work before any script loads.
|
|
18
|
+
|
|
19
|
+
```css
|
|
20
|
+
.carousel {
|
|
21
|
+
display: flex;
|
|
22
|
+
gap: 1rem;
|
|
23
|
+
overflow-x: auto;
|
|
24
|
+
scroll-snap-type: x mandatory;
|
|
25
|
+
scroll-padding-inline: 1.5rem;
|
|
26
|
+
padding-inline: 1.5rem;
|
|
27
|
+
overscroll-behavior-x: contain;
|
|
28
|
+
scrollbar-width: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.carousel > * {
|
|
32
|
+
flex: none;
|
|
33
|
+
scroll-snap-align: start;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
37
|
+
.carousel { scroll-behavior: smooth; }
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Arrow buttons are the one part that needs a script.
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<button data-carousel-dir="-1" aria-label="Previous">←</button>
|
|
45
|
+
<button data-carousel-dir="1" aria-label="Next">→</button>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const carousel = document.querySelector(".carousel");
|
|
50
|
+
const [prev, next] = document.querySelectorAll("[data-carousel-dir]");
|
|
51
|
+
|
|
52
|
+
const step = (dir) => {
|
|
53
|
+
const card = carousel.firstElementChild.getBoundingClientRect().width;
|
|
54
|
+
carousel.scrollBy({ left: dir * card });
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const sync = () => {
|
|
58
|
+
const max = carousel.scrollWidth - carousel.clientWidth;
|
|
59
|
+
prev.disabled = carousel.scrollLeft <= 1;
|
|
60
|
+
next.disabled = carousel.scrollLeft >= max - 1;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
prev.addEventListener("click", () => step(-1));
|
|
64
|
+
next.addEventListener("click", () => step(1));
|
|
65
|
+
carousel.addEventListener("scroll", sync, { passive: true });
|
|
66
|
+
sync();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Why it works:
|
|
70
|
+
|
|
71
|
+
- Scroll snap makes the browser do the scrolling and the landing. Every input method gets the same result.
|
|
72
|
+
- `scrollBy` moves one card width and snapping picks the final position. The script never calculates an offset.
|
|
73
|
+
- `scroll-behavior: smooth` in CSS animates the script's scroll. The media query turns the animation off for people who ask for reduced motion.
|
|
74
|
+
- `overscroll-behavior-x: contain` stops a swipe at the end of the row from triggering the browser's back gesture.
|
|
75
|
+
|
|
76
|
+
Rules:
|
|
77
|
+
|
|
78
|
+
- Never move slides with `transform`, and never handle wheel or touch events yourself.
|
|
79
|
+
- Match `scroll-padding-inline` to `padding-inline` so a snapped card lines up with the page content.
|
|
80
|
+
- Chrome 135 can draw the arrows and dots in CSS with `::scroll-button()` and `::scroll-marker`. Firefox and Safari have neither, so use the script.
|
|
81
|
+
|
|
82
|
+
- Borrowed from: Steve Sewell, [Build buttery smooth carousels with pure CSS like Nike](https://www.builder.io/blog/css-carousel). This version swaps his `scrollIntoView()` for `scrollBy()`, which can only scroll the carousel and never the page.
|
|
83
|
+
- Docs: Adam Argyle, [Carousels with CSS](https://developer.chrome.com/blog/carousels-with-css) · [MDN: scroll snap](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll_snap) · [MDN: scrollBy()](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollBy)
|
|
84
|
+
|
|
85
|
+
Source: [Good CSS](https://good-css.com/specimen/carousel-on-native-scroll), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#carousel-on-native-scroll).
|
|
86
|
+
|
|
87
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-concentric-nested-radius"
|
|
3
|
+
title: "Concentric nested radius"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "spacing"
|
|
8
|
+
- "shape"
|
|
9
|
+
- "concentric"
|
|
10
|
+
- "nested"
|
|
11
|
+
- "radius"
|
|
12
|
+
summary: "Use it wherever a rounded element sits inside a padded rounded parent, such as an image in a card or a button in an input."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
16
|
+
# Concentric nested radius
|
|
17
|
+
|
|
18
|
+
Use it wherever a rounded element sits inside a padded rounded parent, such as an image in a card or a button in an input.
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
.card {
|
|
22
|
+
--radius: 0.75rem;
|
|
23
|
+
--pad: 0.5rem;
|
|
24
|
+
padding: var(--pad);
|
|
25
|
+
border-radius: calc(var(--radius) + var(--pad));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.card > * { border-radius: var(--radius); }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Why it works:
|
|
32
|
+
|
|
33
|
+
- Two curves look parallel only when they share a center. That means outer radius = inner radius + gap. Equal radii make the corner band look pinched.
|
|
34
|
+
|
|
35
|
+
Rules:
|
|
36
|
+
|
|
37
|
+
- Derive the outer radius from the inner one. The other way round reaches zero once the padding exceeds the radius.
|
|
38
|
+
|
|
39
|
+
Support: every browser.
|
|
40
|
+
|
|
41
|
+
- Borrowed from: Chris Coyier, [The Classic Border Radius Advice, Plus an Unusual Trick](https://blog.master.dev/the-classic-border-radius-advice-plus-an-unusual-trick/), 2024-05-13, which quotes Jhey Tompkins's version
|
|
42
|
+
|
|
43
|
+
Source: [Good CSS](https://good-css.com/specimen/concentric-nested-radius), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#concentric-nested-radius).
|
|
44
|
+
|
|
45
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-container-queries-with-container-units"
|
|
3
|
+
title: "Container queries with container units"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "layout"
|
|
8
|
+
- "container"
|
|
9
|
+
- "queries"
|
|
10
|
+
- "units"
|
|
11
|
+
summary: "Use it for any component that appears in slots of different widths. The component responds to the space it is given, where a media query only knows the viewport."
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
15
|
+
# Container queries with container units
|
|
16
|
+
|
|
17
|
+
Use it for any component that appears in slots of different widths. The component responds to the space it is given, where a media query only knows the viewport.
|
|
18
|
+
|
|
19
|
+
```css
|
|
20
|
+
.slot { container-type: inline-size; }
|
|
21
|
+
|
|
22
|
+
.card h2 { font-size: clamp(1.25rem, 1rem + 2cqi, 2rem); }
|
|
23
|
+
|
|
24
|
+
@container (width > 30rem) {
|
|
25
|
+
.card {
|
|
26
|
+
display: grid;
|
|
27
|
+
grid-template-columns: 12rem 1fr;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Why it works:
|
|
33
|
+
|
|
34
|
+
- The query measures the nearest container, so the same card is right in a sidebar and in a wide column.
|
|
35
|
+
- `1cqi` is 1% of that container's inline size. The `clamp()` math from [Fluid sizes with `clamp()`](https://good-css.com/#fluid-sizes-with-clamp) carries over, per component.
|
|
36
|
+
|
|
37
|
+
Rules:
|
|
38
|
+
|
|
39
|
+
- The container must be an ancestor. An element cannot query itself.
|
|
40
|
+
- A container cannot take its width from its content. Never put `container-type` on a shrink-to-fit element, or it collapses to zero.
|
|
41
|
+
- With no container ancestor the query never matches. Kevin Powell makes `header`, `main` and `footer` containers in his reset, so most components need none of their own.
|
|
42
|
+
- A container cannot also be a subgrid. [Subgrid rows shared across cards](https://good-css.com/#subgrid-rows-shared-across-cards) and this entry need separate elements.
|
|
43
|
+
- Container units are different from queries. With no container above the element, `cqi` measures the viewport, so one token written with `cqi` serves the page and every slot.
|
|
44
|
+
- Never register a fluid token that uses `cqi` with `@property`. A registered length computes once on `:root`, where there is no container, and every slot then gets the viewport's value.
|
|
45
|
+
|
|
46
|
+
Support: Chrome 105, Firefox 110, Safari 16.
|
|
47
|
+
|
|
48
|
+
- Borrowed from: Kevin Powell, [Improve your reset with these modern CSS additions](https://www.youtube.com/watch?v=eWmDW4zEXt4), 2024-09-04 · Una Kravets, [Container queries land in stable browsers](https://web.dev/blog/cq-stable), 2023-02-14 · Josh W. Comeau, [A Friendly Introduction to Container Queries](https://www.joshwcomeau.com/css/container-queries-introduction/), 2024-11-04 · Stephanie Eckles, [Container Query Units and Fluid Typography](https://moderncss.dev/container-query-units-and-fluid-typography/)
|
|
49
|
+
|
|
50
|
+
Source: [Good CSS](https://good-css.com/specimen/container-queries-with-container-units), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#container-queries-with-container-units).
|
|
51
|
+
|
|
52
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-content-clear-of-the-notch"
|
|
3
|
+
title: "Content clear of the notch"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "scroll"
|
|
8
|
+
- "viewport"
|
|
9
|
+
- "content"
|
|
10
|
+
- "clear"
|
|
11
|
+
- "notch"
|
|
12
|
+
summary: "Use it on fixed headers, bottom bars, toasts and sheets in anything that should feel like an app on a phone. Add `viewport-fit=cover` to the viewport meta tag, then pad those elements by the safe-area insets."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
16
|
+
# Content clear of the notch
|
|
17
|
+
|
|
18
|
+
Use it on fixed headers, bottom bars, toasts and sheets in anything that should feel like an app on a phone. Add `viewport-fit=cover` to the viewport meta tag, then pad those elements by the safe-area insets.
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
.app-header { padding-block-start: env(safe-area-inset-top, 0px); }
|
|
22
|
+
|
|
23
|
+
.bottom-bar { padding-block-end: env(safe-area-inset-bottom, 0px); }
|
|
24
|
+
|
|
25
|
+
.sheet { padding-block-end: calc(1rem + env(safe-area-inset-bottom, 0px)); }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Why it works:
|
|
29
|
+
|
|
30
|
+
- By default the browser keeps the page inside the safe area and leaves the notch and home-indicator zones as bare background. `viewport-fit=cover` lets the page paint edge to edge.
|
|
31
|
+
- `env(safe-area-inset-*)` gives the size of each zone, so the content moves clear while the background still fills the screen.
|
|
32
|
+
|
|
33
|
+
Rules:
|
|
34
|
+
|
|
35
|
+
- Without `viewport-fit=cover` every inset is `0px` and the padding does nothing.
|
|
36
|
+
- Give `env()` a fallback of `0px` wherever it sits inside `calc()`.
|
|
37
|
+
- Normal page content needs none of this. The header's padding covers it.
|
|
38
|
+
|
|
39
|
+
Support: Chrome 69, Firefox 65, Safari 11.1.
|
|
40
|
+
|
|
41
|
+
- Borrowed from: Emil Kowalski, [mobile-native skill](https://github.com/emilkowalski/skills/blob/main/skills/mobile-native/SKILL.md)
|
|
42
|
+
|
|
43
|
+
Source: [Good CSS](https://good-css.com/specimen/content-clear-of-the-notch), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#content-clear-of-the-notch).
|
|
44
|
+
|
|
45
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|