@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,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-intrinsic-grid"
|
|
3
|
+
title: "Intrinsic grid"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "layout"
|
|
8
|
+
- "intrinsic"
|
|
9
|
+
- "grid"
|
|
10
|
+
summary: "Use it for any set of equal cards or tiles. It replaces a column count per breakpoint."
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
14
|
+
# Intrinsic grid
|
|
15
|
+
|
|
16
|
+
Use it for any set of equal cards or tiles. It replaces a column count per breakpoint.
|
|
17
|
+
|
|
18
|
+
```css
|
|
19
|
+
.grid {
|
|
20
|
+
display: grid;
|
|
21
|
+
gap: 1rem;
|
|
22
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Why it works:
|
|
27
|
+
|
|
28
|
+
- `auto-fit` creates as many 16rem columns as the row holds, and `1fr` shares what is left. The column count follows the container.
|
|
29
|
+
- `min(100%, 16rem)` lets the minimum shrink to the container. Without it a 200px container overflows.
|
|
30
|
+
|
|
31
|
+
Rules:
|
|
32
|
+
|
|
33
|
+
- `auto-fit` stretches a short row to fill the width. Use `auto-fill` when the item count changes, such as a filtered list, so cards keep their size.
|
|
34
|
+
|
|
35
|
+
Support: Chrome 79, Firefox 76, Safari 11.1.
|
|
36
|
+
|
|
37
|
+
- Borrowed from: Kevin Powell, [Breakpoint-Free CSS Grid Layouts](https://www.youtube.com/watch?v=bj0Z_GncIwY), 2025-02-19 · Stephanie Eckles, [Smol Responsive CSS Grid](https://smolcss.dev/#smol-css-grid) · Una Kravets, [Ten modern layouts in one line of CSS](https://web.dev/articles/one-line-layouts)
|
|
38
|
+
|
|
39
|
+
Source: [Good CSS](https://good-css.com/specimen/intrinsic-grid), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#intrinsic-grid).
|
|
40
|
+
|
|
41
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-label-centered-on-its-letters-with-text-box"
|
|
3
|
+
title: "Label centered on its letters with text-box"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "text"
|
|
8
|
+
- "media"
|
|
9
|
+
- "label"
|
|
10
|
+
- "centered"
|
|
11
|
+
- "letters"
|
|
12
|
+
- "box"
|
|
13
|
+
summary: "Use it on single-line labels in buttons, badges and chips, so equal padding looks equal in any font."
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
17
|
+
# Label centered on its letters with `text-box`
|
|
18
|
+
|
|
19
|
+
Use it on single-line labels in buttons, badges and chips, so equal padding looks equal in any font.
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
.button {
|
|
23
|
+
display: inline-block;
|
|
24
|
+
padding: 0.75rem 1.25rem;
|
|
25
|
+
text-box: trim-both cap alphabetic;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.button-with-icon {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 0.5rem;
|
|
32
|
+
padding: 0.75rem 1.25rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.button-with-icon > span { text-box: trim-both cap alphabetic; }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Why it works:
|
|
39
|
+
|
|
40
|
+
- A line of text reserves space above the capitals and below the baseline, and the two amounts differ from font to font. Equal padding around that line puts the letters off-center.
|
|
41
|
+
- `trim-both cap alphabetic` cuts the box down to the cap height and the baseline. The padding then starts at the letters.
|
|
42
|
+
- A font swap or a fallback font no longer moves the label.
|
|
43
|
+
|
|
44
|
+
Rules:
|
|
45
|
+
|
|
46
|
+
- It does nothing on a flex or grid container. A button that is `inline-flex` because it holds an icon needs the declaration on the element that wraps the text, as in the second rule of the block.
|
|
47
|
+
- The button gets shorter, because the padding now starts at the letters. With `0.75rem` of padding it went from 48px tall to 35px in Chrome and Safari. Raise the padding to keep the height.
|
|
48
|
+
- Use it for one line. Descenders hang into the bottom padding, which is the intent.
|
|
49
|
+
- Scope it to labels. Never set it on `*`, because it shrinks every text block. A one-line paragraph at 16px/1.5 went from 25px tall to 11px.
|
|
50
|
+
- Write no fallback. Without support the label keeps its normal line box.
|
|
51
|
+
|
|
52
|
+
Support: Chrome 133, Firefox 154, Safari 18.2.
|
|
53
|
+
|
|
54
|
+
- Borrowed from: Stripe, which ships it on its buttons at stripe.com. There the button is a flex container, so the declaration has no effect and uneven padding does the centering. The first rule above is the fix.
|
|
55
|
+
- Docs: [MDN: text-box](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/text-box)
|
|
56
|
+
|
|
57
|
+
Source: [Good CSS](https://good-css.com/specimen/label-centered-on-its-letters-with-text-box), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#label-centered-on-its-letters-with-text-box).
|
|
58
|
+
|
|
59
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-logical-properties"
|
|
3
|
+
title: "Logical properties"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "foundations"
|
|
8
|
+
- "logical"
|
|
9
|
+
- "properties"
|
|
10
|
+
summary: "Use them in place of every left, right, top and bottom, in spacing, borders, offsets and alignment."
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
14
|
+
# Logical properties
|
|
15
|
+
|
|
16
|
+
Use them in place of every left, right, top and bottom, in spacing, borders, offsets and alignment.
|
|
17
|
+
|
|
18
|
+
```css
|
|
19
|
+
.card {
|
|
20
|
+
position: relative;
|
|
21
|
+
padding-inline: 1.5rem;
|
|
22
|
+
margin-block-end: 2rem;
|
|
23
|
+
border-inline-start: 4px solid;
|
|
24
|
+
text-align: start;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.card .close {
|
|
28
|
+
position: absolute;
|
|
29
|
+
inset-block-start: 1rem;
|
|
30
|
+
inset-inline-end: 1rem;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Why it works:
|
|
35
|
+
|
|
36
|
+
- `inline` is the direction text runs and `block` the direction blocks stack. Start and end follow `dir` and `writing-mode`, so one set of declarations renders mirrored in a right-to-left page.
|
|
37
|
+
|
|
38
|
+
Rules:
|
|
39
|
+
|
|
40
|
+
- The four-value `margin`, `padding` and `inset` shorthands stay physical. Use the `-inline` and `-block` shorthands.
|
|
41
|
+
|
|
42
|
+
Support: Chrome 87, Firefox 66, Safari 14.1.
|
|
43
|
+
|
|
44
|
+
- Borrowed from: Ahmad Shadeed, [Digging Into CSS Logical Properties](https://ishadeed.com/article/css-logical-properties/), 2021-03-10
|
|
45
|
+
|
|
46
|
+
Source: [Good CSS](https://good-css.com/specimen/logical-properties), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#logical-properties).
|
|
47
|
+
|
|
48
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-long-text-that-wraps-truncates-or-clamps"
|
|
3
|
+
title: "Long text that wraps, truncates or clamps"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "text"
|
|
8
|
+
- "media"
|
|
9
|
+
- "long"
|
|
10
|
+
- "wraps"
|
|
11
|
+
- "truncates"
|
|
12
|
+
- "clamps"
|
|
13
|
+
summary: "Use it on any text that comes from a user or a CMS, such as names, titles, URLs and excerpts. Decide for each one what happens when the content is longer than the design. It wraps, it is cut to one line, or it is cut to a few lines."
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
17
|
+
# Long text that wraps, truncates or clamps
|
|
18
|
+
|
|
19
|
+
Use it on any text that comes from a user or a CMS, such as names, titles, URLs and excerpts. Decide for each one what happens when the content is longer than the design. It wraps, it is cut to one line, or it is cut to a few lines.
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
:root { overflow-wrap: break-word; }
|
|
23
|
+
|
|
24
|
+
.name {
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
overflow: clip;
|
|
27
|
+
text-overflow: ellipsis;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.excerpt {
|
|
31
|
+
display: -webkit-box;
|
|
32
|
+
-webkit-box-orient: vertical;
|
|
33
|
+
-webkit-line-clamp: 3;
|
|
34
|
+
overflow: clip;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Why it works:
|
|
39
|
+
|
|
40
|
+
- `overflow-wrap: break-word` breaks a word only when it cannot fit on a line by itself. Ordinary text wraps as before. It inherits, so the one declaration in the reset covers the page.
|
|
41
|
+
- The reset's `min-width: 0` lets a flex or grid item get narrower than its longest word. It does nothing for the word, which then paints over the next column. The two rules need each other.
|
|
42
|
+
- `nowrap` keeps the text on one line, `overflow` cuts it and `text-overflow` draws the ellipsis at the cut.
|
|
43
|
+
- `-webkit-line-clamp` counts lines, so the clamp holds at any font size and any width.
|
|
44
|
+
|
|
45
|
+
Rules:
|
|
46
|
+
|
|
47
|
+
- Put the truncation on the element that holds the text. On a flex or grid container the text is cut and no ellipsis appears.
|
|
48
|
+
- Every flex item between the row and the truncated text must be able to shrink. The reset's `min-width: 0` covers that. Without the reset, set `min-inline-size: 0` on each of them.
|
|
49
|
+
- The clamp needs all four declarations. Unprefixed `line-clamp` is in no browser yet.
|
|
50
|
+
- Put padding on a wrapper, never on the clamped element. The next line shows through the bottom padding.
|
|
51
|
+
- Use `break-word` on the root and not `anywhere`. `anywhere` also shrinks the minimum content width, so a box sized by its content collapses to one letter per line.
|
|
52
|
+
- A table with automatic layout ignores `break-word`. Set `overflow-wrap: anywhere` on the cell.
|
|
53
|
+
- Truncate only text the reader can get in full somewhere else. Never truncate text they have to read.
|
|
54
|
+
|
|
55
|
+
Support: `text-overflow`, `-webkit-line-clamp` and `overflow-wrap: break-word` work everywhere. With `overflow: clip` the ellipsis and the clamp draw in Chrome 150 and Safari 27, tested on 2026-10-01. Firefox is untested, and `overflow: hidden` is the form to fall back to. `overflow-wrap: anywhere` in Chrome 80, Firefox 65, Safari 15.4.
|
|
56
|
+
|
|
57
|
+
- Borrowed from: Ahmad Shadeed, [Defensive CSS](https://defensivecss.dev/), the tips [Long content](https://defensivecss.dev/tip/long-content/) and [Minimum content size in flexbox](https://defensivecss.dev/tip/flexbox-min-content-size/) · Ahmad Shadeed, [Handling Short And Long Content In CSS](https://ishadeed.com/article/css-short-long-content/), 2020-12-16
|
|
58
|
+
- Docs: [MDN: overflow-wrap](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/overflow-wrap) · [MDN: text-overflow](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/text-overflow) · [MDN: line-clamp](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/line-clamp)
|
|
59
|
+
|
|
60
|
+
Source: [Good CSS](https://good-css.com/specimen/long-text-that-wraps-truncates-or-clamps), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#long-text-that-wraps-truncates-or-clamps).
|
|
61
|
+
|
|
62
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-motion-tokens"
|
|
3
|
+
title: "Motion tokens"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "motion"
|
|
8
|
+
- "tokens"
|
|
9
|
+
summary: "Use them in every transition and animation. Two easing curves, defined once, and a rule for durations."
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
13
|
+
# Motion tokens
|
|
14
|
+
|
|
15
|
+
Use them in every transition and animation. Two easing curves, defined once, and a rule for durations.
|
|
16
|
+
|
|
17
|
+
```css
|
|
18
|
+
:root {
|
|
19
|
+
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
|
|
20
|
+
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.dropdown { transition: opacity 180ms var(--ease-out); }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Why it works:
|
|
27
|
+
|
|
28
|
+
- An ease-out curve starts fast, so the interface moves at the moment the user is watching. Use `--ease-out` for anything entering or leaving.
|
|
29
|
+
- An ease-in-out curve suits something that is already on screen and moves to a new place. Use `--ease-in-out` there.
|
|
30
|
+
- The built-in `ease-out` and `ease-in-out` keywords are weak versions of the same shapes. These two are stronger.
|
|
31
|
+
|
|
32
|
+
Rules:
|
|
33
|
+
|
|
34
|
+
- Never use `ease-in` on UI. It starts slow and reads as lag.
|
|
35
|
+
- Keep a UI transition at 300ms or less. Press feedback takes 100 to 160ms, tooltips and small popovers 125 to 200ms, dropdowns 150 to 250ms. The one exception is a modal or drawer, which takes 200 to 500ms.
|
|
36
|
+
- Name the properties in a transition. Never write `transition: all`.
|
|
37
|
+
- Other entries write `var(--ease-out, ease-out)`, so their snippets work before these tokens exist.
|
|
38
|
+
- Whether something should animate at all is not a CSS question. Emil Kowalski's `animate` and `review-animations` skills answer it.
|
|
39
|
+
|
|
40
|
+
Support: every browser.
|
|
41
|
+
|
|
42
|
+
- Borrowed from: Emil Kowalski, [emil-design-eng skill](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) and [7 Practical Animation Tips](https://emilkowal.ski/ui/7-practical-animation-tips)
|
|
43
|
+
|
|
44
|
+
Source: [Good CSS](https://good-css.com/specimen/motion-tokens), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#motion-tokens).
|
|
45
|
+
|
|
46
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-no-rubber-band-bounce-on-desktop"
|
|
3
|
+
title: "No rubber-band bounce on desktop"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "scroll"
|
|
8
|
+
- "viewport"
|
|
9
|
+
- "no"
|
|
10
|
+
- "rubber"
|
|
11
|
+
- "band"
|
|
12
|
+
- "bounce"
|
|
13
|
+
- "desktop"
|
|
14
|
+
summary: "Use it on app-like pages with fixed UI, such as a sidebar or a sticky header. On macOS a trackpad scroll stretches the whole page past its edge and drags the fixed UI along."
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
18
|
+
# No rubber-band bounce on desktop
|
|
19
|
+
|
|
20
|
+
Use it on app-like pages with fixed UI, such as a sidebar or a sticky header. On macOS a trackpad scroll stretches the whole page past its edge and drags the fixed UI along.
|
|
21
|
+
|
|
22
|
+
```css
|
|
23
|
+
html { overscroll-behavior-y: none; }
|
|
24
|
+
|
|
25
|
+
@media (any-pointer: coarse) {
|
|
26
|
+
html { overscroll-behavior-y: auto; }
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Why it works:
|
|
31
|
+
|
|
32
|
+
- `overscroll-behavior-y: none` on the root turns off the bounce.
|
|
33
|
+
- Touch devices get the default back, because pull to refresh depends on it.
|
|
34
|
+
- `any-pointer` checks every input, where `pointer` checks only the primary one. A device with a touchscreen plus a mouse or stylus keeps pull to refresh. Jhey's example is a foldable.
|
|
35
|
+
|
|
36
|
+
Rules:
|
|
37
|
+
|
|
38
|
+
- Set it on `html`. The viewport takes its overscroll behavior from the root element.
|
|
39
|
+
- Use the `-y` longhand. `overscroll-behavior-x: none` also turns off swipe to go back.
|
|
40
|
+
- Give inner scroll containers, such as a sheet, a chat list or a sidebar, `overscroll-behavior: contain`. They keep their own bounce and stop the page behind them from moving. Never use a `touchmove` listener with `preventDefault()` for this.
|
|
41
|
+
|
|
42
|
+
- Borrowed from: wits, @witsdev on X, 2026-09-30, describing how Vercel gates it behind `pointer: fine` · Jhey Tompkins, @jh3yy on X, for the `any-pointer` version · Emil Kowalski, [mobile-native skill](https://github.com/emilkowalski/skills/blob/main/skills/mobile-native/SKILL.md), for the inner containers
|
|
43
|
+
- Docs: [MDN: overscroll-behavior](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/overscroll-behavior) · [MDN: any-pointer](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/any-pointer)
|
|
44
|
+
|
|
45
|
+
Source: [Good CSS](https://good-css.com/specimen/no-rubber-band-bounce-on-desktop), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#no-rubber-band-bounce-on-desktop).
|
|
46
|
+
|
|
47
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-oklch-color"
|
|
3
|
+
title: "OKLCH color"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "foundations"
|
|
8
|
+
- "oklch"
|
|
9
|
+
- "color"
|
|
10
|
+
summary: "Write colors in `oklch()` and derive every related color from a base with `color-mix(in oklch, …)`. Never hardcode a second hex or an `rgba()` for a hover, tint or transparent version."
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
14
|
+
# OKLCH color
|
|
15
|
+
|
|
16
|
+
Write colors in `oklch()` and derive every related color from a base with `color-mix(in oklch, …)`. Never hardcode a second hex or an `rgba()` for a hover, tint or transparent version.
|
|
17
|
+
|
|
18
|
+
```css
|
|
19
|
+
:root {
|
|
20
|
+
--primary: oklch(55% 0.15 250);
|
|
21
|
+
--primary-hover: color-mix(in oklch, var(--primary), black 15%);
|
|
22
|
+
--primary-subtle: color-mix(in oklch, var(--primary) 12%, transparent);
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Why it works:
|
|
27
|
+
|
|
28
|
+
- The three channels are lightness, chroma and hue. Lightness is perceptual, so two colors with the same L look equally light whatever their hue. Scales and contrast become predictable.
|
|
29
|
+
- Mixing in OKLCH keeps the hue of the base color, where sRGB mixing drifts toward gray.
|
|
30
|
+
- One base value drives the whole family. Change the base and the hover and subtle versions follow.
|
|
31
|
+
|
|
32
|
+
Rules:
|
|
33
|
+
|
|
34
|
+
- Write grays, white and black with `none` as the hue, as in `oklch(98% 0 none)`. A written hue of `0` is red, and it pulls every mix toward red. Half white `oklch(1 0 0)` and half a blue at hue 264 came out at hue 312 in Chrome and Safari. With `none`, or with the keyword `white`, it stayed at 264.
|
|
35
|
+
|
|
36
|
+
- Borrowed from: Andrey Sitnik and Travis Turner (Evil Martians), [OKLCH in CSS: why we moved from RGB and HSL](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl) · Björn Ottosson, [A perceptual color space for image processing](https://bottosson.github.io/posts/oklab/)
|
|
37
|
+
- Docs: [MDN: oklch()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/oklch) · [MDN: color-mix()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/color-mix) · [oklch.com picker](https://oklch.com/)
|
|
38
|
+
|
|
39
|
+
Source: [Good CSS](https://good-css.com/specimen/oklch-color), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#oklch-color).
|
|
40
|
+
|
|
41
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-one-fluid-scale-for-type-and-space"
|
|
3
|
+
title: "One fluid scale for type and space"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "foundations"
|
|
8
|
+
- "one"
|
|
9
|
+
- "fluid"
|
|
10
|
+
- "scale"
|
|
11
|
+
- "type"
|
|
12
|
+
- "space"
|
|
13
|
+
summary: "Use it when a design has more than two or three font sizes. Six numbers describe every size, and the browser derives the steps. It replaces a table of sizes per breakpoint, and it replaces a generator that someone has to find and run again."
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
17
|
+
# One fluid scale for type and space
|
|
18
|
+
|
|
19
|
+
Use it when a design has more than two or three font sizes. Six numbers describe every size, and the browser derives the steps. It replaces a table of sizes per breakpoint, and it replaces a generator that someone has to find and run again.
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
:root {
|
|
23
|
+
--narrow: 20;
|
|
24
|
+
--wide: 77.5;
|
|
25
|
+
--size-narrow: 1.125;
|
|
26
|
+
--size-wide: 1.25;
|
|
27
|
+
--ratio-narrow: 1.2;
|
|
28
|
+
--ratio-wide: 1.25;
|
|
29
|
+
|
|
30
|
+
--fluid: clamp(0rem, (100vw - var(--narrow) * 1rem) / (var(--wide) - var(--narrow)), 1rem);
|
|
31
|
+
--at-narrow: calc(var(--size-narrow) * (1rem - var(--fluid)));
|
|
32
|
+
--at-wide: calc(var(--size-wide) * var(--fluid));
|
|
33
|
+
|
|
34
|
+
--step--1: calc(var(--step-0) / var(--ratio-narrow));
|
|
35
|
+
--step-0: calc(var(--at-narrow) + var(--at-wide));
|
|
36
|
+
--step-1: calc(var(--at-narrow) * var(--ratio-narrow) + var(--at-wide) * var(--ratio-wide));
|
|
37
|
+
--step-2: calc(var(--at-narrow) * pow(var(--ratio-narrow), 2) + var(--at-wide) * pow(var(--ratio-wide), 2));
|
|
38
|
+
--step-3: calc(var(--at-narrow) * pow(var(--ratio-narrow), 3) + var(--at-wide) * pow(var(--ratio-wide), 3));
|
|
39
|
+
--step-4: calc(var(--at-narrow) * pow(var(--ratio-narrow), 4) + var(--at-wide) * pow(var(--ratio-wide), 4));
|
|
40
|
+
|
|
41
|
+
--space-s: var(--step-0);
|
|
42
|
+
--space-m: calc(1.5 * var(--step-0));
|
|
43
|
+
--space-l: calc(2 * var(--step-0));
|
|
44
|
+
--space-s-l: calc(var(--at-narrow) + 2 * var(--at-wide));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
body { font-size: var(--step-0); }
|
|
48
|
+
h1 { font-size: var(--step-4); }
|
|
49
|
+
h2 { font-size: var(--step-2); }
|
|
50
|
+
small { font-size: var(--step--1); }
|
|
51
|
+
|
|
52
|
+
.card {
|
|
53
|
+
display: grid;
|
|
54
|
+
gap: var(--space-s);
|
|
55
|
+
padding: var(--space-s-l);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Why it works:
|
|
60
|
+
|
|
61
|
+
- The first six lines are the whole design decision. Widths and sizes are in rem, written as plain numbers, so 20 is 320px and 1.125 is 18px. Body text runs from 18px on a 320px screen to 20px on a 1240px one, and each heading step is 1.2 times the last on the phone and 1.25 times on the desktop.
|
|
62
|
+
- `--fluid` is a length that is 0 at the narrow width, 1rem at the wide width and a straight line between. It holds the only `clamp()`, and that one bounds every token.
|
|
63
|
+
- `--at-narrow` and `--at-wide` are the two base sizes, each weighted by how close the viewport is to its width. Their sum is step 0.
|
|
64
|
+
- `pow()` raises each ratio to the step number. A step is the narrow scale's size times one weight plus the wide scale's size times the other, which is the line [Fluid sizes with `clamp()`](https://good-css.com/#fluid-sizes-with-clamp) draws between two points.
|
|
65
|
+
- A space token is a multiple of step 0, so it grows as much as body text does. A pair such as `--space-s-l` gives the two terms different multipliers. It is the small size on the phone and the large size on the desktop, 18px to 40px here.
|
|
66
|
+
|
|
67
|
+
Rules:
|
|
68
|
+
|
|
69
|
+
- Write the inputs as plain numbers. With `20rem` and `77.5rem` the `--fluid` line divides a length by a length, and Firefox does not support that.
|
|
70
|
+
- Change an input in the rule that declares the steps. A step takes its value where it is declared, so a ratio set on a descendant changes nothing below it.
|
|
71
|
+
- Derive sizes below step 0 from step 0 and the narrow ratio, as `--step--1` does. With both ratios a small size shrinks as the screen grows.
|
|
72
|
+
- Check the top step against the limit in [Fluid sizes with `clamp()`](https://good-css.com/#fluid-sizes-with-clamp). Step `n` passes when `(size-wide / size-narrow) × (ratio-wide / ratio-narrow)ⁿ` is 2.5 or less. The example gives 1.31 at step 4.
|
|
73
|
+
- Write a pair only where a layout uses it, and never use one for a font size. A pair is steep on purpose and breaks that limit.
|
|
74
|
+
- For a scale that follows the container, write `100cqi` in place of `100vw`. [Container queries with container units](https://good-css.com/#container-queries-with-container-units) has the rules.
|
|
75
|
+
- Components use the tokens and never repeat the math.
|
|
76
|
+
|
|
77
|
+
Support: Chrome 120, Firefox 118, Safari 15.4, set by `pow()`. Tested in Chrome 150 and real Safari 27 on 2026-10-01, where every step came out within 0.001px of the formula.
|
|
78
|
+
|
|
79
|
+
- Borrowed from: James Gilyead, [Designing with fluid type scales](https://utopia.fyi/blog/designing-with-fluid-type-scales), 2020-02-01, for the two scales · Trys Mudford, [CSS-only fluid modular type scales](https://utopia.fyi/blog/css-modular-scales), 2020-02-02, and [Fluid custom properties](https://utopia.fyi/blog/fluid-custom-properties), 2020-02-03 · James Gilyead, [Designing with a fluid space palette](https://utopia.fyi/blog/designing-with-a-fluid-space-palette), 2021-03-16, for space pairs. Utopia prints one finished `clamp()` per step from a calculator, because CSS had no powers in 2020. With `pow()` the browser does that math.
|
|
80
|
+
- Background: Miriam Suzanne, [Responsive and Fluid Typography with Baseline CSS Features](https://web.dev/articles/baseline-in-action-fluid-type), 2025-12-16
|
|
81
|
+
- Docs: [MDN: pow()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/pow) · [Utopia type calculator](https://utopia.fyi/type/calculator/)
|
|
82
|
+
|
|
83
|
+
Source: [Good CSS](https://good-css.com/specimen/one-fluid-scale-for-type-and-space), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#one-fluid-scale-for-type-and-space).
|
|
84
|
+
|
|
85
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-one-focus-ring-with-focus-visible"
|
|
3
|
+
title: "One focus ring with :focus-visible"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "interaction"
|
|
8
|
+
- "one"
|
|
9
|
+
- "focus"
|
|
10
|
+
- "ring"
|
|
11
|
+
- "visible"
|
|
12
|
+
summary: "Use it as the focus style for every interactive element. Never write `outline: none`."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
16
|
+
# One focus ring with `:focus-visible`
|
|
17
|
+
|
|
18
|
+
Use it as the focus style for every interactive element. Never write `outline: none`.
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
:focus-visible {
|
|
22
|
+
outline: max(2px, 0.08em) solid currentColor;
|
|
23
|
+
outline-offset: 0.25em;
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Why it works:
|
|
28
|
+
|
|
29
|
+
- The browser decides when a ring helps. Keyboard focus gets one, a mouse click on a button does not, and text inputs always do.
|
|
30
|
+
- `currentColor` and `em` make the ring follow each component's color and size.
|
|
31
|
+
- `outline` takes no layout space and survives forced-colors mode, which removes `box-shadow`.
|
|
32
|
+
|
|
33
|
+
Rules:
|
|
34
|
+
|
|
35
|
+
- On a filled button `currentColor` can fail contrast. Set the outline to the button's background color there.
|
|
36
|
+
- A component that draws its ring with `box-shadow` keeps `outline-color: transparent`, never `outline: none`, so forced-colors mode can repaint it.
|
|
37
|
+
|
|
38
|
+
Support: Chrome 86, Firefox 85, Safari 15.4.
|
|
39
|
+
|
|
40
|
+
- Borrowed from: Stephanie Eckles, [Modern CSS Upgrades To Improve Accessibility](https://moderncss.dev/modern-css-upgrades-to-improve-accessibility/), 2021-04-09, and [Smol Focus Styles](https://smolcss.dev/#smol-focus-styles) · Kevin Powell, [3 super small changes to improve your CSS](https://www.youtube.com/watch?v=zT9Ftmw0-uc), 2023-03-07
|
|
41
|
+
|
|
42
|
+
Source: [Good CSS](https://good-css.com/specimen/one-focus-ring-with-focus-visible), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#one-focus-ring-with-focus-visible).
|
|
43
|
+
|
|
44
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-one-set-of-color-tokens-for-light-and-dark"
|
|
3
|
+
title: "One set of color tokens for light and dark"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "foundations"
|
|
8
|
+
- "one"
|
|
9
|
+
- "set"
|
|
10
|
+
- "color"
|
|
11
|
+
- "tokens"
|
|
12
|
+
- "light"
|
|
13
|
+
- "dark"
|
|
14
|
+
summary: "Use it on any site with a dark mode. Each token holds both values, so there is no second block of variables under a media query or a `.dark` class."
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
18
|
+
# One set of color tokens for light and dark
|
|
19
|
+
|
|
20
|
+
Use it on any site with a dark mode. Each token holds both values, so there is no second block of variables under a media query or a `.dark` class.
|
|
21
|
+
|
|
22
|
+
```css
|
|
23
|
+
:root {
|
|
24
|
+
color-scheme: light dark;
|
|
25
|
+
--background: light-dark(oklch(1 0 none), oklch(0.145 0 none));
|
|
26
|
+
--foreground: light-dark(oklch(0.145 0 none), oklch(0.985 0 none));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[data-theme="light"] { color-scheme: light; }
|
|
30
|
+
[data-theme="dark"] { color-scheme: dark; }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Why it works:
|
|
34
|
+
|
|
35
|
+
- `color-scheme: light dark` says the page supports both and follows the system setting. It also switches the browser's own UI, such as form controls, scrollbars and the default page background.
|
|
36
|
+
- `light-dark(a, b)` returns `a` in a light scheme and `b` in a dark one.
|
|
37
|
+
- It resolves per element. Set `color-scheme` on any subtree and every token used inside it flips. The same tokens serve a manual toggle and a section that is always dark.
|
|
38
|
+
|
|
39
|
+
Rules:
|
|
40
|
+
|
|
41
|
+
- Switch themes by setting `color-scheme`. Never redefine the tokens.
|
|
42
|
+
- The grays use `none` for the hue, so they can be mixed with a color later without drifting toward red. [OKLCH color](https://good-css.com/#oklch-color) has the measurement.
|
|
43
|
+
- `light-dark()` takes two colors. Anything else that differs by theme, such as an image, needs its own rule.
|
|
44
|
+
- Add `<meta name="color-scheme" content="light dark">` so the browser paints the right background before the CSS loads.
|
|
45
|
+
|
|
46
|
+
Support: Chrome 123, Firefox 120, Safari 17.5. Baseline since May 2024.
|
|
47
|
+
|
|
48
|
+
- Borrowed from: IP, @ipwanciu on X, 2025-10-30
|
|
49
|
+
- Docs: [MDN: light-dark()](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/light-dark) · [MDN: color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/color-scheme) · Bramus Van Damme, [CSS color-scheme-dependent colors with light-dark()](https://web.dev/articles/light-dark)
|
|
50
|
+
|
|
51
|
+
Source: [Good CSS](https://good-css.com/specimen/one-set-of-color-tokens-for-light-and-dark), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#one-set-of-color-tokens-for-light-and-dark).
|
|
52
|
+
|
|
53
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-opt-in-motion"
|
|
3
|
+
title: "Opt-in motion"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "motion"
|
|
8
|
+
- "opt"
|
|
9
|
+
summary: "Use it for every transition or animation that moves or scales something."
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
13
|
+
# Opt-in motion
|
|
14
|
+
|
|
15
|
+
Use it for every transition or animation that moves or scales something.
|
|
16
|
+
|
|
17
|
+
```css
|
|
18
|
+
@media (hover: hover) and (pointer: fine) {
|
|
19
|
+
.box:hover { transform: scale(1.2); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
23
|
+
.box { transition: transform 300ms var(--ease-out, ease-out); }
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Why it works:
|
|
28
|
+
|
|
29
|
+
- Motion exists only inside the query, so the default is none. People who never touched the setting match `no-preference` and see it.
|
|
30
|
+
|
|
31
|
+
Rules:
|
|
32
|
+
|
|
33
|
+
- Build the state change first and add the motion inside the query. Do not strip motion afterwards with a global rule that sets every duration to `0.01ms`.
|
|
34
|
+
- Fades of opacity and color are not motion and can stay outside the query.
|
|
35
|
+
- Animation driven by a script checks the same query with `matchMedia`.
|
|
36
|
+
|
|
37
|
+
Support: Chrome 74, Firefox 63, Safari 10.1.
|
|
38
|
+
|
|
39
|
+
- Borrowed from: Josh W. Comeau, [Accessible Animations in React with "prefers-reduced-motion"](https://www.joshwcomeau.com/react/prefers-reduced-motion/) · Kevin Powell, [3 super small changes to improve your CSS](https://www.youtube.com/watch?v=zT9Ftmw0-uc), 2023-03-07
|
|
40
|
+
|
|
41
|
+
Source: [Good CSS](https://good-css.com/specimen/opt-in-motion), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#opt-in-motion).
|
|
42
|
+
|
|
43
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
topic: "good-css-overflow-clip-over-hidden"
|
|
3
|
+
title: "overflow: clip over hidden"
|
|
4
|
+
tier: "reference"
|
|
5
|
+
tags:
|
|
6
|
+
- "good-css"
|
|
7
|
+
- "layout"
|
|
8
|
+
- "overflow"
|
|
9
|
+
- "clip"
|
|
10
|
+
- "over"
|
|
11
|
+
- "hidden"
|
|
12
|
+
summary: "Use it whenever you want to cut off overflow and do not need scrolling."
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<!-- Generated by pnpm good-css:sync. Local notes belong in compatibility.md. -->
|
|
16
|
+
# `overflow: clip` over `hidden`
|
|
17
|
+
|
|
18
|
+
Use it whenever you want to cut off overflow and do not need scrolling.
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
.hero { overflow-x: clip; }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Why it works:
|
|
25
|
+
|
|
26
|
+
- `hidden` turns the element into a scroll container and sets the other axis to `auto`. That causes stray scrollbars and breaks `position: sticky` inside it.
|
|
27
|
+
- `clip` cuts the overflow and does neither.
|
|
28
|
+
|
|
29
|
+
Rules:
|
|
30
|
+
|
|
31
|
+
- Put it on the element that overflows, not on `html` or `body`.
|
|
32
|
+
- Keep `hidden` or `auto` for elements a script scrolls. `clip` blocks that too.
|
|
33
|
+
- Keep `hidden` or `auto` on an element with `resize`. With `clip` the browser draws no resize handle.
|
|
34
|
+
|
|
35
|
+
Support: Chrome 90, Firefox 81, Safari 16.
|
|
36
|
+
|
|
37
|
+
- Borrowed from: Kevin Powell, [2 better alternatives to overflow: hidden](https://www.youtube.com/watch?v=72pUm4tQesw), 2023-12-14
|
|
38
|
+
|
|
39
|
+
Source: [Good CSS](https://good-css.com/specimen/overflow-clip-over-hidden), [pinned source](https://github.com/vojtaholik/good-css/blob/6d16d2fd27f4892e2aea4b5c5c2b016f45be7eef/PRACTICES.md#overflow-clip-over-hidden).
|
|
40
|
+
|
|
41
|
+
Copyright (c) 2026 Vojta Holik. License: upstream/LICENSE in this plugin.
|