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.
| Need | Use | Not |
|---|---|---|
| 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):widthsets the content width; padding and border add to it.box-sizing: border-box:widthincludes 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:
- Importance (
!importantwins, apart from some origin rules). - Specificity, counted as (inline style, IDs, classes/attributes/pseudo-classes, elements/pseudo-elements).
- Order: the later rule wins on a tie.
| Selector | Specificity (a, b, c) |
|---|---|
p | 0, 0, 1 |
.card | 0, 1, 0 |
.card p | 0, 1, 1 |
#nav .item | 1, 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
| Value | Behaviour |
|---|---|
static | normal flow (default); top/left do nothing |
relative | normal flow, then offset visually; creates a positioning context for absolute children |
absolute | removed from flow, positioned against the nearest positioned ancestor |
fixed | positioned against the viewport |
sticky | flows 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-contentworks on the main axis;align-itemson the cross axis. Whenflex-directionbecomescolumn, the roles swap.flex: 1means1 1 0%: share leftover space equally.flex-basisis the starting size,flex-growhow to share extra space,flex-shrinkhow to give space back.min-width: 0on flex children fixes the common "text will not truncate" bug, because flex items default tomin-width: autoand 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(anddvhon mobile browsers whose toolbars resize). - Responsive images:
srcsetandsizes,<picture>,loading="lazy", explicitwidthandheightto 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-motionandprefers-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
transformandopacity: they can run on the compositor without layout or paint. Animatingwidth,topormarginforces 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: autofor long off-screen sections. will-changesparingly; 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
divfor everything, losing semantics and accessibility. - Fighting specificity with
!importantrather than fixing structure. - Fixed pixel widths that overflow on small screens.
- Forgetting
min-width: 0in flex children. z-indexwars caused by unseen stacking contexts.- Using
height: 100vhon mobile and getting clipped by browser chrome (preferdvh). - Animating layout properties.
- Removing the focus outline without providing a replacement.
12. Practice questions
- Explain the box model and
box-sizing. What is margin collapsing? - Compute the specificity of
#a .b pand.a.b.c. Which wins? - Centre a div horizontally and vertically in three ways.
- Flexbox versus grid: when do you choose each?
- Why can a
z-index: 9999element still appear behind another? - Build a responsive card grid with no media queries.
- Why is animating
transformcheaper than animatingleft? - What is the difference between
em,remand%?