Architectural foundations
Another look at the cascade
Understanding the core concepts of the cascade is essential to mastering modern UI development. We’ll revisit specificity, inheritance, and source order with today’s tools in mind, so your styles remain predictable even as codebases grow.
Cascade layers
@layer gives us a predictable hierarchy — base → components → utilities — so conflicts are solved structurally rather than with !important. We’ll place rules intentionally to keep overrides obvious and safe.
:has()
:has() unlocks parent-aware styling and stateful UI patterns without extra utility classes. We’ll use it for validation states, toggle-driven layouts, and component “context” styling, while noting performance and sensible fallbacks.
Nesting
Nesting can improve readability when used with discipline. We’ll set guardrails to avoid creeping specificity and deep DOM coupling, keeping nested rules shallow and component-focused.
The new responsive
Container queries
Design components that respond to their container, not the viewport. Container queries let cards, navs, and media blocks adapt intelligently wherever they’re placed.
:has()
:has() doubles as a “responsive to content” tool: adapt a component when it contains images, long text, or a certain control. That means fewer special-case classes and more self-aware UI.
Dynamic viewport units
Dynamic units (dvh) track the usable viewport amid browser UI changes, especially on mobile. They stabilize full-height sections and avoid content jumps.
Wide-gamut color spaces
Modern displays support richer color — CSS can too. Using wide-gamut spaces lifts brand vibrancy while remaining backward compatible through sensible fallbacks.
color-mix()
color-mix() lets you derive tints, shades, and semantic states from a single brand base, keeping palettes consistent. It’s powerful for theming and on-the-fly contrast adjustments.
System variables (accent-color)
accent-color ties native controls into your theme with minimal code. It improves cohesion quickly, especially for forms, while preserving platform accessibility defaults.
User preference queries
Preferences like prefers-reduced-motion and prefers-color-scheme let users’ needs drive presentation. Respecting them builds trust and keeps experiences inclusive by default.
Theming with light-dark()
The light-dark() function makes it easy to define a single color that adapts automatically to light or dark mode. Instead of duplicating CSS or writing long preference queries, you provide two values — one for light, one for dark — and let the browser pick the right one. Combined with custom properties, this keeps themes compact and consistent.
&
- :has() :focus-visible
- :user-invalid :target-current
- :popover-open :open
- ::view-transition-new() ::view-transition-old()
- ::scroll-button() ::scroll-marker()
- ::details-content ::picker()
- @container @scope
- @layer @starting-style
- @view-transition @position-try
- @function @property
- color(from …) color-mix()
- oklch() oklab()
- if() linear()
- min() max()
- scroll() view()
- clamp() light-dark()
- sibling-index() sibling-count()
- sin() cos()
- anchor() anchor-name
- progress() random()
- calc-size() interpolate-size
- animation-timeline transition-behavior
- text-wrap text-box
- anchor-name position-area
- field-sizing corner-shape
- rlh/lh cap
- cqw svh/dvh
Interactions
Discrete property transitions
CSS supports transitions for “on/off” properties like display and visibility using transition-behavior: allow-discrete. This enables smooth, accessible state changes without extra JavaScript.
Scroll-tied timelines enable motion that follows reading flow and user intent. We’ll add subtle, meaningful movement that enhances comprehension rather than distracts.
View transitions
Animate between pages, routes, and component states to maintain visual continuity. View transitions reduce cognitive load by connecting “before” and “after” in a single, coherent motion.
Anchor positioning
Anchor positioning places overlays relative to a trigger with no hacks and less math. Menus, tooltips, and callouts land where users expect — even as layouts shift.
Popover
The Popover API gives you a declarative, accessible overlay system. It handles focus management, dismissal, and stacking logic, so you can focus on styling and content without writing custom JS.
Command
With the command and commandfor attributes, you can declaratively wire up actions between elements — like buttons and popovers — without writing JavaScript. The browser handles focus, state, and accessibility automatically.
Carousel
-
Feature 1
-
Feature 2
-
Feature 3
-
Feature 4
-
Feature 5
-
Feature 6
-
Feature 7
-
Feature 8
-
Feature 9
-
Feature 10
Bleeding edge
Using the newest features responsibly
It’s tempting to ship the shiniest APIs the moment they land. That’s why understanding browser compatibility and progressive enhancement is essential — so early adopters get the upgrade and everyone else still gets a great experience.
Progressive enhancement as a strategy
Start with a solid baseline that works everywhere, then layer enhancements with @supports, capability checks, and sensible fallbacks. Document decisions in code so future contributors know what can be safely removed once support improves.
Feature detection & fallbacks
Prefer feature detection over UA sniffing and keep fallbacks simple and maintainable. When in doubt, optimize for readability and remove custom code the moment native support is good enough.
New doesn’t automatically mean better; test impact on motion sensitivity, color contrast, and input methods. Measure runtime cost and layout stability before and after—ship what helps users, not just what’s possible.