Intermediate to senior

Frontend Interview Prep

Fourteen chapters on HTML and CSS, core JavaScript, the event loop, browser rendering, React, state, performance, accessibility, security, TypeScript, testing, machine-coding components and frontend system design, with tested code.

Chapter 2 of 14Foundations · HTML, CSS, Layout and Responsive Design

HTML, CSS, Layout and Responsive Design

Even React-heavy interviews return to CSS. Expect questions on the box model, specificity, positioning, flexbox, grid, centring and responsive design, and often a live "build this layout" task. This chapter gives you the mental models that make these answers reliable rather than trial and error.

1. Semantic HTML

Use elements for their meaning, not their looks.

NeedUseNot
Navigation, main content, sidebar, footer<nav>, <main>, <aside>, <footer>a stack of <div>s
Headings in order<h1> to <h6>styled <div>s or skipped levels
An action<button type="button"><div onclick>
Navigation to a URL<a href><span> with a script
Lists<ul>, <ol>, <li>line-broken text
Form fields<label for> plus <input>placeholder text as the label
Tabular data<table>, <th scope>grids of <div>s
Images<img alt>background image for content

Semantics give you keyboard support, focus behaviour, screen-reader meaning, search-engine understanding and default styling for free. A <button> is focusable, activates with Enter and Space, and announces itself as a button. A <div> does none of that until you rebuild it.

2. The box model

Every element is a box: content, then padding, border and margin.

  • box-sizing: content-box (the default): width sets the content width; padding and border add to it.
  • box-sizing: border-box: width includes padding and border. This is what almost everyone wants, so most projects set it globally:
*, *::before, *::after { box-sizing: border-box; }

Margin collapsing: the vertical margins of adjacent block siblings, and of a parent and its first or last child (when nothing separates them), combine into the larger of the two rather than adding. Horizontal margins never collapse, and margins inside flex and grid containers do not collapse.

Block, inline and inline-block: block elements take the full line and respect width and height; inline elements flow within text and ignore width, height and vertical margins; inline-block flows like inline but accepts a size.

3. Specificity and the cascade

When rules conflict, the browser decides by:

  1. Importance (!important wins, apart from some origin rules).
  2. Specificity, counted as (inline style, IDs, classes/attributes/pseudo-classes, elements/pseudo-elements).
  3. Order: the later rule wins on a tie.
SelectorSpecificity (a, b, c)
p0, 0, 1
.card0, 1, 0
.card p0, 1, 1
#nav .item1, 1, 0
[type="text"]0, 1, 0
:where(.a, .b)0, 0, 0 (zero by design)

The comparison is by column, so ten classes never beat one ID. Keep specificity low and flat: use classes, avoid IDs for styling, avoid !important, and use cascade layers (@layer) to control order explicitly. Inheritance: some properties (color, font-*, line-height) pass from parent to child; others (margin, border, background) do not.

The specificity comparison can be written as a function, which is a nice way to check your understanding:

// specificity as a [ids, classes, elements] triple, compared left to right
const cmp = (a, b) => {
  for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i] ? 1 : -1;
  return 0;
};
assert.equal(cmp([0, 10, 0], [1, 0, 0]), -1);   // ten classes still lose to one ID
assert.equal(cmp([0, 1, 1], [0, 1, 0]), 1);
assert.equal(cmp([0, 1, 0], [0, 1, 0]), 0);     // equal: the later rule wins

4. Positioning

ValueBehaviour
staticnormal flow (default); top/left do nothing
relativenormal flow, then offset visually; creates a positioning context for absolute children
absoluteremoved from flow, positioned against the nearest positioned ancestor
fixedpositioned against the viewport
stickyflows normally until a scroll threshold, then sticks within its container

z-index only works on positioned elements (and flex/grid items) and operates within a stacking context. A stacking context is created by, among other things, position with z-index, opacity < 1, transform and isolation: isolate. A child can never escape its parent's context: that is why a "z-index: 9999" modal sometimes still sits behind something.

5. Flexbox

Flexbox lays items out along one axis.

