CSS Interview Questions – Practice Quiz for Frontend Developers
Reviewed by Mark Dickie · Last updated
CSS is a stylesheet language that controls the visual presentation of HTML documents — how elements are sized, spaced, coloured, and arranged on screen. To do well in a CSS interview you need a solid grip on the cascade and specificity rules, the box model, the two main layout systems (Flexbox and Grid), and how the browser paints and repaints. Interviewers at all levels will probe whether you understand why a rule wins, not just that it does. Knowing common gotchas — stacking contexts, margin collapse, and specificity wars — separates candidates who have read the docs from those who have debugged real layouts.
What the cascade actually decides
The browser resolves style conflicts in a fixed order. Get this sequence wrong in an interview and everything downstream looks shaky:
- Origin and importance — browser defaults lose to author styles;
!importantflips the order within each origin. - Specificity — inline styles beat IDs, IDs beat classes, classes beat element selectors.
- Source order — when specificity ties, the rule that appears later in the stylesheet wins.
- Inheritance — some properties (mostly text-related) flow down to child elements automatically; others don't.
Core topic areas at a glance
The table below maps each topic area to what an interviewer is typically probing with it.
| Topic | What the interviewer is checking |
|---|---|
| Box model (content-box vs border-box) | Whether you know why box-sizing: border-box is set globally in most resets |
| Specificity & the cascade | That you can predict which rule wins without guessing |
| Flexbox | One-dimensional alignment — rows or columns, flex-grow, align-items |
| Grid | Two-dimensional layout — grid-template-areas, implicit vs explicit tracks |
| Stacking contexts | Whether you understand what actually controls z-index layering |
| Pseudo-elements vs pseudo-classes | Syntactic distinction (::before vs :hover) and practical use cases |
| Custom properties (variables) | Scope, inheritance, and how they differ from preprocessor variables |
| Media & container queries | Responsive design, and the newer shift toward component-level breakpoints |
| Paint & composite layers | Performance awareness — what triggers layout reflow vs a cheap composite |
How to prepare across difficulty levels
Questions on this page range from straightforward syntax recall up to tricky layout debugging. A good study arc looks like this:
- Lock in the fundamentals first. Be able to draw the box model from memory and explain the cascade steps without prompting.
- Build small layout demos. Write a Flexbox nav bar and a Grid card layout by hand — interviewers often ask you to produce code, not just describe it.
- Read the spec for the hard bits. Stacking contexts and margin collapse are under-explained in tutorials; MDN's formal descriptions are worth the effort.
- Practice explaining out loud. CSS concepts that feel obvious on screen get fuzzy when you have to justify them verbally under interview pressure.
At a glance
| Questions | 15 |
|---|---|
| Difficulty | 1–4 of 5 |
| Formats | Multiple choice, True / false, Short answer, Flashcard, Fill in the blank, Multiple answer |
What you'll review
- flexbox
- grid
- positioning
- box model
- units
- pseudo classes
- cascade specificity
- css variables
- media queries
- transitions
- selectors
- stacking contexts
- formatting contexts
- centering
- render pipeline
Practice questions
CSS/layout/flexbox
In a flex container with flex-direction: column, which property positions items along the main (vertical) axis?
Options
align-itemsjustify-contentalign-contentplace-items
Show answer
justify-content positions items along the main axis. With flex-direction: column the main axis runs vertically, so justify-content controls vertical placement while align-items handles the horizontal cross axis. This is the reverse of the default row direction, where the axes are swapped.
justify-content always aligns items along the main axis. With flex-direction: column the main axis is vertical, so it controls vertical placement; align-items then controls the horizontal (cross) axis. The axes swap compared to the default row.
CSS/layout/grid
Given grid-template-columns: 1fr 2fr 1fr in a 800px-wide grid (no gaps), how wide is the middle column?
Options
- 200px
- 267px
- 400px
- 266.67px
Show answer
The middle column is 400px wide. The fr unit divides available space by ratio: the tracks total 1 + 2 + 1 = 4 fractions, so each fr equals 800 / 4 = 200px. The 2fr middle column gets two of those fractions, 2 × 200 = 400px.
The fr unit splits the available space by ratio. The tracks total 1 + 2 + 1 = 4 fractions, so each fr is 800 / 4 = 200px. The 2fr middle column is 2 × 200 = 400px.
CSS/layout/positioning
An element with position: absolute is positioned relative to which ancestor?
Options
- Its direct parent element, always
- The nearest ancestor with a
positionother thanstatic - The
<body>element, always - The viewport, always
Show answer
An position: absolute element is positioned relative to its containing block: the nearest ancestor whose position is anything other than static (relative, absolute, fixed, or sticky). If no such ancestor exists, it falls back to the viewport. It is position: fixed, not absolute, that always anchors to the viewport.
An absolutely positioned element is offset from its containing block: the nearest ancestor whose position is relative, absolute, fixed, or sticky. If no such ancestor exists, it falls back to the initial containing block (the viewport). position: fixed (not absolute) is what is always relative to the viewport.
CSS/layout/box-model
With box-sizing: border-box, a width: 200px padding: 20px border: 5px solid element renders how wide on screen?
Options
- 200px
- 240px
- 250px
- 245px
Show answer
The element renders 200px wide. With box-sizing: border-box, the declared width includes padding and border rather than adding to them, so 200px is the full rendered width and the padding and border eat into it. Under the default content-box, the same element would be 200 + 2×20 + 2×5 = 250px.
border-box makes width include the content box plus padding and border. The declared 200px is the full rendered width; padding and border eat into it. Under the default content-box, the same element would be 200 + 2×20 + 2×5 = 250px wide.
CSS/responsive/units
The rem unit is relative to the font size of which element?
Options
- The element itself
- The element's parent
- The root (
<html>) element - The viewport
Show answer
The rem unit is relative to the root (<html>) element's font size. Because it always references the root, rem values do not compound through nesting. This contrasts with em, which is relative to the current element's own font size and so multiplies when elements are nested.
rem means "root em" — it is always relative to the root (<html>) font size, so it does not compound through nesting. em, by contrast, is relative to the current element's own font size (and for most other properties, the parent's), which is what causes em-based sizes to multiply when nested.
CSS/styling/pseudo-classes
Which selector correctly styles generated content inserted before an element's content?
Options
.box:beforeonly.box::before(the modern syntax).box:first-child.box > before
Show answer
Use .box::before, the modern double-colon syntax. The double colon marks pseudo-elements and distinguishes them from pseudo-classes like :hover. The single-colon :before is the legacy CSS2 form kept only for backward compatibility. Either way, generated content needs a content property to render.
::before is a pseudo-element and takes the double-colon syntax that distinguishes pseudo-elements from pseudo-classes (:hover). The single-colon :before is the legacy CSS2 form kept only for backward compatibility. It still requires a content property to render anything.
CSS/selectors-cascade/cascade-specificity
A single ID selector (#nav) has higher specificity than a selector made of ten classes (.a.b.c.d.e.f.g.h.i.j).
Show answer
True. A single ID outweighs any number of classes because specificity compares the (id, class, type) columns left to right, and a higher column always wins regardless of the lower ones. One ID gives (1, 0, 0) while ten classes give (0, 10, 0) — the ID column is compared first, and class count never carries up into it.
Specificity compares the (id, class, type) columns left to right; a higher column always wins regardless of the lower ones. One ID gives (1, 0, 0) while ten classes give (0, 10, 0). Because the ID column is compared first and 1 > 0, the ID wins — the class count can never carry into the ID column.
CSS/css-architecture/css-variables
What are CSS custom properties (variables), and how do they differ from a preprocessor variable like Sass's $color?
Show answer
A custom property is a --name declaration read with var(--name). Unlike a Sass variable, which is resolved once at compile time and then disappears, a custom property is live in the browser: it inherits down the DOM, can be overridden per selector or scope, read and changed at runtime with JavaScript, and respond to media queries — so one value can cascade and update dynamically.
Custom properties (--name / var(--name)) are part of the cascade: they inherit, can be scoped to any selector, and are resolved live in the browser, so they can be changed at runtime via JavaScript or media queries. Sass variables are static — substituted at build time and gone from the output CSS.
CSS/responsive/media-queries
In a mobile-first stylesheet, does @media (min-width: 768px) { ... } apply on screens narrower or wider than 768px?
Show answer
Wider (or equal). min-width is a floor: the rules apply when the viewport is at least 768px. Mobile-first stylesheets write the base styles for small screens, then layer on min-width queries to enhance progressively larger viewports.
min-width sets a minimum threshold, so the query matches viewports that are that wide or wider. This is the mobile-first pattern: small-screen styles are the default and min-width breakpoints add to them as the screen grows.
CSS/styling/transitions
In transition: 1s 2s, which time is the duration and which is the delay?
Show answer
The first time is the duration (1s) and the second is the delay (2s). In the transition shorthand, the first time value is always transition-duration and the second is transition-delay.
When the transition shorthand has two time values, order disambiguates them: the first is the duration and the second is the delay. So transition: 1s 2s waits 2s, then animates over 1s.
CSS/selectors-cascade/selectors
The child combinator div _____ p matches a <p> only when it is a direct child of a <div>, while the attribute selector a[_____] matches any anchor that has an href.
Show answer
The child combinator div **>** p matches a <p> only when it is a direct child of a <div>, while the attribute selector a[**href**] matches any anchor that has an href.
> is the child combinator: div > p targets only <p> elements that are immediate children of a <div> (a plain space would match any descendant). a[href] is an attribute selector matching every <a> that has an href attribute, regardless of its value.
CSS/layout/stacking-contexts
A dropdown menu renders behind a page overlay even though its z-index is far higher: <div class="toolbar"> <div class="menu">…</div> </div> <div class="overlay"></div> .toolbar { transform: translateZ(0); } .menu { position: relative; z-index: 9999; } .overlay { position: fixed; inset: 0; z-index: 10; } Why is the menu behind the overlay, and where is the fix?
Options
- The overlay's
z-index: 10needs!importantto be beaten; addz-index: 9999 !importantto.menu 9999exceeds the maximumz-indexbrowsers support, so the value is ignored; use a smaller number- The
transformon.toolbarcreates a stacking context, so.menu'sz-indexonly competes among.toolbar's descendants; fix it at the toolbar level — make.toolbarpositioned with az-indexabove 10 (e.g.position: relative; z-index: 11) or remove the transform .menumust useposition: fixedlike the overlay, because elements can only be z-ordered against others with the samepositionvalue
Show answer
The transform on .toolbar (even a no-op translateZ(0)) creates a stacking context, so .menu's z-index: 9999 only competes among .toolbar's descendants — never against .overlay. The fix belongs on the ancestor: make .toolbar positioned with a z-index above 10 (e.g. position: relative; z-index: 11), or remove the transform so no context forms.
Any transform (even a no-op like translateZ(0)) makes .toolbar establish a stacking context. Inside one, descendants' z-index values are resolved locally: .menu's 9999 ranks it against other children of .toolbar, not against .overlay. Against the overlay, what matters is .toolbar's own stacking level — and with no z-index it sits below the overlay's 10, dragging the whole subtree (menu included) behind it. No child value can escape, so the fix lives on the ancestor: position .toolbar (e.g. position: relative) and give it a z-index above 10 — z-index itself only applies to a positioned element, so setting it on the static transformed toolbar would do nothing — or remove the transform so no context is created. !important is irrelevant (there is no specificity conflict), z-index accepts values far beyond 9999 (up to ~2147483647), and elements with different position values are ordered against each other all the time.
CSS/layout/formatting-contexts
A card's background disappears because every child is floated: <div class="card"> <img class="thumb" src="cover.png" /> <div class="meta">…</div> </div> .thumb, .meta { float: left; } .card { background: #eee; /* renders 0px tall */ } Adding overflow: hidden (or display: flow-root) to .card makes it wrap its children again. Why does that fix the collapsed height?
Options
overflow: hiddenclips the floated children back inside the parent's box, which forces the box to grow around them- Any
overflowvalue other thanvisibledisablesfloaton the children, returning them to normal flow - It makes
.cardestablish a new block formatting context, and a BFC contains its floats — its height accounts for them - It triggers a repaint that recalculates the parent's height to include floated descendants
Show answer
overflow: hidden makes .card establish a new block formatting context, and a BFC root contains its floats — so its height is computed to include them, rather than collapsing to 0. The clipping is just a side effect, not the mechanism, which is why display: flow-root (a BFC with no clipping) fixes the collapsed height identically.
Floated elements are taken out of normal flow, so a parent whose children are all floated computes a height of 0 — the classic collapsed-parent problem. overflow: hidden (any value other than visible) makes the parent establish a new block formatting context, and a BFC root contains its floats: its height is computed to include them. The clipping is a side effect, not the mechanism — which is why display: flow-root, which creates a BFC with no clipping or scrollbars, fixes it identically. Floating stays fully in effect, and no repaint magic is involved.
CSS/layout/centering
Which of these reliably center a child element both horizontally and vertically inside its parent?
Options
text-align: center; vertical-align: middleon a block parentdisplay: flex; justify-content: center; align-items: centeron the parentmargin: autoon a plain block child in normal flowdisplay: grid; place-items: centeron the parentposition: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)on the child, with the parent positioned
Show answer
Three approaches center on both axes: display: flex with justify-content: center and align-items: center; display: grid with place-items: center; and the absolute recipe of top: 50%; left: 50%; transform: translate(-50%, -50%) on the child (the parent must be positioned). text-align/vertical-align fails vertically, and margin: auto only centers horizontally.
Flexbox (justify-content for the main axis, align-items for the cross axis) and grid (place-items: center is the align-items/justify-items shorthand) both center on both axes with one rule on the parent. The absolute-positioning recipe also works: top: 50%; left: 50% moves the child's top-left corner to the parent's center, and translate(-50%, -50%) pulls it back by half its own size — but the parent must be positioned (e.g. position: relative), or the child centers in some further-up positioned ancestor instead. The other two fail vertically: vertical-align only affects inline-level and table-cell content, not a block child, and in normal flow margin: auto centers horizontally while the vertical auto margins resolve to 0.
CSS/rendering/render-pipeline
On a 60fps animation budget, changing width or top every frame janks badly, changing background-color or box-shadow is lighter but still costs, and animating transform or opacity stays smooth. After the browser recalculates style, a frame still passes through a sequence of rendering stages before pixels reach the screen. Name those stages in order, describe what each one does, and map each property group above to the stages it forces the browser to re-run. Why are transform and opacity (on an element with its own layer) the only properties you can animate cheaply?
Show answer
After style recalculation a frame flows through three stages: layout, paint, and composite. Layout computes geometry — where every box sits and how big it is, with one element's size able to push every other element around (this is the "reflow" work). Paint rasterizes pixels into layers: filling in colors, text, borders, shadows. Composite is the final assembly step where the compositor thread, largely on the GPU, takes the already-painted layers and stacks them together with their transforms and opacities applied. The cost of a property change is determined by the earliest stage it dirties, because every later stage must re-run too: width, top, or font-size change geometry, so they trigger layout + paint + composite — the most expensive path. background-color, color, or box-shadow change pixels but not geometry, so they skip layout and trigger paint + composite. transform and opacity on an element with its own compositor layer change neither geometry nor the layer's pixels — the GPU just re-blends or repositions a texture it already has — so they hit composite only. That is precisely why transform and opacity are the properties to animate for 60fps: each frame costs a cheap compositor pass instead of a full re-layout or repaint.
The pipeline after style is layout → paint → composite, and the rule that makes it useful is: a property change re-runs the pipeline from the earliest stage it invalidates. Layout (reflow) is geometry — positions and sizes — and it cascades: resizing one box can move everything below it, so geometric properties like width, top, margin, and font-size are the most expensive to animate, costing layout + paint + composite every frame. Paint-only properties like background-color, color, and box-shadow leave geometry untouched but force the affected layers to be re-rasterized, costing paint + composite. transform and opacity are special-cased by design: when the element has its own compositor layer (promoted via will-change: transform or by being animated), the GPU already holds its painted texture, and moving, scaling, or fading that texture is pure compositor work — no layout, no repaint, often not even main-thread work. That's the entire basis of the "only animate transform and opacity" rule: a 16ms frame budget survives a compositor pass but rarely survives per-frame reflow. The classic interview follow-up — animate left vs transform: translateX — is answered entirely by which stage each one dirties.
Sources
The official documentation these questions are checked against:
Related interview questions
Practice this for real
CodePrep turns your target job description into an adaptive quiz from a bank of tagged questions, scores your answers, and resurfaces the topics you miss.