Modern CSS: Container Queries, `:has()`, OKLCH, Cascade Layers
| Field | Value |
|---|---|
| Type | Skill Resource |
| Source | ~/.copilot/skills/design/references/css/modern-layout.md |
| Description | Not specified |
Source Content
Modern CSS: Container Queries, :has(), OKLCH, Cascade Layers
Four 2024-2026-era CSS features that are now baseline-supported across every current browser this stack targets. Reach for these first — they replace patterns this skill used to solve with JavaScript or extra markup.
Container queries — component-level responsiveness
A media query responds to the viewport. A container query responds to the component’s own box — the thing that actually matters for a reusable component that might sit in a sidebar, a modal, or a full-width section.
.card { container-type: inline-size; container-name: card;}
@container card (min-width: 400px) { .card__body { grid-template-columns: auto 1fr; }}Use this instead of a JS ResizeObserver for “does this component have room for a two-column layout.” Every component that changes layout based on its own width, not the viewport’s, is a container-query candidate.
:has() — the parent selector
Style a parent based on what it contains, without a class toggled by JavaScript.
/* A form field with an invalid input gets an error border on the wrapper. */.field:has(input:invalid) { border-color: var(--color-destructive);}
/* A card with an image gets different padding than a text-only card. */.card:has(img) { padding-block-start: 0;}This replaces the common pattern of a JS effect that adds .has-error or .has-image to a parent. If you’re writing that effect, check whether :has() covers it first.
OKLCH — perceptually uniform color
HSL and hex both misrepresent how humans perceive lightness — two colors with the same HSL lightness value can look very different in brightness. OKLCH fixes this: its lightness channel actually corresponds to perceived brightness, which is why token scales built in OKLCH produce more even-looking steps than the same scale built in HSL.
:root { --color-primary: oklch(62% 0.19 259); /* lightness | chroma | hue */ --color-primary-hover: oklch(54% 0.19 259); /* same hue/chroma, only L drops */}Practical rule for this skill: when defining a new semantic token or an accent-color ramp, author it in OKLCH, not HSL or hex. Existing HSL/hex tokens don’t need an urgent rewrite — see references/oklch-migration.md for the conversion path and how to keep check_contrast.py passing during the move.
Cascade layers — controlled specificity
@layer lets you declare the override order between groups of rules up front, so a later rule in the source doesn’t need higher specificity to win.
@layer reset, tokens, components, utilities;
@layer components { .button { padding: var(--space-sm) var(--space-md); }}
@layer utilities { .p-0 { padding: 0; } /* wins over .button's padding — utilities is a later layer, always */}This is the mechanism that lets Tailwind utility classes reliably override component CSS without a specificity fight — declare @layer reset, tokens, components, utilities; once in the stylesheet entry point, and every rule’s override behavior is decided by which layer it’s in, not how many classes it has.
When to reach for these vs. the existing patterns
| Need | Old pattern | Now |
|---|---|---|
| Component-width-based layout change | ResizeObserver + state | container-type + @container |
| Style a parent from a child’s state | JS class toggle | :has() |
| New color token or ramp | HSL/hex | OKLCH |
| Utility classes must always win | !important or specificity hacks | @layer ordering |
None of these require a build-tool change — they’re plain CSS, supported everywhere this stack ships to. lint_tokens.sh does not yet check for OKLCH-vs-HSL in new token definitions; that’s a reasonable future addition to lints.toml once a real usage pattern to check against exists.