.row {
  display: flex;
  flex-direction: row;          /* main axis: row (default) or column */
  justify-content: space-between; /* distribute along the MAIN axis */
  align-items: center;          /* align along the CROSS axis */
  gap: 16px;
  flex-wrap: wrap;
}
.grow { flex: 1 1 0; }          /* grow, shrink, basis */
  • justify-content works on the main axis; align-items on the cross axis. When flex-direction becomes column, the roles swap.
  • flex: 1 means 1 1 0%: share leftover space equally.
  • flex-basis is the starting size, flex-grow how to share extra space, flex-shrink how to give space back.
  • min-width: 0 on flex children fixes the common "text will not truncate" bug, because flex items default to min-width: auto and refuse to shrink below their content.

Centring, the perennial question:

.center { display: grid; place-items: center; }     /* shortest */
/* or */
.center { display: flex; justify-content: center; align-items: center; }
/* or, for a block with a known width */
.block { margin-inline: auto; }

6. Grid

Grid lays items out in two dimensions.

.layout {
  display: grid;
  grid-template-columns: 240px 1fr;             /* sidebar and content */
  grid-template-areas:
    "header header"
    "nav    main"
    "footer footer";
  gap: 24px;
}
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));  /* responsive with no media query */
}

fr is a share of the free space. auto-fill keeps empty tracks; auto-fit collapses them, so items stretch. Rule of thumb: flexbox for components and one-dimensional rows, grid for page layout and anything two-dimensional. They nest well.

7. Responsive design

  • Viewport meta tag: <meta name="viewport" content="width=device-width, initial-scale=1">. Without it mobile browsers pretend to be wide.
  • Mobile first: write base styles for small screens, then add @media (min-width: 768px) { ... } for larger ones.
  • Fluid layout: percentages, fr, minmax, max-width, clamp().
  • Relative units: rem (root font size) for type and spacing, em (the element's own font size), %, vw/vh (and dvh on mobile browsers whose toolbars resize).
  • Responsive images: srcset and sizes, <picture>, loading="lazy", explicit width and height to prevent layout shift.
  • Container queries (@container) let a component respond to its container's width instead of the viewport, which suits reusable components.
  • Touch targets of at least about 44 by 44 CSS pixels, no hover-only interactions, and respect prefers-reduced-motion and prefers-color-scheme.
.title { font-size: clamp(1.25rem, 1rem + 1.5vw, 2rem); }   /* scales smoothly between bounds */

8. Common layout patterns

Sticky footer: make the page a column flex container with min-height: 100dvh, and give main flex: 1.

Holy grail / app shell: a grid with grid-template-rows: auto 1fr auto.

Truncating text:

.ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.clamp3  { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

Aspect ratio: aspect-ratio: 16 / 9 replaces the old padding hack.

Equal-height cards come free with grid or flex's default align-items: stretch.

9. Performance-friendly CSS

  • Animate transform and opacity: they can run on the compositor without layout or paint. Animating width, top or margin forces layout every frame.
  • Avoid deep selector chains and huge stylesheets; inline the critical CSS for first paint and load the rest later.
  • Use content-visibility: auto for long off-screen sections.
  • will-change sparingly; it reserves memory.

10. CSS in modern projects

CSS Modules, utility frameworks (Tailwind), CSS-in-JS and design tokens (custom properties like --space-4) all aim at the same thing: avoid global name collisions and keep style decisions consistent. Custom properties cascade and can change at runtime, which makes theming (dark mode) straightforward:

:root { --bg: #fff; --fg: #111; }
@media (prefers-color-scheme: dark) { :root { --bg: #0b0b0f; --fg: #eee; } }
body { background: var(--bg); color: var(--fg); }

11. Common mistakes

  • Using div for everything, losing semantics and accessibility.
  • Fighting specificity with !important rather than fixing structure.
  • Fixed pixel widths that overflow on small screens.
  • Forgetting min-width: 0 in flex children.
  • z-index wars caused by unseen stacking contexts.
  • Using height: 100vh on mobile and getting clipped by browser chrome (prefer dvh).
  • Animating layout properties.
  • Removing the focus outline without providing a replacement.

12. Practice questions

  1. Explain the box model and box-sizing. What is margin collapsing?
  2. Compute the specificity of #a .b p and .a.b.c. Which wins?
  3. Centre a div horizontally and vertically in three ways.
  4. Flexbox versus grid: when do you choose each?
  5. Why can a z-index: 9999 element still appear behind another?
  6. Build a responsive card grid with no media queries.
  7. Why is animating transform cheaper than animating left?
  8. What is the difference between em, rem and %?
Header Logo