// Semantic HTML & Accessibility
Structure, Meaning, and Whether Every User Can Actually Use What You Built
Why does semantic HTML (using nav, main, article, section, aside instead of div everywhere) actually matter, beyond readability of the source?
Semantic elements carry meaning that assistive technology, browsers, and search engines all rely on — a screen reader user can jump directly to <main> or between <nav> landmarks; a div-only page offers no such navigation shortcuts at all. Semantic tags also get sensible default behavior and implicit ARIA roles for free (a <button> is focusable and keyboard-activatable without any extra code; a div styled to look like a button is not, unless you manually re-implement all of that). It is a case where doing the 'more correct' thing is also simply less work.
What's the actual difference between <div> and <span>, and when would a semantic tag be preferred over both?
Both are generic containers with no inherent meaning — div is block-level, span is inline. The difference from a semantic tag isn't display behavior, it's that div/span communicate nothing about the CONTENT's role, while <article>, <nav>, <button>, <time>, etc. tell the browser, search engines, and assistive tech what the content actually is. Reach for div/span only when no semantic element genuinely fits — they are the fallback, not the default.
How would you make a custom-styled clickable card component accessible, if the design calls for the entire card (not just a small link inside it) to be clickable?
Wrap the actionable content in a real <a> or <button> rather than adding a click handler to a div — this gets keyboard focus, Enter/Space activation, and correct screen-reader announcement for free. A common real pattern: make the whole card a single <a> wrapping its contents (valid HTML, since a link can contain block-level content), or use a 'stretched link' technique where a positioned pseudo-element or absolutely positioned link fills the card while the actual link text stays visually inside it.
What is the accessibility tree, and how does it relate to what you write in HTML?
The accessibility tree is a parallel structure the browser builds alongside the DOM specifically for assistive technology — it strips out purely visual/structural nodes and keeps the ones that carry meaning: roles, names, states, and values. Semantic HTML and correct ARIA attributes are what shape this tree; writing <div onClick> instead of <button> produces an accessibility tree node with no role and no keyboard interaction, which is invisible or useless to a screen reader user even though it's fully visible on screen.
When should you reach for an ARIA attribute instead of a native HTML element?
Only when no native element already provides the needed semantics — the first rule of ARIA use is 'no ARIA is better than bad ARIA,' and native elements come with correct behavior built in. ARIA earns its place for things HTML has no native equivalent for: a live region announcing dynamic content changes (aria-live), or supplementing a genuinely custom widget (like a combobox built from scratch) with the roles/states a native equivalent doesn't fully cover.
What's the difference between visibility: hidden, display: none, and a visually-hidden-but-screen-reader-accessible pattern — and when would you use each?
display: none removes the element from layout AND the accessibility tree entirely — invisible to everyone, including screen readers. visibility: hidden removes it visually and from the accessibility tree, but it still occupies layout space. A 'visually hidden' utility class (absolute positioning, 1px clip, no display/visibility change) removes it from SIGHT only, keeping it in the accessibility tree — the standard technique for content meant for screen reader users only, like an icon-only button's descriptive label.
// The Box Model, the Cascade & Specificity
The Fundamentals Every Front-End Interview Assumes You Know Cold
Walk through exactly how the box model works, including the difference box-sizing makes.
Every element is a box with four layers, from the inside out: content, padding, border, margin. Under the default box-sizing: content-box, the width/height you declare applies ONLY to the content box — padding and border are added on top, so a 300px-wide box with 20px padding and a 2px border renders at 344px. Under box-sizing: border-box, width/height instead include padding and border, so that same box stays exactly 300px wide with the content area shrinking to accommodate. Margin is never included in either mode — it's space outside the border, affecting layout position, not the element's own rendered size.
Explain the CSS cascade — what actually determines which conflicting rule wins?
In order of precedence: importance and origin first (author !important beats everything except user-agent !important; then author normal styles; then user-agent defaults), then specificity (inline > ID > class/attribute/pseudo-class > element), then source order (later rules win ties). A common trap: people assume source order is the primary tiebreaker, but specificity is checked FIRST — a highly specific rule earlier in the file still beats a low-specificity rule declared later.
How exactly is specificity calculated, and can you give an example where the 'wrong' rule wins because of it?
Specificity is commonly represented as a 4-part tuple: inline styles, ID selectors, class/attribute/pseudo-class selectors, element/pseudo-element selectors. Given #header .nav a { color: blue; } (specificity 0-1-1-1) and a { color: red; text-decoration: underline; } declared later (0-0-0-1), the FIRST rule still wins the color, despite appearing earlier — its higher specificity beats source order, a genuinely common source of 'I changed the CSS and nothing happened' confusion.
What's the difference between inherited and non-inherited CSS properties, and why does that distinction matter practically?
Inherited properties (color, font-family, font-size, line-height, and most text-related properties) automatically pass down to descendant elements unless explicitly overridden. Non-inherited properties (margin, padding, border, width, display, and most box/layout properties) do NOT pass down — each element starts fresh. This matters practically because setting font-family once on body cascades everywhere for free, while a border set on a parent has zero effect on its children — a distinction that trips up people expecting CSS properties to behave uniformly.
What does the universal box-sizing: border-box reset actually do, and why is it applied so broadly across real projects?
A universal selector rule (targeting every element, often paired with its ::before and ::after pseudo-elements) that switches every element's box model so declared width/height include padding and border, matching what most developers intuitively expect and making layout math dramatically more predictable — a component with padding no longer silently grows past its declared width. It is applied nearly universally in real projects specifically because content-box's default behavior causes constant, subtle layout bugs otherwise.
// Flexbox, Grid & Positioning
Layout Decision-Making — The Question Every Interview Eventually Asks
How do you decide between Flexbox and Grid for a given layout?
Flexbox is one-dimensional — it excels at distributing space along a single row or column (a navbar, a button group, centering content). Grid is two-dimensional — it defines both rows and columns simultaneously, making it the right tool whenever a layout has real structure in both directions at once (a page shell with header/sidebar/main/footer, a dashboard, a card gallery with aligned rows AND columns). A useful mental test: if you find yourself trying to force a Flexbox row to also align items consistently in columns below it, that's usually the signal to switch to Grid. In practice, most real pages use both together — Grid for the page-level shell, Flexbox for the one-dimensional alignment inside individual components.
Explain each value of the position property and what establishes a new positioning context.
static is the default — no special positioning, position properties (top/left/etc.) have no effect. relative positions the element relative to its own normal position, WITHOUT removing it from the normal flow, and — critically — establishes a positioning context for absolutely-positioned descendants. absolute removes the element from normal flow entirely and positions it relative to its nearest ancestor with a position value other than static (falling back to the initial containing block/viewport if none exists). fixed positions relative to the viewport and stays put during scrolling. sticky behaves like relative until a scroll threshold is crossed, then behaves like fixed within its containing block's bounds.
What is a stacking context, and why can z-index sometimes 'not work' even though the value looks correct?
A stacking context is a self-contained layer for z-index comparisons — z-index values only compete against SIBLINGS within the same stacking context, never across contexts. Certain CSS properties create a new stacking context implicitly (position with a z-index value, opacity less than 1, transform, filter, and several others). A very common bug: an element has z-index: 9999 but still renders behind something else, because a parent created its own stacking context with a lower effective stacking order, and no z-index value on the child can escape that parent's context to compete with elements outside it.
What's the difference between align-items and justify-content in Flexbox, and how does flex-direction affect which is which?
justify-content aligns items along the MAIN axis; align-items aligns items along the CROSS axis. With the default flex-direction: row, the main axis is horizontal, so justify-content controls horizontal spacing and align-items controls vertical alignment. Switch to flex-direction: column and the axes swap — justify-content now controls VERTICAL spacing and align-items controls horizontal alignment. This axis-relative (not screen-relative) behavior is one of the most common sources of Flexbox confusion for people new to it.
When would you reach for CSS Grid's grid-template-areas instead of grid-template-columns/rows with explicit line numbers?
grid-template-areas gives layout a literal, readable ASCII-art shape directly in the CSS — genuinely valuable for a page shell (header/sidebar/main/footer) where the visual structure benefits from being immediately legible in the stylesheet itself, and where that structure needs to change per breakpoint (redefining grid-template-areas inside a media query cleanly reflows the whole layout). Explicit line-number placement is more precise for finer-grained or highly dynamic grids (like a gallery with a variable number of items) where naming every area doesn't make sense.
// Responsive Design & Performance
Building for Every Screen, and Keeping It Fast
What does 'mobile-first' actually mean in terms of how the CSS itself is written, not just the design process?
Mobile-first means the UNPREFIXED, default CSS rules target the smallest screen, and media queries use min-width to progressively ADD complexity as the viewport grows — the opposite of writing desktop styles as the default and using max-width queries to strip things away for small screens. Practically, this means base styles should be genuinely simple layouts (often single-column, stacked), with min-width breakpoints introducing multi-column layouts, larger typography, and additional visual elements as space allows.
What's the difference between px, %, em, rem, and viewport units (vw/vh), and when does each genuinely earn its place?
px is an absolute unit, disconnected from any user font-size preference — fine for things like a 1px border, poor for font sizes. % is relative to the parent's corresponding value. em is relative to the CURRENT element's font-size (and compounds when nested, since each em references its own element's computed font-size, not a fixed root). rem is relative to the ROOT element's font-size only, avoiding the compounding problem — the standard choice for most font-size and spacing values in a real project. vw/vh are relative to the viewport itself, useful for things that should genuinely scale with screen size, like a full-bleed hero section's height.
How does the srcset/sizes combination on an img tag actually improve performance, versus just using a single large image everywhere?
srcset provides the browser with several versions of the same image at different resolutions, and sizes tells the browser how large the image will actually be RENDERED at different viewport widths — the browser then downloads only the smallest image file that satisfies the actual rendered size and the device's pixel density, rather than always downloading one large image and scaling it down in the browser. On a phone, this can mean downloading a fraction of the bytes a desktop-optimized single image would require, directly improving load time and Core Web Vitals.
Why are transform and opacity considered the 'cheap' properties to animate, while properties like width, height, or top/left are considered expensive?
Animating layout-affecting properties (width, height, top, left, margin) forces the browser to re-run layout (recalculating the position/size of potentially many other elements) and then repaint, on every single frame — expensive, and prone to visible jank. transform and opacity can typically be handled entirely on the GPU's compositing layer, skipping layout and paint recalculation altogether, which is why the standard advice for smooth animation is 'animate transform/opacity, not layout properties' — e.g. translate an element instead of animating its top/left position.
What does prefers-reduced-motion do, and why does implementing it matter beyond just accessibility compliance?
It's a media query that reflects a user's OS-level accessibility setting requesting reduced or disabled non-essential motion, most often set by users with vestibular disorders where large animations can cause genuine physical discomfort. Wrapping decorative animations in @media (prefers-reduced-motion: no-preference) — the inverse pattern, applying motion only when NOT reduced — respects that setting without requiring any extra work from the user. It matters beyond compliance because it's a real, documented category of user harm that a small, cheap CSS change directly prevents.
// Hands-On Coding & Layout Challenges
Four Classic Problems, Worked Through Completely
Beyond conceptual questions, most front-end interviews include at least one live "build this" or "fix this" exercise. These four are among the most commonly seen — not because the exact prompts repeat verbatim, but because the underlying techniques (centering, grid-based composition, flex-based page shells, box-model debugging) generalize to a huge fraction of what actually gets asked.
1. Center a div — three different ways, and their trade-offs
A deceptively simple prompt that is really testing whether you understand several layout systems well enough to choose deliberately between them, not just whether you can produce centered content.
Approach 1 — Flexbox (the most common real-world default)
.parent {
display: flex;
justify-content: center; /* centers on the main axis */
align-items: center; /* centers on the cross axis */
height: 100vh;
}
/* Works for one or many children, unknown child dimensions, and adapts
instantly if the child's size changes. The default choice in most
real projects for this exact problem. */
Approach 2 — CSS Grid (equally simple, sometimes preferred if Grid is already in use)
.parent {
display: grid;
place-items: center; /* shorthand for align-items + justify-items, both centered */
height: 100vh;
}
/* Marginally more concise than the Flexbox version for a SINGLE
centered child. Less natural if you also need to distribute
several children in a row alongside the centering. */
Approach 3 — Absolute positioning with transform (works without a flex/grid parent)
.parent { position: relative; height: 100vh; }
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* Useful specifically when the parent's layout mode can't change
(it needs to stay a normal block flow for other reasons), or when
centering an element ON TOP of other content rather than among it.
Requires knowing this specific top/left + transform combination —
margin: auto alone only centers horizontally in a block context. */
The trade-off worth stating out loud in an interview: Flexbox and Grid are the modern defaults because they don't remove the element from normal flow and adapt automatically to content size changes; the absolute-positioning approach is reached for specifically when the element needs to be centered independent of its siblings' layout, such as a modal overlay centered on top of unrelated page content.
2. Build a responsive card grid from scratch
A staple layout exercise — a grid of cards that reflows its column count based on available width, without a fixed breakpoint list to maintain.
A self-adjusting card grid — no media queries required
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 24px;
}
/* auto-fit tells Grid to fit as many 240px+ columns as the container
allows, and 1fr lets each column stretch to fill any remaining
space evenly. As the viewport shrinks, columns drop one at a time
automatically — the same rule handles a phone, a tablet, and an
ultrawide monitor with zero explicit breakpoints. */
An individual card, using the box-model and spacing-scale habits from earlier modules
.card {
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,.1);
padding: 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
A strong follow-up an interviewer often asks: what happens with exactly one card in the grid? With auto-fit, that single card stretches to fill the full row width (since 1fr claims all remaining space). If a single card should instead stay at its minimum size rather than stretching, swapping auto-fit for auto-fill changes that behavior — worth knowing the distinction exists, even if auto-fit is the more commonly desired behavior for a genuine card grid.
3. Fix this broken sticky footer
A footer that should sit at the bottom of the viewport on short pages, but scrolls up and leaves a gap, or overlaps content, is one of the most commonly seen "fix this" prompts.
The broken version
body { margin: 0; }
.footer { position: absolute; bottom: 0; width: 100%; }
/* On a page with little content, the footer sits at the bottom of the
VIEWPORT initially, but position: absolute takes it out of normal
flow entirely — it doesn't push against the actual page content, so
as soon as any content overlaps that same vertical space, the
footer sits ON TOP of it instead of below it. */
Fixed — the Flexbox sticky-footer pattern
html, body { height: 100%; margin: 0; }
.page {
display: flex;
flex-direction: column;
min-height: 100%;
}
.main-content { flex: 1; } /* grows to fill any remaining space */
.footer { /* no special positioning needed at all */ }
/* <body><div class="page"><header>...</header>
<main class="main-content">...</main>
<footer class="footer">...</footer>
</div></body> */
The mechanism: flex: 1 on the main content area means it grows to consume any leftover vertical space in the flex column, pushing the footer down to the bottom of the viewport on short pages — but because the footer is still in normal flow (not position: absolute), it is naturally pushed further down by real content on longer pages instead of overlapping it. This is the same core idea covered in the Flexbox in Practice module, worth being able to reproduce from memory.
4. Explain and fix this box-sizing bug
A short, deliberately broken snippet — a very common warm-up exercise meant to check that box model fundamentals are genuinely internalized, not just memorized as a definition.
The broken layout — three 33.33% columns that wrap to a second row
.column {
width: 33.33%;
padding: 0 16px;
float: left;
box-sizing: content-box; /* the default, made explicit here */
}
/* Three columns at exactly 33.33% width EACH, plus 32px of padding
added on top of every one of them (16px left + 16px right), pushes
the total rendered width past 100% of the container — the third
column has nowhere left to go and wraps onto a new row. */
Fixed — border-box makes the declared width the FULL rendered width
.column {
width: 33.33%;
padding: 0 16px;
float: left;
box-sizing: border-box; /* padding is now included WITHIN the 33.33% */
}
/* Now each column's rendered width, padding included, is exactly
33.33% of the container — three of them sum to exactly 100%,
fitting on a single row as intended. */
Worth naming explicitly in an interview: this exact bug is precisely why * { box-sizing: border-box; } is applied so broadly as a near-universal reset in real projects — it eliminates this entire category of "the math doesn't add up to 100%" layout bug by default, rather than requiring every component author to remember it individually.
// Misconceptions About Front-End Interviews
Four Misconceptions About How These Interviews Are Actually Graded
✕ ""The interviewer mainly wants to see whether you remember exact CSS property names and syntax""
Most interviewers weight reasoning and trade-off awareness — WHY Flexbox versus Grid, WHY a bug is happening, WHAT you would verify before calling something done — at least as heavily as syntax recall, and many explicitly allow looking up exact property names. Being able to explain the box model or a stacking context clearly, even while double-checking a specific syntax detail, generally reads better than reciting syntax with no explanation of the underlying mechanism.
✕ ""Since it's just CSS, layout questions are lower-stakes than JavaScript/algorithm questions""
Layout and CSS questions are frequently where real production bugs live in day-to-day front-end work — far more often than algorithmic edge cases — so many teams weight them just as heavily, specifically because they predict how a candidate will actually perform on real tickets far more directly than an algorithm question would.
✕ ""A live layout exercise is graded purely on whether the final result looks pixel-correct""
The process is usually the bigger signal — whether you clarify ambiguous requirements before coding, whether you consider a responsive/accessibility angle without being prompted, and whether you verify edge cases (very few items, very long text, a very narrow viewport) yourself rather than needing the interviewer to point them out.
✕ ""Accessibility questions are a specialized, separate track from general front-end interview questions""
Semantic HTML and basic accessibility (keyboard focus, correct roles, color contrast, prefers-reduced-motion) are treated as baseline front-end competence at most companies now, not a specialty — expect at least one question or live-coding decision point that touches it, even in an interview loop not explicitly labeled "accessibility."