Frontend Design

Crafting User Interfaces

Settings

Chapters

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.

&

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-driven animations

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

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.

Performance & accessibility first

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.