- Layout Fundamentals
- Responsive Design
- Visual Effects
- Interaction & UX
- Performance Optimization
- Modern Selectors
- Animations
- Container Queries
- Production Starter Template
- Interview Tips
Why it matters: Still the most practical solution for one-dimensional layouts. Non-negotiable for any modern frontend role.
.container {
display: flex;
gap: 16px; /* Modern: replaces margin hacks */
align-items: center;
justify-content: space-between;
}
.item {
flex: 1 1 auto; /* Shorthand: grow shrink basis */
align-self: flex-start; /* Override parent alignment */
}Key modern additions:
gapproperty (now works in flexbox, not just grid)align-selffor individual item control
Real-world use cases:
- Navigation bars
- Toolbars
- Button groups
- Single-row/column layouts
Why it matters: Mandatory for senior roles. The most powerful layout system for two-dimensional designs.
.layout {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 24px;
}Critical patterns interviewers expect:
/* auto-fit: collapses empty tracks */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
/* auto-fill: preserves empty tracks */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));When to use:
auto-fit→ Stretch items to fill spaceauto-fill→ Maintain grid structure even with few items
/* Item is at least 240px, can grow to fill available space */
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));/* Fractional units: distributes available space */
grid-template-columns: 1fr 2fr 1fr; /* middle column is 2x wider */.layout {
display: grid;
grid-template-areas:
"header header header"
"sidebar content content"
"footer footer footer";
grid-template-columns: 200px 1fr 1fr;
grid-template-rows: auto 1fr auto;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer { grid-area: footer; }Real-world use cases:
- Dashboard layouts
- Card grids
- Magazine-style layouts
- Complex page structures
Why it matters: Eliminates media query juggling for font sizes. Essential for modern responsive design.
h1 {
font-size: clamp(1.5rem, 2.5vw, 3rem);
/* min: 1.5rem, preferred: 2.5vw, max: 3rem */
}
.container {
padding: clamp(1rem, 5vw, 3rem);
/* Works for any numeric value */
}How it works:
- Browser calculates
2.5vwbased on viewport - If result <
1.5rem, uses1.5rem - If result >
3rem, uses3rem - Otherwise, uses the calculated value
Replaces this old pattern:
/* ❌ Old way */
h1 { font-size: 1.5rem; }
@media (min-width: 768px) {
h1 { font-size: 2rem; }
}
@media (min-width: 1200px) {
h1 { font-size: 3rem; }
}Why it matters: 100vh breaks on mobile browsers due to address bars. Modern units fix this.
/* ❌ Old way - breaks on mobile */
.hero {
height: 100vh;
}
/* ✅ Modern way */
.hero {
height: 100dvh; /* Dynamic viewport height */
}The three modern units:
| Unit | Name | Behavior |
|---|---|---|
svh |
Small Viewport Height | Smallest possible viewport (with address bar visible) |
lvh |
Large Viewport Height | Largest possible viewport (address bar hidden) |
dvh |
Dynamic Viewport Height | Adjusts as address bar shows/hides ⭐ |
Which to use:
- Most cases: Use
dvh(smoothly adapts to mobile browsers) - Fixed content: Use
svh(ensures content always visible) - Fullscreen effects: Use
lvh(maximizes space when possible)
Why it matters: No more padding-bottom hacks for maintaining aspect ratios.
/* ❌ Old way */
.video-wrapper {
position: relative;
padding-bottom: 56.25%; /* 16:9 ratio hack */
}
/* ✅ Modern way */
.video {
aspect-ratio: 16 / 9;
}
.square {
aspect-ratio: 1;
}
.portrait {
aspect-ratio: 3 / 4;
}Real-world use cases:
- Video embeds
- Image containers
- Card thumbnails
- Placeholder elements
Why it matters: Creates glassmorphism effects without complex layering. Used in modern UIs everywhere.
.glass-modal {
backdrop-filter: blur(12px);
background: rgba(255, 255, 255, 0.6);
border: 1px solid rgba(255, 255, 255, 0.8);
}
.glass-navbar {
backdrop-filter: blur(8px) saturate(180%);
background: rgba(255, 255, 255, 0.7);
}Available filters:
blur()→ Background blurbrightness()→ Lighten/darkencontrast()→ Adjust contrastsaturate()→ Color intensityhue-rotate()→ Color shift
Real-world use cases:
- Modals and overlays
- Navigation bars
- Floating panels
- macOS/iOS-style interfaces
Performance note: Use sparingly on large areas; can be GPU-intensive.
Why it matters: Apply visual effects without Photoshop or image manipulation.
img {
filter: grayscale(100%);
}
img:hover {
filter: grayscale(0%);
transition: filter 0.3s ease;
}
.sepia {
filter: sepia(80%);
}
.dark-mode img {
filter: brightness(0.8) contrast(1.2);
}Common filters:
grayscale(0-100%)blur(px)brightness(0-200%)contrast(0-200%)drop-shadow()hue-rotate(deg)invert(0-100%)opacity(0-100%)saturate(0-200%)sepia(0-100%)
Why it matters: Smooth scrolling without JavaScript.
html {
scroll-behavior: smooth;
}Works with:
- Anchor links (
<a href="#section">) scrollIntoView()JavaScript calls- Browser back/forward navigation
Note: Respects prefers-reduced-motion automatically in modern browsers.
Why it matters: Create carousel-like experiences with pure CSS.
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: 16px;
}
.slide {
scroll-snap-align: center;
flex-shrink: 0;
width: 80vw;
}Scroll snap types:
x mandatory→ Always snaps horizontallyy mandatory→ Always snaps verticallyx proximity→ Snaps if close enoughboth mandatory→ Snaps in both directions
Alignment options:
start→ Snap to start of containercenter→ Snap to centerend→ Snap to end
Real-world use cases:
- Image carousels
- Onboarding flows
- Card sliders
- Fullscreen sections
Why it matters: Prevents scroll chaining on mobile. Essential for modals and nested scroll areas.
body {
overscroll-behavior: contain;
/* Prevents pull-to-refresh and navigation gestures */
}
.modal {
overscroll-behavior: contain;
/* Stops scroll from propagating to page behind */
}Values:
auto→ Default browser behaviorcontain→ Prevents scroll chainingnone→ Prevents scroll chaining AND effects (pull-to-refresh)
Why it matters: Hints to browser what will animate, optimizing rendering.
.card {
will-change: transform;
}
.animated-box {
will-change: transform, opacity;
}- Don't use everywhere (counterproductive)
- Apply only before animation starts
- Remove after animation completes (if possible)
- Browser already optimizes common properties
Interview trap question: "Should you add will-change to every animated element?"
Correct answer: "No. Overuse consumes memory and degrades performance. Use sparingly and only for complex animations with performance issues."
When to actually use:
- Complex animations
- Frequent transform/opacity changes
- 60fps requirements
- After profiling shows rendering bottleneck
Why it matters: Massive performance boost for long pages. Can improve initial render by 50%+.
.section {
content-visibility: auto;
contain-intrinsic-size: 600px; /* Estimated height for layout */
}How it works:
- Browser skips rendering offscreen content
- Maintains layout space with
contain-intrinsic-size - Automatically renders when scrolled into view
Real-world impact:
- Long blog posts: ~40% faster render
- Infinite scroll: Significantly reduced memory
- Dashboard cards: Improved time-to-interactive
Best practices:
- Use on repeating sections
- Provide accurate
contain-intrinsic-size - Test with actual content
Why it matters: Isolates component rendering, preventing layout thrashing.
.component {
contain: layout paint;
}
.strict-isolation {
contain: strict; /* layout + paint + size */
}Containment types:
layout→ Isolates layout calculationspaint→ Content won't paint outside boundssize→ Size independent of childrenstyle→ Scoped counter/quote styles
Real-world use cases:
- Widget components
- Third-party embeds
- Dynamic content sections
- Repeated list items
Why it matters: Parent selectors without JavaScript. Game-changing for conditional styling.
/* Card with image gets different styling */
.card:has(img) {
display: grid;
grid-template-columns: 200px 1fr;
}
/* Form with error */
.form:has(.error) {
border-color: red;
}
/* Article without images */
.article:not(:has(img)) {
max-width: 65ch;
}Advanced patterns:
/* Parent based on sibling state */
.nav:has(+ .hero) {
position: absolute;
}
/* Adjacent elements */
h2:has(+ p) {
margin-bottom: 0.5rem;
}
/* Multiple conditions */
.card:has(.featured):has(.new) {
border: 2px solid gold;
}Replaces JavaScript patterns:
- Parent class toggling
- Conditional rendering
- State-based styling
Why it matters: Simplified selector grouping with controllable specificity.
/* :is() - normal specificity */
:is(h1, h2, h3) {
margin-top: 0;
line-height: 1.2;
}
/* :where() - zero specificity */
:where(h1, h2, h3) {
margin: 0;
}Key difference:
/* :is() takes highest specificity */
:is(.class, #id) { } /* specificity: 1-0-0 (id) */
/* :where() always has zero specificity */
:where(.class, #id) { } /* specificity: 0-0-0 */When to use :where():
- Utility/reset styles (easy to override)
- Default styles
- Framework base styles
When to use :is():
- Normal component styles
- When specificity matters
- Replacing complex selector lists
Why it matters: Only transform and opacity trigger GPU acceleration for smooth 60fps animations.
/* ✅ Performant */
.card {
transition: transform 0.2s ease, opacity 0.2s ease;
}
.card:hover {
transform: translateY(-8px) scale(1.02);
}
/* ❌ Causes layout thrashing */
.card:hover {
top: -8px; /* Triggers layout */
width: 102%; /* Triggers layout */
}Recommended properties to animate:
transform(translate, scale, rotate)opacity
Properties to avoid animating:
width,height→ Usescale()insteadtop,left→ Usetranslate()insteadmargin,padding→ Usetranslate()or layout shifts
Common animation pattern:
@keyframes slideIn {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.element {
animation: slideIn 0.3s ease-out;
}Why it matters: Accessibility requirement. Some users experience motion sickness from animations.
/* Default: smooth animations */
.card {
transition: transform 0.3s ease;
}
/* Respect user preference */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}What to disable:
- Automatic animations
- Parallax effects
- Infinite animations
- Smooth scrolling
- Complex transitions
What to keep:
- Hover state changes (instant)
- User-triggered interactions
- Loading states (simplified)
The mental model: Media queries are for page layout. Container queries are for component layout.
/* Component defines itself as a container */
.card {
container-type: inline-size;
/* or: container-name: card; */
}
/* Component adapts based on ITS OWN width, not viewport */
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
}Key difference from media queries:
/* ❌ Media query: breaks in sidebar/modal */
@media (min-width: 768px) {
.card { flex-direction: row; }
}
/* ✅ Container query: works anywhere */
@container (min-width: 400px) {
.card { flex-direction: row; }
}.product-card {
container-type: inline-size;
}
@container (min-width: 500px) {
.product-card {
grid-template-columns: 1fr 2fr;
}
}Why: Card works in full-width page, sidebar, modal, or grid without changes.
Scenario: You're building a component that others will use in unknown layouts.
.widget {
container-type: inline-size;
}
/* Widget adapts to wherever it's placed */
@container (min-width: 300px) {
.widget__content { display: flex; }
}Scenario: Component inside variable-width parent.
<div class="dashboard"> <!-- 1400px wide -->
<aside class="sidebar"> <!-- 300px wide -->
<div class="card"> <!-- Needs to be narrow layout -->
</div>
</aside>
<main> <!-- 1100px wide -->
<div class="card"> <!-- Needs to be wide layout -->
</div>
</main>
</div>Solution: Container queries let cards adapt independently.
Why: You don't know the host page viewport, but you know your container width.
.embedded-widget {
container-type: inline-size;
}
/* Reliable regardless of iframe or host page */
@container (min-width: 600px) {
.embedded-widget { /* wide layout */ }
}.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.dashboard-card {
container-type: inline-size;
}
/* Cards adapt as grid columns resize */
@container (min-width: 450px) {
.dashboard-card__content {
display: grid;
grid-template-columns: 1fr 1fr;
}
}/* DON'T: Use media queries for this */
@media (min-width: 768px) {
.sidebar { display: block; }
.main { grid-column: 2; }
}/* DON'T: Use appropriate media queries */
@media (pointer: coarse) { /* Touch devices */
button { min-height: 44px; }
}
@media (hover: hover) { /* Devices with hover */
.card:hover { transform: scale(1.05); }
}If component:
- Exists only once in app
- Tightly coupled to specific layout
- Never reused
Then: Media queries are simpler.
/* 1. Define container */
.card {
container-type: inline-size;
container-name: card; /* Optional: for specific targeting */
}
/* 2. Default (small) layout */
.card {
display: flex;
flex-direction: column;
}
/* 3. Adapted layouts */
@container card (min-width: 400px) {
.card {
flex-direction: row;
}
}
@container card (min-width: 600px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}| Question | If YES → Use |
|---|---|
| Is this a reusable component? | Container queries |
| Will it appear in unknown layouts? | Container queries |
| Is this page-level structure? | Media queries |
| Is this device-specific behavior? | Media queries |
| Does component need self-awareness? | Container queries |
/* ================================
Modern CSS Reset
================================ */
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
margin: 0;
}
html {
color-scheme: light dark;
hanging-punctuation: first last;
}
body {
min-height: 100dvh;
line-height: 1.6;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
button {
all: unset;
cursor: pointer;
}
a {
color: inherit;
text-decoration: none;
}/* ================================
Design Tokens
================================ */
:root {
/* Colors */
--color-bg: #ffffff;
--color-text: #0f172a;
--color-muted: #64748b;
--color-primary: #2563eb;
--color-danger: #dc2626;
/* Spacing Scale */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 24px;
--space-6: 32px;
--space-8: 48px;
--space-10: 64px;
/* Border Radius */
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 16px;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.15);
/* Typography */
--font-sans: system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, Ubuntu, Cantarell, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco,
Consolas, monospace;
}
/* Dark Mode */
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #020617;
--color-text: #e5e7eb;
--color-muted: #94a3b8;
}
}/* ================================
Typography
================================ */
body {
font-family: var(--font-sans);
font-size: clamp(14px, 1vw + 0.5rem, 16px);
color: var(--color-text);
background-color: var(--color-bg);
}
h1 {
font-size: clamp(2rem, 4vw, 3rem);
line-height: 1.1;
font-weight: 700;
}
h2 {
font-size: clamp(1.5rem, 3vw, 2.25rem);
line-height: 1.2;
font-weight: 600;
}
h3 {
font-size: 1.25rem;
line-height: 1.3;
font-weight: 600;
}
p {
color: var(--color-muted);
max-width: 70ch;
line-height: 1.7;
}/* ================================
Layout Utilities
================================ */
.container {
width: min(1200px, 100% - 2rem);
margin-inline: auto;
}
.flex {
display: flex;
gap: var(--space-4);
}
.grid {
display: grid;
gap: var(--space-4);
}
.center {
display: grid;
place-items: center;
}
/* Stack: vertical spacing between elements */
.stack > * + * {
margin-top: var(--space-4);
}/* ================================
Components
================================ */
/* Card */
.card {
background: var(--color-bg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
padding: var(--space-5);
border: 1px solid rgba(0, 0, 0, 0.05);
}
/* Button */
.btn {
display: inline-flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
background: var(--color-primary);
color: white;
font-weight: 500;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn:active {
transform: translateY(0);
}
/* Input */
.input {
padding: var(--space-2) var(--space-3);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: var(--radius-md);
background: var(--color-bg);
color: var(--color-text);
}
.input:focus {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}/* ================================
Accessibility
================================ */
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Screen reader only */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}/* ================================
Performance Optimization
================================ */
/* Lazy-load sections */
.lazy-section {
content-visibility: auto;
contain-intrinsic-size: 600px;
}
/* Isolate components */
.isolated {
contain: layout paint;
}/* ================================
Interaction & Scroll
================================ */
html {
scroll-behavior: smooth;
}
.scroll-x {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
gap: var(--space-4);
padding: var(--space-4);
}
.scroll-x > * {
scroll-snap-align: center;
flex-shrink: 0;
}
body {
overscroll-behavior: contain;
}styles/
├── reset.css # CSS reset + defaults
├── tokens.css # Design system variables
├── typography.css # Font styles + scale
├── layout.css # Layout utilities
├── components/
│ ├── button.css
│ ├── card.css
│ ├── input.css
│ └── modal.css
├── utilities.css # Helper classes
└── main.css # Imports all above
For large applications, use @layer:
@layer reset, tokens, layout, components, utilities;
@import "reset.css" layer(reset);
@import "tokens.css" layer(tokens);
/* ... */Layout:
"I use Grid for two-dimensional layouts and Flexbox for one-dimensional. I rely on
gapinstead of margins, and useminmax()withauto-fitfor responsive grids."
Responsive Design:
"I use
clamp()for fluid typography to eliminate media query breakpoints,dvhinstead ofvhfor mobile-safe viewport heights, and container queries for component-level responsiveness."
Performance:
"I use
content-visibility: autofor long pages,containfor component isolation, and avoid animating layout-triggering properties—onlytransformandopacity."
Modern Selectors:
"
:has()lets me style parents based on children without JavaScript. I use:where()for zero-specificity utilities and:is()for grouped selectors."
Container Queries:
"Media queries handle page layout; container queries handle component layout. I use container queries for reusable components that appear in multiple contexts."
Q: "Should you use will-change on all animated elements?"
✅ Correct: "No. Overuse degrades performance. Only use after profiling identifies rendering bottlenecks."
Q: "Can container queries replace media queries?" ✅ Correct: "No, they complement each other. Media queries for viewport-level concerns, container queries for component-level."
Q: "Why not animate width and height?"
✅ Correct: "They trigger layout recalculation on every frame. Use transform: scale() instead for GPU-accelerated animations."
Q: "What's the difference between auto-fit and auto-fill?"
✅ Correct: "auto-fit collapses empty tracks and stretches items to fill space. auto-fill preserves empty tracks even with few items."
Q: "When should you use dvh vs vh?"
✅ Correct: "Always use dvh on mobile to account for dynamic browser UI. vh breaks when address bars appear/disappear."
❌ "I use !important to fix specificity issues"
✅ "I structure CSS with proper specificity hierarchy and use :where() for utilities"
❌ "I animate left and top for movement"
✅ "I use transform: translate() for performant animations"
❌ "Container queries replace media queries" ✅ "They complement each other—media queries for viewport, container queries for components"
❌ "I add will-change to everything that moves"
✅ "I use will-change sparingly after profiling identifies bottlenecks"
Why it matters: Explicit control over CSS cascade without specificity wars.
/* Define layer order upfront */
@layer reset, base, components, utilities;
/* Add styles to specific layers */
@layer reset {
* { margin: 0; padding: 0; }
}
@layer utilities {
.mt-4 { margin-top: 1rem; }
}How cascade works with layers:
- Unlayered styles (highest priority)
- Layers in declared order
- Specificity only matters within same layer
Real-world benefit:
/* Utility always wins, even with low specificity */
@layer components {
.button.primary.large { padding: 20px; }
}
@layer utilities {
.p-0 { padding: 0; } /* ✅ This wins */
}Why it matters: Grid items can inherit parent grid tracks.
.parent {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
gap: 20px;
}
.child {
display: grid;
grid-template-columns: subgrid; /* Inherits parent columns */
grid-column: span 3;
}Without subgrid (problems):
/* ❌ Child grid doesn't align with parent */
.child {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* Must manually match */
}Real-world use cases:
- Card grids with aligned content
- Form layouts
- Magazine-style layouts
- Dashboard widgets
Example: Card alignment
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}
/* All card titles, images, and buttons align across columns */
.card__image { grid-row: 1; }
.card__title { grid-row: 2; }
.card__button { grid-row: 3; }Why it matters: One-line form styling that respects system colors.
:root {
accent-color: #2563eb;
}
/* Now all native form controls use this color */
input[type="checkbox"] { /* automatically styled */ }
input[type="radio"] { /* automatically styled */ }
input[type="range"] { /* automatically styled */ }
progress { /* automatically styled */ }Before accent-color:
/* ❌ Required custom HTML + CSS for each control */
input[type="checkbox"] {
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #ccc;
/* ...50 more lines */
}After accent-color:
/* ✅ One line */
accent-color: #2563eb;Why it matters: Mix colors in CSS without preprocessors.
:root {
--color-primary: #2563eb;
}
.button {
background: var(--color-primary);
}
.button:hover {
/* Mix primary with white for lighter shade */
background: color-mix(in srgb, var(--color-primary) 80%, white);
}
.button:active {
/* Mix with black for darker shade */
background: color-mix(in srgb, var(--color-primary) 80%, black);
}Advanced patterns:
/* Semi-transparent overlays */
.overlay {
background: color-mix(in srgb, black 50%, transparent);
}
/* Theme variations */
.success {
--base: #10b981;
color: color-mix(in srgb, var(--base) 90%, black);
background: color-mix(in srgb, var(--base) 10%, white);
}Color spaces:
srgb→ Standard RGBhsl→ Hue, Saturation, Lightnessoklch→ Perceptually uniform (recommended for design systems)
Why it matters: Show focus only for keyboard navigation, not mouse clicks.
/* ❌ Old way: focus ring on mouse click too */
button:focus {
outline: 2px solid blue;
}
/* ✅ Modern way: focus ring only for keyboard */
button:focus-visible {
outline: 2px solid blue;
}Real-world pattern:
/* Remove default focus (only for mouse) */
button:focus {
outline: none;
}
/* Add custom focus (only for keyboard) */
button:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}Why it matters: Better text wrapping control.
/* Prevent orphans (single words on last line) */
h1 {
text-wrap: balance;
}
/* Prevent awkward line breaks */
p {
text-wrap: pretty;
}Comparison:
/* Default wrapping */
text-wrap: wrap;
/* Balance: more even line lengths */
text-wrap: balance; /* Best for headlines */
/* Pretty: avoids orphans */
text-wrap: pretty; /* Best for paragraphs */
/* Stable: doesn't reflow on edits */
text-wrap: stable; /* Best for editable text */Why it matters: Prevents layout shift when scrollbar appears.
html {
scrollbar-gutter: stable;
}Problem it solves:
Page with no scrollbar → 100% width
User adds content → scrollbar appears → width decreases → layout shifts
Solution:
/* Always reserve space for scrollbar */
scrollbar-gutter: stable;
/* Content width stays consistent */Why it matters: Fine-grained control over scroll chaining in specific directions.
/* Prevent vertical scroll chaining */
.modal {
overscroll-behavior-block: contain;
/* User can still horizontal scroll to parent */
}
/* Prevent horizontal scroll chaining */
.carousel {
overscroll-behavior-inline: contain;
}Why it matters: Shorthand for top, right, bottom, left.
/* ❌ Old way */
.overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
/* ✅ Modern way */
.overlay {
position: absolute;
inset: 0;
}
/* With different values */
.element {
inset: 10px 20px 30px 40px; /* top right bottom left */
inset: 10px 20px; /* vertical horizontal */
}
/* Logical properties */
.element {
inset-block: 0; /* top + bottom */
inset-inline: 20px; /* left + right */
}Why it matters: Control how images fill containers without distortion.
.avatar {
width: 100px;
height: 100px;
object-fit: cover; /* Crop to fill */
object-position: center top; /* Focus on top */
}
.logo {
width: 200px;
height: 100px;
object-fit: contain; /* Scale to fit, preserve ratio */
}object-fit values:
fill→ Stretch to fill (default, distorts)contain→ Scale to fit, preserve ratiocover→ Scale to fill, crop excessnone→ Don't resizescale-down→ Usenoneorcontain, whichever is smaller
Why it matters: Offset scroll position for fixed headers or padding.
/* Fixed header is 80px tall */
header {
position: fixed;
height: 80px;
}
/* Anchor links should scroll 80px before target */
section {
scroll-margin-top: 80px;
}
/* OR add padding to scroll container */
main {
scroll-padding-top: 80px;
}Use cases:
- Fixed navigation headers
- Sticky table headers
- Scroll snap with spacing
- Anchor link offsets
Why it matters: Dynamic, context-aware styling without JavaScript.
Pattern 1: Scoped Variables
.card {
--card-bg: white;
--card-padding: 1rem;
background: var(--card-bg);
padding: var(--card-padding);
}
.card.large {
--card-padding: 2rem; /* Override in context */
}Pattern 2: Computed Values
:root {
--spacing-unit: 8px;
--spacing-2: calc(var(--spacing-unit) * 2); /* 16px */
--spacing-3: calc(var(--spacing-unit) * 3); /* 24px */
}Pattern 3: Property Fallbacks
.element {
color: var(--theme-color, #2563eb); /* Fallback if not defined */
}Pattern 4: Type-safe Custom Properties with @property
@property --rotation {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.spinner {
--rotation: 45deg;
transform: rotate(var(--rotation));
}Every project should start with these:
/* ================================
Modern Defaults Checklist
================================ */
/* ✅ Box model fix */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* ✅ Remove default margins */
* {
margin: 0;
}
/* ✅ Mobile-safe viewport height */
body {
min-height: 100dvh;
}
/* ✅ Responsive images */
img {
max-width: 100%;
height: auto;
display: block;
}
/* ✅ Inherit fonts for form controls */
input,
button,
textarea,
select {
font: inherit;
}
/* ✅ Better line height */
body {
line-height: 1.6;
}
/* ✅ Smooth scrolling (respects user preference) */
html {
scroll-behavior: smooth;
}
/* ✅ Focus visible only for keyboard */
:focus:not(:focus-visible) {
outline: none;
}
:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
/* ✅ Prevent scroll chaining */
body {
overscroll-behavior: contain;
}
/* ✅ Accessibility: reduced motion */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* ✅ Text rendering */
body {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}/* Block */
.card { }
/* Element */
.card__title { }
.card__image { }
/* Modifier */
.card--featured { }
.card__title--large { }/* Spacing utilities */
.mt-4 { margin-top: 1rem; }
.p-6 { padding: 1.5rem; }
/* Layout utilities */
.flex { display: flex; }
.grid { display: grid; }
/* Responsive utilities */
@media (min-width: 768px) {
.md\:flex-row { flex-direction: row; }
}/* Component styles are scoped */
.container {
/* Only affects this component */
}
/* Global styles */
:global(.utility) {
/* Available everywhere */
}- Animate only
transformandopacity - Use
content-visibility: autofor long lists - Use
containfor independent components - Minimize layout thrashing (batch DOM reads/writes)
- Use
will-changesparingly (remove after animation) - Load critical CSS inline, defer non-critical
- Use CSS Grid/Flexbox instead of floats
- Compress/minify CSS in production
- Animate
width,height,top,left,margin - Overuse
will-change(memory leak) - Use
*selector with complex rules - Use deeply nested selectors (
.a .b .c .d .e) - Import large unused CSS libraries
- Use
@importin production (blocks rendering) - Trigger layout thrashing with alternating read/write
✅ Flexbox
✅ Grid
✅ CSS Variables
✅ clamp()
✅ gap
✅ aspect-ratio
✅ :is(), :where()
✅ dvh, svh, lvh
✅ :has() (Safari 15.4+, Chrome 105+, Firefox 121+)
✅ Container queries (All modern browsers 2023+)
✅ accent-color
✅ color-mix()
✅ text-wrap: balance
@property (Chromium only, fallback needed)
subgrid (Firefox full support, Safari/Chrome improving)
color-mix()
Fallback strategy:
/* Fallback */
background: #2563eb;
/* Enhanced */
background: color-mix(in oklch, var(--primary) 80%, white);| Scenario | Use |
|---|---|
| One-dimensional (row or column) | Flexbox |
| Two-dimensional (rows AND columns) | Grid |
| Items wrap at viewport breakpoints | Grid + auto-fit |
| Items adapt to own width | Container queries + Flexbox |
| Complex magazine layout | Grid + grid-template-areas |
| Centered content | Grid + place-items: center |
| Goal | Use |
|---|---|
| Fluid font size | clamp() |
| Mobile-safe full height | 100dvh |
| Maintain aspect ratio | aspect-ratio |
| Component-aware layout | Container queries |
| Page-level layout | Media queries |
| Device capability detection | @media (hover), @media (pointer) |
| Property | Performance | Alternative |
|---|---|---|
transform |
✅ Excellent (GPU) | - |
opacity |
✅ Excellent (GPU) | - |
width, height |
❌ Poor (layout) | transform: scale() |
top, left |
❌ Poor (layout) | transform: translate() |
margin, padding |
❌ Poor (layout) | transform |
background-color |
opacity if possible |
When asked: "Tell me about your CSS approach"
"I structure CSS with modern layout systems—Grid for two-dimensional layouts and Flexbox for one-dimensional. I use
clamp()for fluid typography, eliminating many breakpoints, anddvhunits for mobile-safe viewport heights.For reusable components, I use container queries so they adapt based on their own width, not the viewport. This is crucial for design systems.
I prioritize performance by using
content-visibility: autofor long pages,containfor component isolation, and animating onlytransformandopacityfor 60fps animations.Modern selectors like
:has()eliminate parent-state JavaScript hacks, and I use:where()for zero-specificity utilities in design systems.I always implement
prefers-reduced-motionfor accessibility, and structure larger projects with@layerto manage cascade complexity."
For Mid-Level → Senior:
- Container queries (deep understanding)
@layercascade management- Performance profiling (Chrome DevTools)
- CSS-in-JS tradeoffs
- Design system architecture
For Senior → Staff:
- CSS Houdini APIs
- Advanced Grid patterns (subgrid)
- Color science (
oklch, perceptual uniformity) - CSS build optimization
- Framework-specific CSS patterns (React, Vue, Svelte)
Official Documentation:
- MDN Web Docs: https://developer.mozilla.org
- CSS Spec: https://www.w3.org/Style/CSS/
Learning:
- CSS Tricks: https://css-tricks.com
- Smashing Magazine: https://www.smashingmagazine.com
Tools:
- Can I Use: https://caniuse.com
- CSS Stats: https://cssstats.com
Communities:
- Frontend Masters
- CSS-Tricks Forums
- Stack Overflow (CSS tag)
If you only remember 20% of this guide, remember:
- Grid for 2D, Flexbox for 1D
clamp()for fluid typographydvhinstead ofvh- Container queries for components, media queries for pages
:has()for parent selectors- Animate only
transformandopacity content-visibility: autofor performance- Always implement
prefers-reduced-motion
These eight concepts will get you through 80% of modern CSS challenges and interviews.
End of Guide