What are CSS custom properties, and how are they different from Sass variables?
Custom properties (
--brand: #2563eb;, read withvar(--brand)) live in the browser: they cascade, inherit and can change at runtime. Sass variables become fixed values at build time.HTML<style> :root { --bg: #ffffff; --text: #111827; --accent: #2563eb; } .dark { --bg: #111827; --text: #f9fafb; --accent: #93c5fd; } .card { background: var(--bg); color: var(--text); border: 2px solid var(--accent); padding: 10px; margin-bottom: 8px; border-radius: 8px; } .card b { color: var(--accent); } </style> <div class="card">Default theme, <b>accent</b></div> <div class="dark"><div class="card">Same rule inside .dark, <b>accent</b></div></div>Redefine them on a parent and everything below changes, which is how themes work; JavaScript can set them with
element.style.setProperty(). More in CSS variables.How would you implement dark mode in CSS?
Put every color in custom properties, define a dark set in
@media (prefers-color-scheme: dark), and setcolor-schemeso scrollbars and form controls darken too. Adata-themeattribute lets a manual toggle override the system setting.:root { color-scheme: light dark; --bg: #ffffff; --text: #111827; } @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --bg: #0f172a; --text: #e5e7eb; } } :root[data-theme="dark"] { color-scheme: dark; --bg: #0f172a; --text: #e5e7eb; } :root[data-theme="light"] { color-scheme: light; } body { background: var(--bg); color: var(--text); }Apply the saved choice from a small inline script in the
head, before first paint, or the page flashes light first.What is a block formatting context (BFC)?
An isolated layout region: floats inside stay inside, floats outside do not overlap it, and margins inside it do not collapse with margins outside it. Floats, absolute elements,
inline-block, table cells,overflowother thanvisibleorclip, flex and grid items, anddisplay: flow-rootcreate one.HTML<style> .float { float: left; width: 90px; height: 90px; background: #f97316; margin-right: 10px; } .text { background: #dbeafe; padding: 6px; margin-bottom: 8px; } .bfc { display: flow-root; } </style> <div class="float"></div> <div class="text">Normal block: its background runs under the float, only the text wraps.</div> <div style="clear: both"></div> <div class="float"></div> <div class="text bfc">New BFC: the whole box sits beside the float.</div>flow-rootexists only to create a BFC;overflow: hiddenalso clips.What is the difference between reflow and repaint, and what is layout thrashing?
Reflow (layout) recalculates sizes and positions; repaint redraws pixels without changing geometry.
widthtriggers both,coloronly a repaint,transformandopacitycan skip both. Layout thrashing is JavaScript alternating writes and reads, which forces a layout on every read:const items = [...document.querySelectorAll('.item')]; // Slow: every offsetHeight read forces layout after the previous write for (const el of items) { el.style.height = el.offsetHeight + 10 + 'px'; } // Fast: read everything first, then write const heights = items.map((el) => el.offsetHeight); items.forEach((el, i) => { el.style.height = heights[i] + 10 + 'px'; });Layout-forcing reads include
offsetWidth,getBoundingClientRect()andscrollTop.What are cascade layers (@layer) and what problem do they solve?
@layergroups styles into named layers whose order you declare once. A later layer beats an earlier one regardless of specificity, so a plain class incomponentsoverrides a long selector invendor.@layer reset, vendor, components, utilities; @layer vendor { .widget .header nav ul li a { color: gray; } /* high specificity */ } @layer components { .nav-link { color: navy; } /* wins: later layer */ }Unlayered styles beat every layer, and for
!importantdeclarations the order is reversed.How do you organize CSS in a large project: BEM, CSS Modules, utility classes or CSS-in-JS?
Pick by team and stack; all four fight global class names that collide. BEM is a naming convention with no tooling; CSS Modules rename classes per file at build time; Tailwind replaces custom class names with long utility lists; CSS-in-JS gives dynamic styles at a runtime cost. Whatever you pick, keep design tokens in custom properties, specificity low and flat, and no styling by id.
Why is CSS render-blocking, and how do you load it faster?
The browser will not paint until every stylesheet in the
headthat matches the current media has loaded, or it would flash unstyled content. To cut the delay, inline the critical CSS for the first screen, give non-matching sheets amediaattribute (media="print"does not block), remove unused CSS, and avoid@import, which chains requests.What are CSS logical properties and why do they matter?
They name sides by writing direction instead of physical direction:
margin-inline-startinstead ofmargin-left,padding-blockinstead of top and bottom,inset-inline-endinstead ofright. In right-to-left languages like Arabic or Hebrew they flip automatically, so you skip a second set of[dir="rtl"]rules.HTML<style> .note { border-inline-start: 4px solid #2563eb; padding-inline-start: 10px; margin-block: 8px; background: #f3f4f6; } </style> <p class="note" dir="ltr">Left to right: the border is on the left.</p> <p class="note" dir="rtl">من اليمين إلى اليسار: الحد على اليمين.</p>What is the difference between :focus and :focus-visible?
:focusmatches whenever an element has focus, including after a mouse click (except in Safari, which by design does not focus a button on click);:focus-visiblematches only when the browser decides focus should show, typically during keyboard navigation.HTML<style> button { padding: 8px 14px; margin: 4px; border: 1px solid #9ca3af; border-radius: 6px; background: white; } .a:focus { outline: 3px solid crimson; } .b:focus { outline: none; } .b:focus-visible { outline: 3px solid #2563eb; outline-offset: 2px; } </style> <button class="a">:focus (click me)</button> <button class="b">:focus-visible (click, then press Tab)</button>The anti-pattern is
*:focus { outline: none; }with no replacement.How do you keep an element at a fixed aspect ratio?
Set one dimension and the
aspect-ratioproperty; the browser computes the other.HTML<style> .row { display: flex; gap: 10px; align-items: flex-start; } .video { width: 200px; aspect-ratio: 16 / 9; background: #2563eb; color: white; display: grid; place-items: center; } .square { width: 90px; aspect-ratio: 1; background: #f97316; color: white; display: grid; place-items: center; } </style> <div class="row"><div class="video">16 / 9</div><div class="square">1 / 1</div></div>Follow-up: why did the old
padding-top: 56.25%hack work? Vertical padding percentages are calculated from the containing block's width.Do you still need Sass now that CSS has variables and nesting?
Less than before. CSS now has custom properties, nesting,
calc(),color-mix()and cascade layers, and nesting works in Chrome 120+, Firefox 117+ and Safari 17.2+..card { padding: 1rem; & h2 { margin: 0; } &:hover { box-shadow: 0 2px 8px rgb(0 0 0 / 20%); } @media (min-width: 640px) { padding: 2rem; } }Sass still adds mixins, loops and partials. New projects can often skip it; existing Sass codebases have no urgent reason to migrate.
How do you debug a CSS rule that is not being applied?
In DevTools, read the element's Styles pane: matching rules in cascade order, overridden ones struck through, invalid ones flagged. Then check:
- The selector does not match (a typo, a class added later by JavaScript).
- Another rule wins on specificity, layer or order.
- The value is invalid, so it is dropped (
colour). - The property does nothing here (
z-indexon a static element). - The stylesheet did not load (Network tab).
Advanced CSS Interview Questions
Theming, formatting contexts, rendering performance, cascade layers and architecture for mid-level and senior developers.