frontend / css / 04_modern_css_and_perf.md

Logical Properties, Modern CSS, and Performance Hints

4 min read source

Logical Properties, Modern CSS, and Performance Hints

TL;DR

A grab-bag of modern CSS that comes up at senior level: logical properties (margin-inline, inset-block-start) that adapt to writing direction (RTL/vertical) for free; aspect-ratio to reserve space (CLS); scroll-snap for carousels; and the performance levers — content-visibility (skip rendering offscreen content), contain (isolate a subtree), and will-change (promote to a layer, used sparingly). Plus the two preference-driven features every senior wires up: dark mode (prefers-color-scheme + color-scheme) and prefers-reduced-motion.

Interview Q&A

Q: What are logical properties and why use them?

A: Flow-relative equivalents of physical properties — they follow the writing direction instead of fixed sides:

Physical Logical
margin-left/right margin-inline-start/end (or margin-inline)
margin-top/bottom margin-block-start/end (or margin-block)
top/left (inset) inset-block-start / inset-inline-start
width/height inline-size / block-size
border-top-left-radius border-start-start-radius

In an RTL locale, margin-inline-start automatically becomes the right side — so one stylesheet works for LTR and RTL without flipping. Essential for internationalized apps; good default even for LTR-only.

Q: What does aspect-ratio do?

A: Sizes a box to a ratio so space is reserved before content loads — preventing layout shift (CLS):

.video { aspect-ratio: 16 / 9; width: 100%; }   /* height derived; no jump when the iframe loads */
img { aspect-ratio: 3 / 2; width: 100%; height: auto; }

Pair with width/height attributes on <img> for the best result (../01_html/02_responsive_images.md).

Q: How does scroll-snap work?

A: Declarative snapping for carousels/galleries — no JS:

.carousel { scroll-snap-type: x mandatory; overflow-x: auto; display: flex; }
.slide    { scroll-snap-align: start; flex: 0 0 100%; }

mandatory always snaps; proximity snaps only when close. scroll-driven animations (animation-timeline: view()/scroll()) extend this to tie animation progress to scroll position, also without JS.

Q: What are content-visibility and contain?

A: Performance hints that limit how much the browser renders:

  • content-visibility: auto — skips rendering (layout/paint) of offscreen elements until they’re near the viewport, with contain-intrinsic-size to reserve approximate space. Massive win for long lists/articles — the browser does layout only for what’s visible.
    .post { content-visibility: auto; contain-intrinsic-size: auto 500px; }
  • contain: layout/paint/size/style — promises the browser a subtree is independent, so changes inside don’t reflow/repaint the rest. contain: content is a common bundle. Reduces the scope of layout work.

Both improve rendering performance on large pages — see ../15_performance/.

Q: When should you use will-change?

A: Sparingly. will-change: transform hints the browser to promote an element to its own compositor layer before an animation, avoiding a first-frame jank. But it costs memory, creates a stacking context, and overuse (or leaving it on permanently) hurts performance. Apply it just before animating and remove it after; never put will-change on many elements “to be safe.” Prefer animating only transform/opacity (compositor-only, no layout/paint).

Q: How do you implement dark mode in modern CSS?

A: CSS variables + prefers-color-scheme, with color-scheme so form controls/scrollbars adapt too:

:root { color-scheme: light dark; --bg: white; --fg: #111; }
@media (prefers-color-scheme: dark) { :root { --bg: #0b0b0b; --fg: #f5f5f5; } }
[data-theme="dark"] { --bg: #0b0b0b; --fg: #f5f5f5; }   /* explicit override */
body { background: var(--bg); color: var(--fg); }

Default from the OS preference, allow an explicit toggle to override, and set color-scheme so native UI (inputs, scrollbars) matches. Token details: ../08_component_libraries/design_tokens_and_theming.md.

Q: What is prefers-reduced-motion and why honor it?

A: A media query for users who’ve asked the OS to reduce motion (vestibular disorders, distraction). Gate non-essential animation behind it (WCAG 2.3.3):

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

Reduce or remove large transforms/parallax; keep essential motion subtle.

Gotchas / edge cases

  • content-visibility: auto without contain-intrinsic-size makes the scrollbar jump as elements render — always provide an intrinsic size estimate.
  • will-change left on permanently is a known anti-pattern — wastes memory and can reduce performance; add/remove around the animation.
  • Animating width/height/top/left triggers layout every frame (jank); animate transform/opacity instead.
  • Mixing logical and physical properties for the same side can conflict; commit to logical for i18n.
  • Dark mode flash (FOUC) — set the theme before first paint via an inline script; don’t wait for React hydration (../19_rendering_modes/).
  • aspect-ratio with explicit height — if both are set and conflict, height wins and the ratio is ignored; let one dimension be derived.

What a senior is expected to say

  • “Logical properties (margin-inline, inset-block-start) adapt to writing direction — one stylesheet for LTR/RTL. aspect-ratio reserves space to avoid CLS.”
  • content-visibility: auto (with contain-intrinsic-size) skips offscreen rendering — big win on long pages; contain scopes layout/paint. I animate only transform/opacity.”
  • will-change is a sparing, temporary hint — leaving it on costs memory and backfires.”
  • “Dark mode via CSS vars + prefers-color-scheme + color-scheme, with a pre-paint script to avoid the flash; and I always honor prefers-reduced-motion.”

Cross-references

Further reading