HTML, CSS and accessibility interview questions
Markup, layout and accessibility questions from frontend interviews: semantic HTML, the box model, the cascade and cascade layers, flexbox and grid, stacking contexts, container queries, reflow, ARIA, keyboard focus, contrast and WCAG, forms, and alt text.
Answer out loud or on paper before revealing. If your answer misses a key point, revisit the linked guide or explainer.
Nothing is due right now. Every question in this set is scheduled for a later review.
Why does semantic HTML matter? What would you change in a page built from `<div class="header">`, `<div class="nav">` and `<span class="title">`?
Reveal model answer
Semantic elements carry meaning that browsers expose to the accessibility tree, so screen reader users can jump by headings and landmarks, keyboard users get built-in focus and activation, and search engines and reader modes understand the structure. Replace the generic wrappers with `<header>`, `<nav>`, `<main>`, `<footer>`, `<article>` and `<aside>` for landmarks, real headings (`<h1>` to `<h6>`) in a logical order instead of styled spans, lists for lists, `<button>` for actions and `<a href>` for navigation. Keep the source order logical, since that is the reading and tab order, and do layout with CSS. Use one `<main>` per page and give repeated landmarks like two `<nav>` elements distinct labels.
WHAT A STRONG ANSWER COVERS
- Semantics feed the accessibility tree: roles, names and states for free.
- Landmarks and a sensible heading outline enable fast navigation for screen reader users.
- Buttons for actions, links for navigation; native elements bring keyboard support.
- DOM order is reading and focus order; do not reorder visually in ways that contradict it.
RED FLAGS
- "Semantic HTML is only for SEO."
- Choosing heading levels by font size.
- Adding `role` attributes to divs instead of using the native element.
LIKELY FOLLOW-UPS
- When is `<section>` a landmark, and when is it just a grouping?
How did your answer compare?
SOURCES
Explain the CSS box model. A card has `width: 300px; padding: 20px; border: 1px solid`. How wide is it, and what does `box-sizing: border-box` change?
Reveal model answer
Every box is content, then padding, then border, then margin. With the default `box-sizing: content-box`, `width` sets only the content area, so the card renders 300 + 2 × 20 + 2 × 1 = 342px wide plus any margin. With `border-box`, `width` includes padding and border, so the card is exactly 300px and the content area shrinks to 258px; margin is never included in either model. Most codebases set `*, *::before, *::after { box-sizing: border-box; }` in their reset because it makes percentage widths and grids predictable.
WHAT A STRONG ANSWER COVERS
- Content, padding, border, margin, from inside out.
- `content-box` adds padding and border to the declared width; `border-box` includes them.
- Margin is outside the box in both models.
- A global `border-box` reset is a common, sensible default.
RED FLAGS
- Saying `border-box` includes margin.
- Unaware that vertical margins between blocks can collapse.
LIKELY FOLLOW-UPS
- When do vertical margins collapse, and why don't they inside a flex or grid container?
How did your answer compare?
SOURCES
Two rules set a button's color and the wrong one wins. How does the cascade decide, and how do cascade layers (`@layer`) change the way you manage overrides?
Reveal model answer
The cascade compares, in order: origin and importance, then cascade layer, then specificity, then scoping proximity, then source order. Specificity is compared in ID, class, type columns, so one ID beats any number of classes; `:is()`, `:not()` and `:has()` take the specificity of their most specific argument, and `:where()` counts as zero. Layers sit above specificity: with `@layer reset, base, components, utilities;` a plain `.btn` in `utilities` beats `#app .btn` in `reset`, and unlayered styles beat all layered ones. For `!important` the layer order reverses, so the earliest layer wins. Layers let you put resets, third-party CSS and utilities into an explicit order instead of fighting with selector weight and `!important`.
WHAT A STRONG ANSWER COVERS
- Order: origin and importance, layer, specificity, proximity, source order.
- Specificity columns (ID, class, type); `:where()` is zero, `:is()` takes its strongest argument.
- Later layers win; unlayered styles beat layered ones; `!important` reverses layer order.
- Layers replace specificity wars and most uses of `!important`.
- Inline styles beat normal author rules.
RED FLAGS
- "The last rule in the file always wins."
- Adding IDs or `!important` to win every conflict.
- Thinking specificity can beat layer order.
LIKELY FOLLOW-UPS
- How would you import a third-party stylesheet so your own styles always override it?
How did your answer compare?
SOURCES
When do you reach for flexbox and when for grid?
Reveal model answer
Flexbox lays out items in one dimension, a row or a column, and works from the content out: items size themselves and share leftover space, which suits toolbars, nav bars, button groups and centering. Grid lays out in two dimensions at once and works from the layout in: you define rows and columns and place items into them, which suits page layouts, card galleries and anything that must align in both directions. A useful signal is that if you are disabling flex behaviour with fixed widths to make items line up across rows, you want grid. They combine well, such as a grid page whose cells contain flex rows, and `repeat(auto-fill, minmax(16rem, 1fr))` gives a responsive card grid without media queries.
WHAT A STRONG ANSWER COVERS
- Flexbox: one dimension, content-driven sizing.
- Grid: two dimensions, layout-driven placement.
- Fighting flex wrap alignment across rows means grid.
- They nest; `gap` works in both.
RED FLAGS
- "Grid replaces flexbox" or "flexbox is legacy."
- Using `order` or grid placement to rearrange content so that visual order contradicts DOM and tab order.
LIKELY FOLLOW-UPS
- What does `subgrid` solve?
How did your answer compare?
SOURCES
A dropdown has `z-index: 9999` but still renders behind a sticky header with `z-index: 10`. Why?
Reveal model answer
`z-index` only orders elements within the same stacking context, and each stacking context is painted as one unit inside its parent. The dropdown is probably inside an ancestor that creates its own stacking context, for example one with `transform`, `filter`, `opacity` below 1, `will-change: transform`, `isolation: isolate`, `container-type`, or `position` with a `z-index`, and that ancestor is ranked below the header, so nothing inside it can rise above the header. Fix it by finding that ancestor in DevTools and adjusting its stacking, or render the overlay outside it: a portal to the end of `body`, the native `popover` attribute, or `<dialog>` with `showModal()`, both of which use the browser's top layer. Keep a small documented scale of z-index values instead of escalating numbers.
WHAT A STRONG ANSWER COVERS
- z-index is local to a stacking context; children cannot escape a lower-ranked parent.
- Many properties besides `position` plus `z-index` create stacking contexts (`transform`, `opacity`, `filter`, `isolation`, `will-change`).
- Portals, `popover` and modal `<dialog>` (top layer) avoid the problem for overlays.
- A shared z-index scale prevents arms races.
RED FLAGS
- Raising the number to 99999.
- Not knowing that `transform` or `opacity` affects stacking.
LIKELY FOLLOW-UPS
- What does `isolation: isolate` do, and when would you add it on purpose?
How did your answer compare?
SOURCES
A card component must switch to a horizontal layout when it has room, whether it sits in a sidebar or a wide main column. How would you build it?
Reveal model answer
Media queries respond to the viewport, but this card's available space depends on where it is placed, so use a container query. Make the wrapper a query container with `container: card / inline-size;`, then write `@container card (width > 30rem) { .card { grid-template-columns: 10rem 1fr; } }`, and use container units such as `cqi` for fluid sizing inside it. `inline-size` containment means the container's width can no longer come from its content, so it needs its width from its layout context. Size container queries have been supported in all major browsers since 2023; keep media queries for page-level layout and user preferences such as `prefers-reduced-motion` and `prefers-color-scheme`.
WHAT A STRONG ANSWER COVERS
- Media queries: viewport; container queries: the component's own available space.
- Declare a container with `container-type` or the `container` shorthand, optionally named.
- Container query units (`cqi`, `cqw`) for intrinsic, fluid sizing.
- Inline-size containment stops the container sizing from its content.
- Mobile-first with relative units and fluid layouts before reaching for breakpoints.
RED FLAGS
- Passing a `variant="compact"` prop from every parent to fake it.
- Measuring width with `ResizeObserver` in JavaScript when CSS can do it.
- Breakpoints in fixed pixels tied to particular devices.
LIKELY FOLLOW-UPS
- Why can't an element query its own size, only an ancestor container's?
How did your answer compare?
SOURCES
An expanding panel animates `height` and `top` and stutters on mid-range phones. Explain what the browser is doing and how you would fix it.
Reveal model answer
Changing geometric properties such as `width`, `height`, `top` or `left` invalidates layout, so on every frame the browser recalculates style, reflows the affected part of the page, repaints and composites, which easily exceeds a 16ms frame budget on slow devices. `transform` and `opacity` can be animated on the compositor without layout or paint, so animate `transform: translateY()` or `scale` instead, and promote only the animating element with `will-change: transform`, since each layer costs GPU memory. In JavaScript, avoid forced synchronous layout: reading `offsetHeight` or `getBoundingClientRect()` after writing styles in a loop causes layout thrashing, so batch all reads and then all writes. Verify with the DevTools Performance panel, look for long layout tasks, and respect `prefers-reduced-motion`.
WHAT A STRONG ANSWER COVERS
- Pipeline: style, layout, paint, composite; geometry changes trigger layout.
- Animate `transform` and `opacity`; they skip layout and paint.
- `will-change` sparingly, because layers cost memory.
- Forced synchronous layout and thrashing from interleaved reads and writes.
- Measure in the Performance panel before and after.
RED FLAGS
- Putting `will-change` or `translateZ(0)` on everything.
- "CSS animations are always GPU accelerated."
- Fixing it with a JavaScript animation library without changing which properties animate.
LIKELY FOLLOW-UPS
- How would you animate an element to its natural `auto` height?
- How does `content-visibility: auto` reduce rendering work on long pages?
How did your answer compare?
SOURCES
What are the rules for using ARIA? What is wrong with `<span role="button" onclick="save()">Save</span>` and `<a href="/cart" aria-hidden="true">Cart</a>`?
Reveal model answer
W3C's four rules of ARIA use start with using a native element with the semantics and behaviour you need instead of adding ARIA; then don't change native semantics unless you must, make every interactive ARIA control keyboard operable, and never put `aria-hidden="true"` or `role="presentation"` on a focusable element. On top of those, WCAG 4.1.2 requires every interactive element to expose an accessible name, role and state. A role is only a promise: `role="button"` changes what a screen reader announces but adds no focusability, no Enter or Space handling and no disabled behaviour, so the span is unreachable by keyboard; use `<button type="button">`. The hidden link is still focusable, so keyboard users land on something screen readers do not announce; remove it from the tab order too, or more likely don't hide it at all. Incorrect ARIA is worse than none, so test with a real screen reader and the browser's accessibility tree.
WHAT A STRONG ANSWER COVERS
- Native HTML first; ARIA only fills real gaps.
- ARIA changes semantics only; behaviour and keyboard support are your job.
- Never hide focusable content from assistive technology.
- Every interactive element needs an accessible name.
- Follow the APG patterns for custom widgets and test with screen readers.
RED FLAGS
- Adding `aria-label` to everything, including elements that already have visible text.
- Believing `role="button"` makes an element focusable.
- Treating automated checker scores as proof of accessibility.
LIKELY FOLLOW-UPS
- What is the difference between `aria-label`, `aria-labelledby` and `aria-describedby`?
How did your answer compare?
SOURCES
How do you make a custom modal keyboard accessible? A designer also asks you to add `*:focus { outline: none; }`. What do you say?
Reveal model answer
On open, move focus into the dialog, keep Tab inside it while it is open, make the background inert, close it on Escape, and on close return focus to the control that opened it; the native `<dialog>` with `showModal()` gives you focus movement, an inert background, Escape handling and the top layer. Don't remove outlines globally: keyboard users then cannot tell where they are, which fails WCAG's Focus Visible criterion. Instead style `:focus-visible`, which browsers apply when focus should be shown (typically after keyboard navigation, not a mouse click on a button), with a clear indicator of at least 3:1 contrast. More generally, everything interactive must be reachable and operable by keyboard in a logical order matching the DOM, avoid positive `tabindex`, and use roving `tabindex` or `aria-activedescendant` for composite widgets like menus and tabs.
WHAT A STRONG ANSWER COVERS
- Focus in on open, contained while open, Escape closes, focus returned on close.
- Prefer `<dialog>` with `showModal()` or the `inert` attribute over hand-rolled focus traps.
- Never remove focus indicators without a replacement; style `:focus-visible`.
- Tab order follows DOM order; no positive `tabindex`.
- Arrow-key navigation inside composite widgets with roving tabindex.
RED FLAGS
- "Our users use a mouse."
- Using `tabindex="1"` and up to fix tab order.
- Leaving focus on the removed modal or the document body after closing.
LIKELY FOLLOW-UPS
- What should happen to focus after a client-side route change?
How did your answer compare?
SOURCES
What contrast does WCAG require, what are the conformance levels, and which version should a project target?
Reveal model answer
WCAG has three conformance levels: A (minimum), AA (the usual legal and contractual target) and AAA (enhanced, rarely required for a whole site). For text, AA requires a contrast ratio of at least 4.5:1, or 3:1 for large text (about 24px regular or 18.5px bold), and AAA raises that to 7:1 and 4.5:1; the thresholds are not rounded, so 4.49:1 fails. Non-text contrast at AA requires 3:1 for UI component boundaries, focus indicators and meaningful graphics, and colour must never be the only way information is conveyed. Target WCAG 2.2 AA, the current W3C Recommendation, which added criteria such as Focus Not Obscured and Target Size (Minimum) and removed 4.1.1 Parsing; it is backwards compatible with 2.1, which many regulations still reference.
WHAT A STRONG ANSWER COVERS
- Levels A, AA, AAA; AA is the practical target.
- Text: 4.5:1 normal, 3:1 large at AA; 7:1 and 4.5:1 at AAA.
- Non-text contrast 3:1 for controls, focus indicators and graphics.
- Do not rely on colour alone (for example error states shown only in red).
- WCAG 2.2 is current; meeting 2.2 also meets 2.1.
RED FLAGS
- Checking only body text and ignoring placeholders, disabled-looking but active controls, and focus rings.
- Assuming a colour that "looks readable" passes without measuring the ratio.
- Citing WCAG 2.0 as the current standard.
LIKELY FOLLOW-UPS
- How would you enforce contrast in a design system so teams cannot ship failing combinations?
How did your answer compare?
SOURCES
Review this field: `<input placeholder="Email"><span class="error" style="color: red">Invalid</span>`. How do you build accessible forms and error messages?
Reveal model answer
The input has no label, since a placeholder disappears on typing, often has poor contrast and is not a reliable accessible name; add a visible `<label for="email">` matched to `id="email"` and use `type="email"` with `autocomplete="email"`. The error is only red text that is not connected to the field, so connect it with `aria-describedby` pointing to the message, set `aria-invalid="true"` while it is invalid, and make the text say how to fix it, with an icon or prefix so colour is not the only cue. Group related controls such as radio buttons in `<fieldset>` with a `<legend>`, mark required fields in text as well as with `required`, and on submit show an error summary linking to each field and move focus to it or the first invalid field. Validate on the server as well, because client validation is only for UX.
WHAT A STRONG ANSWER COVERS
- Visible `<label>` associated with `for` and `id`; placeholders are not labels.
- Errors linked with `aria-describedby`, `aria-invalid` on the field, and actionable text.
- Do not rely on colour alone.
- `fieldset` and `legend` for groups; correct `type` and `autocomplete`.
- Error summary and focus management on submit; announce dynamic errors appropriately.
RED FLAGS
- Using `title` or `placeholder` as the only label.
- Announcing every keystroke's validation through an assertive live region.
- Disabling the submit button without saying why.
LIKELY FOLLOW-UPS
- When should validation run: on input, on blur or on submit?
How did your answer compare?
SOURCES
How do you decide what to put in an image's `alt` attribute? Give examples for a logo link, a decorative divider, a product photo and a sales chart.
Reveal model answer
Write `alt` for what the image contributes in context, not what it looks like. A logo that links home is functional, so its alt describes the destination, such as `alt="Acme home"`; a decorative divider or an image that repeats adjacent text gets `alt=""` so screen readers skip it (leaving `alt` out entirely makes many read the file name). A product photo is informative, so it gets a short description of what matters to the buyer, such as colour and style. A chart needs a brief alt summarizing the takeaway plus the full data elsewhere on the page, such as a table or text description. Don't start with "image of", don't put text in images when real text would do, and for icon-only buttons name the action.
WHAT A STRONG ANSWER COVERS
- Alt depends on purpose: decorative, functional, informative, complex.
- Decorative images use empty `alt=""`, never a missing attribute.
- Functional images describe the action or destination.
- Complex images need the information available in text elsewhere.
- No "image of", no redundant text, no keyword stuffing.
RED FLAGS
- Using the file name or "image" as alt text.
- Omitting `alt` on decorative images.
- Describing a linked logo's appearance instead of where it goes.
LIKELY FOLLOW-UPS
- How should SVG icons be made accessible, or hidden when decorative?
How did your answer compare?
SOURCES