css-expert
Expert-level CSS patterns covering custom properties as design tokens, CSS Grid deep dive, Flexbox vs Grid decision guide, container queries, cascade layers, modern selectors (:has, :is, :where), logical properties, clamp() for fluid typography, scroll-driven animations,
CSS Expert
Modern CSS (2024) is a different language from 2015 CSS. Container queries let components
respond to their own size. Cascade layers give you full control over specificity. :has()
enables parent selection. Scroll-driven animations work without JavaScript. Custom properties
create design systems. The browser does more than ever — learning when to let CSS handle it
natively is the key skill.
Core Mental Model
CSS is a set of cascading declarations applied to a document tree. The cascade — specificity,
source order, and now layers — determines which rules win. Custom properties cascade like any
other property, which makes them powerful design tokens. The box model, formatting context, and
stacking context are the mental models that explain every layout mystery. Modern CSS makes
JavaScript animation and media-query-based breakpoints less necessary.
Custom Properties as Design Tokens
/* Design token system — define once, use everywhere */
:root {
/* Primitive tokens */
--color-teal-400: #2dd4bf;
--color-teal-500: #14b8a6;
--color-slate-900: #0f172a;
--color-slate-800: #1e293b;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-4: 1rem;
--space-8: 2rem;
/* Semantic tokens (reference primitives) */
--ds-bg-primary: var(--color-slate-900);
--ds-bg-surface: var(--color-slate-800);
--ds-text-primary: #f8fafc;
--ds-text-secondary: #94a3b8;
--ds-text-muted: #475569;
--ds-accent: var(--color-teal-400);
--ds-border-subtle: rgba(255, 255, 255, 0.08);
/* Component tokens (reference semantic) */
--button-bg: var(--ds-accent);
--button-radius: 0.5rem;
}
/* Dark/light mode via :root overrides */
@media (prefers-color-scheme: light) {
:root {
--ds-bg-primary: #ffffff;
--ds-text-primary: #0f172a;
}
}
/* Component theming */
.card {
background: var(--ds-bg-surface);
color: var(--ds-text-primary);
border: 1px solid var(--ds-border-subtle);
}
/* Local override without global change */
.card--highlight {
--ds-bg-surface: #1a2744; /* local scope */
}
CSS Grid — Deep Dive
Template areas
.dashboard {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: 60px 1fr auto;
grid-template-areas:
"sidebar header"
"sidebar main"
"sidebar footer";
min-height: 100dvh;
}
.sidebar { grid-area: sidebar; }
.header { grid-area: header; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* Responsive: collapse sidebar */
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"footer";
}
.sidebar { display: none; }
}
auto-fill vs auto-fit with minmax
/* auto-fill: creates as many tracks as fit, even if empty */
.grid-fill {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
/* → 5 columns at 1200px, 3 at 700px, 1 at 250px */
/* → empty tracks still occupy space */
/* auto-fit: collapses empty tracks, items stretch */
.grid-fit {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
/* → same column count, but 3 items at 1200px stretch to fill width */
/* The RAM pattern (Repeat, Auto, Minmax) for responsive without media queries */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
gap: clamp(1rem, 3vw, 2rem);
}
Explicit placement and spanning
.featured-article {
grid-column: 1 / -1; /* span all columns */
grid-row: 1 / 3; /* span 2 rows */
}
.sidebar-widget {
grid-column: 3; /* specific column */
grid-row: span 3; /* span 3 rows from auto position */
}
/* Dense packing to fill holes */
.masonry-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-flow: dense; /* fills holes with smaller items */
}
Flexbox Decision Guide
Use Flexbox when: Use Grid when:
───────────────────────────────── ────────────────────────────────
One dimension (row OR column) Two dimensions (rows AND columns)
Dynamic content sizes Fixed/known layout structure
Centered content (space-around) Named template areas
Navigation bars Dashboard / page layout
Button groups Card grids
Distributing space between items Overlapping elements
/* Flexbox: centering (classic use case) */
.center-content {
display: flex;
align-items: center;
justify-content: center;
}
/* Flexbox: navigation */
.nav {
display: flex;
align-items: center;
gap: 1rem;
}
.nav .spacer { flex: 1; } /* push right-side items to the right */
/* Flexbox: equal-height cards in a row */
.card-row {
display: flex;
gap: 1rem;
align-items: stretch; /* default: cards match tallest sibling */
}
Container Queries
/* Define a containment context */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Style based on container width, not viewport width */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
@container card (max-width: 400px) {
.card-title { font-size: 1rem; }
}
/* Container queries: component is truly reusable at any size */
/* Same card component works in narrow sidebar AND wide main area */
Cascade Layers
/* Establish layer order — later layers win over earlier */
@layer reset, base, components, utilities, overrides;
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
}
@layer base {
h1 { font-size: 2rem; color: var(--ds-text-primary); }
a { color: var(--ds-accent); }
}
@layer components {
.btn {
padding: 0.5rem 1rem;
border-radius: var(--button-radius);
background: var(--button-bg);
}
}
@layer utilities {
.mt-4 { margin-top: 1rem; }
.text-muted { color: var(--ds-text-muted); }
}
/* Styles outside layers have highest priority (migration path for legacy CSS) */
.override { color: red !important; } /* still wins vs layered styles */
Modern Selectors
/* :is() — forgiving, matches any in list, takes highest specificity */
:is(h1, h2, h3, h4) {
font-weight: 700;
line-height: 1.2;
}
/* :where() — like :is() but ZERO specificity (safe for resets) */
:where(article, section) :where(h1, h2, h3) {
margin-top: 1.5rem;
}
/* :has() — parent/relational selector (game changer) */
/* Card with an image gets special layout */
.card:has(img) {
display: grid;
grid-template-columns: 120px 1fr;
}
/* Form field with invalid input */
.field:has(input:invalid) label {
color: var(--color-error);
}
/* Navigation item with active child */
.nav-item:has(.active) {
background: var(--ds-bg-elevated);
}
/* :not() with compound selectors */
.btn:not(:disabled):not(.btn--ghost):hover {
filter: brightness(1.1);
}
Logical Properties
/* Physical properties (tied to left/right/top/bottom) */
/* ❌ Breaks in RTL languages */
.card { margin-left: 1rem; padding-right: 1rem; }
/* Logical properties (flow-relative) */
/* ✅ Works in LTR and RTL */
.card {
margin-inline-start: 1rem; /* left in LTR, right in RTL */
padding-inline-end: 1rem; /* right in LTR, left in RTL */
margin-block: 1.5rem; /* top and bottom */
padding-inline: 1rem; /* left and right */
border-inline-start: 3px solid var(--ds-accent); /* left border in LTR */
inset-inline-start: 0; /* left: 0 in LTR */
}
/* Sizing */
.element {
inline-size: 100%; /* width in horizontal writing mode */
block-size: auto; /* height in horizontal writing mode */
min-inline-size: 280px; /* min-width */
}
Fluid Typography with clamp()
/* clamp(MIN, PREFERRED, MAX) */
/* PREFERRED: fluid value between breakpoints */
/* Formula: MIN + (MAX - MIN) * (viewport - minVP) / (maxVP - minVP) */
:root {
/* Font sizes that scale fluidly between 320px and 1280px viewport */
--text-sm: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-base: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
--text-lg: clamp(1.125rem, 1rem + 0.5vw, 1.5rem);
--text-xl: clamp(1.25rem, 1rem + 1vw, 2rem);
--text-2xl: clamp(1.5rem, 1rem + 2vw, 3rem);
--text-4xl: clamp(2rem, 1rem + 4vw, 5rem);
/* Spacing that scales with viewport */
--space-section: clamp(3rem, 8vw, 8rem);
--space-content: clamp(1rem, 3vw, 2rem);
}
/* Gap that adjusts to container */
.grid { gap: clamp(1rem, 3vw, 2rem); }
Scroll-Driven Animations
/* Link animation progress to scroll position — no JavaScript */
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Entry animation: plays as element enters viewport */
.card {
animation: fade-in-up linear both;
animation-timeline: view(); /* tied to element's visibility in viewport */
animation-range: entry 0% entry 40%; /* play during first 40% of entry */
}
/* Reading progress bar */
.progress-bar {
position: fixed;
top: 0;
left: 0;
height: 3px;
background: var(--ds-accent);
transform-origin: left;
animation: grow-bar linear;
animation-timeline: scroll(root); /* tied to page scroll */
}
@keyframes grow-bar {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
/* Parallax effect */
.hero-bg {
animation: parallax linear both;
animation-timeline: view();
animation-range: exit 0% exit 100%;
}
@keyframes parallax {
to { transform: translateY(-30%); }
}
color-mix()
/* Mix colors in CSS — no preprocessor needed */
.muted-accent {
color: color-mix(in srgb, var(--ds-accent) 40%, var(--ds-bg-primary));
}
/* Tints and shades from a single token */
:root {
--brand: #2dd4bf;
--brand-10: color-mix(in srgb, var(--brand) 10%, white);
--brand-20: color-mix(in srgb, var(--brand) 20%, white);
--brand-dark: color-mix(in srgb, var(--brand) 80%, black);
}
/* Transparent variant */
.badge {
background: color-mix(in srgb, var(--ds-accent) 15%, transparent);
color: var(--ds-accent);
}
@starting-style for Entry Animations
/* Animate elements entering the DOM (e.g., from display:none) */
.dialog[open] {
opacity: 1;
transform: scale(1);
transition: opacity 200ms, transform 200ms;
}
@starting-style {
.dialog[open] {
opacity: 0; /* start from here when [open] attribute added */
transform: scale(0.95);
}
}
CSS Architecture
Approach | Best for | Pros | Cons
────────────┼───────────────────────┼─────────────────────────┼──────────────────
BEM | Team/large projects | Predictable, portable | Verbose classnames
Utility | Rapid prototyping | Fast, consistent | HTML clutter
(Tailwind) | | |
Component | Design systems | Encapsulated, composable| Setup cost
(CSS Modules)| | |
Cascade | Layered architecture | Clear specificity order | New concept
Layers | | |
Anti-Patterns
/* ❌ !important for specificity battles */
.btn { color: red !important; }
/* ✅ Restructure with cascade layers or lower specificity with :where() */
/* ❌ Fixed pixel widths that break on mobile */
.container { width: 1200px; }
/* ✅ max-width with padding */
.container { max-width: 1200px; width: 100%; padding-inline: 1rem; }
/* ❌ Using margin for layout between sibling components */
.card { margin-bottom: 2rem; } /* bleeds into container */
/* ✅ gap on the parent grid/flex container */
.card-grid { display: grid; gap: 2rem; }
/* ❌ Overriding :root variables for a single component */
:root { --ds-accent: orange; } /* changes everywhere */
/* ✅ Scope to component */
.special-section { --ds-accent: orange; }
/* ❌ Absolute positioning for everything */
/* ✅ Grid/Flex handle 90% of layout; position:absolute for overlays/tooltips */
/* ❌ px for font sizes (overrides user preferences) */
body { font-size: 14px; }
/* ✅ rem relative to user's browser setting */
body { font-size: 1rem; }
Quick Reference
Tokens: :root custom props → semantic → component tiers
Grid areas: grid-template-areas names, named grid-area on children
auto-fill vs fit: fill = empty tracks kept; fit = empty tracks collapsed
Container queries: container-type:inline-size → @container name (min-width:Xpx)
Cascade layers: @layer order declaration → later layers win
:has(): parent selection, relational queries, no JS needed
:is()/:where(): :is() keeps specificity; :where() = zero specificity
clamp(): fluid sizes between min and max with viewport units
Scroll-driven: animation-timeline:view() entry/exit, scroll(root) for progress
color-mix(): mix colors without preprocessor, tints and transparent variantsSkill Information
- Source
- MoltbotDen
- Category
- Coding Agents & IDEs
- Repository
- View on GitHub
Related Skills
go-expert
Write idiomatic, production-quality Go code. Use when building Go APIs, CLIs, microservices, or systems code. Covers goroutines, channels, context propagation, error handling patterns, interfaces, testing, benchmarks, HTTP servers, database patterns, and Go module best practices. Expert-level Go idioms that senior engineers expect.
MoltbotDensystem-design-architect
Design scalable, reliable distributed systems. Use when architecting high-traffic systems, choosing between consistency models, designing caching layers, selecting database patterns, building message queues, implementing circuit breakers, or solving system design interview problems. Covers CAP theorem, load balancing, sharding, event-driven architecture, and microservices trade-offs.
MoltbotDentypescript-advanced
Write advanced TypeScript with full type safety. Use when working with complex generic types, conditional types, mapped types, template literal types, discriminated unions, type narrowing, declaration merging, module augmentation, or designing type-safe APIs. Covers TypeScript 5.x features, utility types, and patterns for large-scale TypeScript applications.
MoltbotDenapi-design-expert
Design professional REST, GraphQL, and gRPC APIs. Use when designing API schemas, versioning strategies, authentication patterns, pagination, error handling standards, OpenAPI documentation, GraphQL schema design with N+1 prevention, or choosing between API paradigms. Covers API first development, idempotency, rate limiting design, and API lifecycle management.
MoltbotDenrust-systems
Write safe, performant Rust systems code. Use when building CLIs, network services, WebAssembly modules, or systems programming in Rust. Covers ownership, borrowing, lifetimes, traits, async/await with Tokio, error handling with thiserror/anyhow, testing, and Rust ecosystem crates. Idiomatic Rust patterns that pass code review.
MoltbotDen