@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.
Files changed (75) hide show
  1. package/README.md +77 -2
  2. package/dist/index.js +2 -0
  3. package/icon.svg +8 -0
  4. package/knowledge/INDEX.md +67 -0
  5. package/knowledge/compatibility.md +33 -0
  6. package/knowledge/examples/container-queries-with-container-units.md +150 -0
  7. package/knowledge/examples/content-grid-with-breakouts.md +161 -0
  8. package/knowledge/examples/intrinsic-grid.md +150 -0
  9. package/knowledge/examples/sidebar-that-wraps-on-its-own.md +156 -0
  10. package/knowledge/examples/subgrid-rows-shared-across-cards.md +154 -0
  11. package/knowledge/procedures.md +37 -0
  12. package/knowledge/recipes/accordion-that-animates-its-height.md +59 -0
  13. package/knowledge/recipes/anchor-targets-that-clear-a-sticky-header.md +45 -0
  14. package/knowledge/recipes/carousel-on-native-scroll.md +87 -0
  15. package/knowledge/recipes/concentric-nested-radius.md +45 -0
  16. package/knowledge/recipes/container-queries-with-container-units.md +52 -0
  17. package/knowledge/recipes/content-clear-of-the-notch.md +45 -0
  18. package/knowledge/recipes/content-grid-with-breakouts.md +69 -0
  19. package/knowledge/recipes/cross-document-view-transitions.md +39 -0
  20. package/knowledge/recipes/enter-and-exit-transitions-from-display-none.md +87 -0
  21. package/knowledge/recipes/fluid-sizes-with-clamp.md +46 -0
  22. package/knowledge/recipes/form-feedback-with-user-invalid.md +45 -0
  23. package/knowledge/recipes/has-for-parent-and-page-state.md +46 -0
  24. package/knowledge/recipes/hit-area-larger-than-the-visual.md +47 -0
  25. package/knowledge/recipes/hover-styles-only-where-hover-exists.md +43 -0
  26. package/knowledge/recipes/icon-sized-by-the-text-beside-it.md +69 -0
  27. package/knowledge/recipes/image-box-that-holds-any-upload.md +67 -0
  28. package/knowledge/recipes/indicator-that-slides-to-the-active-item.md +80 -0
  29. package/knowledge/recipes/intrinsic-grid.md +41 -0
  30. package/knowledge/recipes/label-centered-on-its-letters-with-text-box.md +59 -0
  31. package/knowledge/recipes/logical-properties.md +48 -0
  32. package/knowledge/recipes/long-text-that-wraps-truncates-or-clamps.md +62 -0
  33. package/knowledge/recipes/motion-tokens.md +46 -0
  34. package/knowledge/recipes/no-rubber-band-bounce-on-desktop.md +47 -0
  35. package/knowledge/recipes/oklch-color.md +41 -0
  36. package/knowledge/recipes/one-fluid-scale-for-type-and-space.md +85 -0
  37. package/knowledge/recipes/one-focus-ring-with-focus-visible.md +44 -0
  38. package/knowledge/recipes/one-set-of-color-tokens-for-light-and-dark.md +53 -0
  39. package/knowledge/recipes/opt-in-motion.md +43 -0
  40. package/knowledge/recipes/overflow-clip-over-hidden.md +41 -0
  41. package/knowledge/recipes/popover-anchored-to-its-trigger.md +57 -0
  42. package/knowledge/recipes/press-feedback.md +43 -0
  43. package/knowledge/recipes/push-one-item-away-with-an-auto-margin.md +70 -0
  44. package/knowledge/recipes/reveal-with-clip-path.md +61 -0
  45. package/knowledge/recipes/safe-alignment.md +41 -0
  46. package/knowledge/recipes/scroll-area-between-a-fixed-header-and-footer.md +67 -0
  47. package/knowledge/recipes/section-spacing-that-depends-on-its-neighbors.md +54 -0
  48. package/knowledge/recipes/shadow-change-that-fades-and-does-not-repaint.md +80 -0
  49. package/knowledge/recipes/sidebar-that-wraps-on-its-own.md +56 -0
  50. package/knowledge/recipes/space-between-siblings-set-by-the-parent.md +59 -0
  51. package/knowledge/recipes/stack-layers-with-grid.md +43 -0
  52. package/knowledge/recipes/styles-that-apply-only-when-a-scroller-overflows.md +99 -0
  53. package/knowledge/recipes/subgrid-rows-shared-across-cards.md +51 -0
  54. package/knowledge/recipes/tabular-numbers.md +40 -0
  55. package/knowledge/recipes/textarea-that-grows-with-its-content.md +73 -0
  56. package/knowledge/recipes/the-reset.md +96 -0
  57. package/knowledge/recipes/transition-a-custom-property-with-property.md +70 -0
  58. package/knowledge/recipes/whole-card-clickable-from-one-link.md +71 -0
  59. package/knowledge/upstream-guidance.md +72 -0
  60. package/manifest.json +12 -0
  61. package/package.json +15 -4
  62. package/playgrounds/base.css +57 -0
  63. package/playgrounds/container-queries-with-container-units.css +10 -0
  64. package/playgrounds/container-queries-with-container-units.html +13 -0
  65. package/playgrounds/content-grid-with-breakouts.css +21 -0
  66. package/playgrounds/content-grid-with-breakouts.html +13 -0
  67. package/playgrounds/fixtures.json +32 -0
  68. package/playgrounds/intrinsic-grid.css +5 -0
  69. package/playgrounds/intrinsic-grid.html +19 -0
  70. package/playgrounds/sidebar-that-wraps-on-its-own.css +16 -0
  71. package/playgrounds/sidebar-that-wraps-on-its-own.html +13 -0
  72. package/playgrounds/subgrid-rows-shared-across-cards.css +12 -0
  73. package/playgrounds/subgrid-rows-shared-across-cards.html +13 -0
  74. package/upstream/LICENSE +21 -0
  75. 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.