Frontend EngineeringHTML & CSSJune 2026

HTML & CSS Interview
Authority.

19+ questions covering the browser rendering pipeline, Container Queries, Cascade Layers, semantic HTML, accessibility, and modern CSS animations. No surface-level definitions β€” just the depth interviewers actually probe.

19
Questions
5
Modules
3
Difficulty levels

Frontend engineering in 2026 is no longer about β€œmaking things look pretty.” Interviewers at top companies expect you to reason fluently about the browser's rendering pipeline, explain why compositing beats layout for animation performance, and understand how Container Queries, Cascade Layers, and Scroll-Driven Animations replace patterns that previously required JavaScript.

Every answer in this guide goes beyond the definition to include the production consequence, the common gotcha, and the kind of β€œwhy not this other approach” reasoning that separates senior candidates from junior ones.

01
Module 01Junior

HTML Fundamentals & Semantic Structure

Semantic HTML is the foundation that everything else β€” accessibility, SEO, performance β€” sits on. Interviewers probe here to check that you understand why structure matters, not just what tags exist.

Q01Junior

What is semantic HTML and why does it matter beyond visual presentation?

Semantic HTML means using elements that describe the meaning and role of content, not just its appearance. A <nav> element tells the browser, screen readers, and search engines that its contents are navigation links. A <main> element identifies the primary content of the page. An <article> marks self-contained content that could stand alone (a blog post, a news story). The consequences of getting this wrong are real: screen reader users navigate pages by landmark regions β€” if your header is a <div>, keyboard-only users lose the ability to skip directly to content. Search engines use heading hierarchy (<h1> through <h6>) to infer topic structure and page authority. Chrome's accessibility tree is built from semantic elements; non-semantic layouts require extensive ARIA patching to make them equally accessible, which is error-prone.

Practical note

A common interview test: ask candidates what the difference between <section> and <div> is. A <div> is purely presentational with no semantic meaning. A <section> implies that its content is thematically grouped and typically has a heading. Knowing this distinction signals architectural intent.

Q02Junior

What is the purpose of the 'alt' attribute on images β€” and what should it contain?

The alt attribute serves three distinct purposes, each important. First, accessibility: screen readers read the alt text aloud to visually impaired users, so the text must describe the image's meaning in context, not just its content ('photo of person' is useless; 'CEO Jane Smith presenting Q3 results' is useful). Second, fallback: if an image fails to load due to a broken URL or slow connection, the browser renders the alt text in its place β€” a blank white box with no text is a worse user experience than a description. Third, SEO: search engines cannot 'see' images and rely on alt text to index image content and understand page context. The rule: decorative images (background textures, icon stripes) should have alt="" so screen readers skip them. Informative images need descriptive alt text. Images that are links need alt text describing the link's destination.

Practical note

A common mistake: alt="image" or alt="photo" adds no value to any of the three use cases. Write alt text that could replace the image in a text-only context.

Q03Junior

Explain the CSS Box Model and the critical difference between content-box and border-box.

Every HTML element is rendered as a rectangular box with four layers: content (the actual text/image), padding (transparent space inside the border), border (a line around the padding), and margin (transparent space outside the border). The critical question is how width and height are calculated. In the default content-box model, width refers only to the content area β€” padding and borders are added on top, making the rendered element larger than the declared width. This causes constant layout miscalculations. border-box includes padding and border within the declared width, so a box with width: 300px, padding: 20px will always occupy exactly 300px of space β€” the content shrinks to accommodate the padding. The industry-standard reset (* { box-sizing: border-box }) applies border-box universally, making layout calculations predictable.

Practical note

Without border-box: a 300px-wide container with 20px padding on each side renders at 340px β€” blowing its parent container. With border-box: it stays at 300px.

Q04Junior

What is the difference between 'display: none', 'visibility: hidden', and 'opacity: 0'?

These three approaches all hide an element visually, but they behave very differently. display: none removes the element from the document flow entirely β€” it takes up no space, its children are also hidden, and it triggers a layout reflow. Screen readers ignore it. visibility: hidden hides the element but preserves its space in the layout β€” the gap remains. Screen readers also ignore hidden elements, but unlike display: none, transitions and animations can still apply. opacity: 0 makes the element fully transparent but it still occupies space, can still receive click events (a common gotcha), and is still accessible to screen readers. For accessible hide-and-show animations, opacity: 0 combined with pointer-events: none and aria-hidden="true" is the correct approach β€” it allows CSS transitions while properly hiding from both pointer events and assistive technology.

Practical note

A dropdown menu that animates open/close should use opacity and transform transitions, not display toggling β€” display can't be transitioned. Use a class toggle to switch between opacity: 0 (+ pointer-events: none) and opacity: 1.

Q05Junior

What are Semantic Tags like <article>, <section>, and <header>, and how do they differ?

<header> represents introductory content for its nearest section or the page β€” a site logo, navigation, a hero headline. There can be multiple <header> elements (one per <article>). <nav> contains navigation links β€” primary navigation, breadcrumbs, pagination. <main> contains the primary content of the page; there should be exactly one per page. <article> marks self-contained content that makes sense independently of its surrounding context β€” a blog post, a comment, a product card. <section> groups thematically related content and should generally have a heading. <footer> contains information about its parent section β€” copyright, legal links, author bio. <aside> marks content tangentially related to the surrounding content β€” a sidebar, a callout box, a pull quote. The practical test: could this content be extracted and republished elsewhere? If yes, use <article>. If it's just a themed group within the page, use <section>.

Practical note

A blog post should be wrapped in <article>, its byline in a <header> inside that article, its navigation between posts in a <nav>, and related articles in an <aside>.

02
Module 02Mid

CSS Layout: Flexbox, Grid & Modern Patterns

CSS layout has matured enormously in the past five years. Interviewers expect you to choose the right tool for the right job β€” Flexbox, Grid, Subgrid, and Container Queries each solve different problems, and knowing the boundary between them is a senior signal.

Q01Mid

What is the fundamental difference between Flexbox and CSS Grid β€” and when should you choose each?

Flexbox is a one-dimensional layout system: it distributes items along a single axis (either a row or a column). It excels at distributing items with flexible sizes along that axis β€” navigation bars, button groups, card rows. The primary axis is controlled by flex-direction; the cross axis is controlled by align-items. CSS Grid is two-dimensional: it manages items in both rows and columns simultaneously. It excels at placing items on an explicit grid structure β€” page layouts, data tables, image galleries. The practical rule: use Flexbox for components (alignment within a component), Grid for layout (placing components within a page or region). They compose perfectly: a page might use Grid for the overall layout and Flexbox inside each card component. In 2026, Subgrid (grid-template-columns: subgrid) lets child components inherit their parent grid's track structure, enabling pixel-perfect alignment across unrelated sibling components.

Practical note

A horizontal navigation bar with items that should spread or centre β†’ Flexbox. A dashboard with a sidebar, header, and main content area β†’ Grid. A card inside that grid where the footer should always stick to the bottom β†’ Flexbox inside Grid.

Q02Mid

What are CSS Container Queries and why do they make components genuinely reusable in a way Media Queries don't?

Media Queries apply styles based on the viewport size β€” the total browser window. This means a component that needs to adapt (e.g., a card that should stack vertically when narrow) must know its context to apply the right styles. If the same card is used in a full-width layout and a narrow sidebar, the media query that works in one context breaks in the other. Container Queries solve this: instead of querying the viewport, you query the size of a container element. You declare a containment context with container-type: inline-size on the parent, then use @container (min-width: 400px) { } to apply styles when that specific parent is 400px or wider. The component becomes context-agnostic β€” it adapts to whatever space it's given, regardless of the viewport width. This is the missing piece that makes design systems truly component-driven.

Practical note

A card component with @container query can render in a single-column stacked layout when used in a narrow sidebar and switch to a side-by-side layout automatically when used in the main content area β€” same HTML, same CSS, no JavaScript.

Q03Mid

How do you manage Z-index and stacking contexts without descending into 'Z-index wars'?

Z-index wars happen when developers keep increasing Z-index values to force elements in front of others, reaching values like 99999, because they don't understand stacking contexts. A stacking context is an isolated rendering layer: Z-index values inside it only compete with siblings within the same context, not with the global page. Several CSS properties create new stacking contexts automatically: position with a value other than static combined with a Z-index, opacity below 1, transform, filter, will-change, isolation: isolate. The most precise tool is isolation: isolate β€” it creates a new stacking context without any visual side effects. When a modal or tooltip is failing to appear in front of a parent with transform: translateY(), it's because the transform created a stacking context that traps the child's Z-index. The fix is to render portals (popups, tooltips, modals) outside of transformed ancestors, at the document root.

Practical note

CSS custom properties for Z-index layers prevent wars: --z-dropdown: 10; --z-modal: 100; --z-toast: 1000. Any component picks from the named scale rather than guessing a number.

Q04Mid

Explain CSS Specificity and how Cascade Layers (@layer) change the way you resolve conflicts.

CSS specificity determines which rule 'wins' when multiple rules target the same element. It's calculated as a three-part score: ID selectors (1,0,0), class/attribute/pseudo-class selectors (0,1,0), element/pseudo-element selectors (0,0,1). Inline styles override all, !important overrides inline styles (mostly). This creates a maintenance problem in large codebases: specificity wars where selectors get increasingly nested or !important gets abused. Cascade Layers (@layer) add an explicit priority dimension above specificity: styles in a later-declared layer always win over styles in an earlier-declared layer, regardless of specificity. You declare the layer order: @layer reset, defaults, components, utilities; β€” then styles in components always beat defaults, and utilities always beat components. This makes specificity mostly irrelevant within a well-designed layer system and eliminates the need for !important in most cases.

Practical note

Tailwind CSS v4 uses @layer internally to ensure utility classes always win over component styles. Understanding this is the key to debugging why a Tailwind class isn't applying.

03
Module 03Advanced

Browser Rendering & CSS Performance

Frontend performance is increasingly a hiring signal at product companies. Questions here test whether you understand how the browser processes your HTML and CSS, and whether you can identify the specific rendering phases your code touches.

Q01Senior

Walk me through the browser's rendering pipeline from HTML bytes to pixels on screen.

The browser performs a sequence of distinct steps to convert your HTML and CSS into what the user sees. First, it fetches and parses the HTML, building the DOM (Document Object Model) β€” a tree of nodes. Concurrently, it fetches and parses CSS, building the CSSOM (CSS Object Model). These are independent trees that cannot be combined until both are complete β€” render-blocking CSS is the performance consequence. The DOM and CSSOM are merged into the Render Tree, which contains only visible nodes with their computed styles. The browser then calculates the Layout (also called Reflow) β€” the exact position and size of each element on the page. Next comes Paint: filling in pixels β€” colours, text, images, borders. Finally, Compositing: the browser may split the page into layers (for elements with GPU-accelerated properties like transform and opacity) and composites them on the GPU. Understanding this pipeline is how you diagnose performance issues: layout thrashing, paint storms, and composition bottlenecks each have different causes and different fixes.

Practical note

Reading offsetHeight after a style change forces a synchronous layout (the browser must recalculate positions to answer). In a loop, this causes 'layout thrashing'. The fix: batch reads together and writes together.

Q02Advanced

What does 'content-visibility: auto' do and when does it produce meaningful performance gains?

content-visibility: auto instructs the browser to skip the layout and paint work for elements that are outside the visible viewport. The browser still reserves space for the element (using the contain-intrinsic-size hint to estimate the element's dimensions), but defers all rendering computation until the user scrolls the element into view. On a long page β€” a technical blog with dozens of sections, a dashboard with many data panels β€” this can reduce initial render time dramatically because the browser only fully renders what's visible. The performance gain is proportional to the amount of off-screen content and its rendering complexity. For a page with 20 equal-height sections where only 2 are visible, content-visibility: auto can cut rendering work by 90%. The limitation: it has no effect on images or iframes (which have their own lazy-loading mechanisms), and the contain-intrinsic-size estimate must be accurate to prevent layout shifts when items come into view.

Practical note

Google's research on Wikipedia pages found content-visibility: auto reduced rendering time by 7x on long article pages. Pair it with contain-intrinsic-size: auto to let the browser cache the previously rendered size.

Q03Senior

What CSS properties trigger Layout, Paint, or Composite respectively β€” and why does it matter for animations?

Layout (Reflow) is triggered by properties that affect the document flow: width, height, margin, padding, font-size, top, left (with position), display. Layout is the most expensive operation because it cascades β€” changing one element's size may cause siblings and parents to recalculate. Paint is triggered by visual properties that don't affect layout: colour, background, border-radius, box-shadow. It's cheaper than layout but still CPU-bound. Composite is triggered by transform and opacity β€” these properties are handled on the GPU without touching layout or paint. This is why the golden rule for smooth animations is: only animate transform and opacity. An animation that changes width causes layout + paint + composite on every frame. An animation that changes transform causes only compositing β€” 60fps on any modern device with no jank. For triggering GPU compositing proactively (e.g., before a transform animation starts), will-change: transform creates a dedicated compositor layer in advance.

Practical note

A slide-in animation: never animate left/right. Animate transform: translateX() instead β€” visually identical, but composite-only vs layout+paint+composite. The difference is the gap between 60fps and jank.

Q04Mid

What is the ':has()' selector, why is it called the 'parent selector', and what problems does it solve?

:has() allows you to select an element based on whether it contains a specific descendant or sibling. It's called the parent selector because it breaks CSS's longstanding limitation of selecting only downward in the DOM β€” previously, CSS could style children based on parents but never style parents based on children. .card:has(img) selects cards that contain an image. form:has(:invalid) styles the form when any input is invalid. nav:has(+ .hero) targets a nav immediately followed by a hero section. This eliminates entire categories of JavaScript that were used solely to add classes based on DOM conditions β€” most 'active state' and 'conditional layout' class toggles can now be replaced with :has(). Browser support reached baseline availability in 2023 and is now safe for production without fallbacks in all major browsers.

Practical note

A card grid where cards with images should have different layout than text-only cards: .card:has(.card-image) { grid-template-rows: 200px 1fr; } β€” no JavaScript class toggling needed.

04
Module 04Advanced

Accessibility & Semantic Best Practices

Accessibility questions assess whether you build inclusively by default or treat a11y as an afterthought. Senior candidates are expected to know WCAG standards, ARIA correctly, and the practical consequences of poor accessibility decisions.

Q01Mid

Why is using <button> always better than <div onClick> for interactive elements?

A native <button> element comes with four accessibility and usability behaviours built in, for free. First, it is natively focusable via Tab β€” no tabindex attribute needed. Second, it responds to keyboard activation: pressing Space or Enter on a focused button fires the click event β€” the same expectation users have from operating systems and other apps. Third, it communicates role='button' to the browser's accessibility tree automatically, which screen readers announce as 'button'. Fourth, it participates in form submission and can be disabled natively. A <div onClick> provides none of these. To make it equivalent, you need tabindex="0", role="button", onKeyDown handlers for Space and Enter, and management of the disabled state β€” and you'll inevitably miss edge cases. The rule is simple: if it does something when clicked, use <button>. If it navigates, use <a>.

Practical note

Audit tool: run axe DevTools or Lighthouse on any page and look for 'interactive elements must be focusable' and 'elements must have discernible text' violations β€” both almost always trace back to clickable divs.

Q02Advanced

Explain ARIA roles, states, and properties β€” and when should you NOT use ARIA?

ARIA (Accessible Rich Internet Applications) is a specification that extends HTML with attributes to communicate semantics to assistive technologies. Roles (role="dialog", role="tablist") override or add to an element's semantic role. States (aria-expanded, aria-checked, aria-disabled) communicate dynamic UI states that change at runtime. Properties (aria-label, aria-labelledby, aria-describedby) add or override accessible names and descriptions. The first rule of ARIA is: don't use ARIA if a native HTML element can do the job. <button> is always better than <div role="button">. <select> is always better than a custom dropdown with role="listbox". ARIA doesn't add behaviour β€” it only modifies what assistive technology sees. A div with role="checkbox" still needs JavaScript to manage keyboard interaction, state toggling, and focus management. Use ARIA to enhance custom widgets that have no HTML equivalent, not to label elements you should have structured semantically from the start.

Practical note

aria-live="polite" is one of ARIA's most valuable tools: it tells screen readers to announce dynamically injected content (form validation errors, toast notifications, search results) without requiring a page reload.

Q03Mid

What is the difference between the <picture> element and the srcset attribute on <img>?

Both handle responsive images, but they solve different sub-problems. The srcset attribute on <img> handles resolution switching: the same image at different sizes for different screen pixel densities. The browser picks the most appropriate size from the list based on the device's DPR and viewport width. This is the right tool when the image content is the same but the optimal file size varies. The <picture> element handles art direction: serving fundamentally different images for different contexts. A landscape hero image on desktop that is cropped to a portrait orientation on mobile, or a photograph that becomes a simplified SVG icon at small sizes β€” these require <picture> with separate <source> elements for each context. In both cases, lazy loading (loading="lazy") and explicit width/height attributes (to reserve space and prevent CLS) are mandatory for Largest Contentful Paint optimisation.

Practical note

If you can solve it with srcset, use srcset β€” simpler and more cacheable. Reserve <picture> for when the visual content genuinely needs to change between breakpoints.

05
Module 05Senior

Modern CSS: Scroll Animations, View Transitions & Design Systems

These features represent the frontier of CSS capability in 2026. Questions here test whether you're actively tracking the evolution of the platform and can articulate not just what these features do but the class of problems they replace.

Q01Senior

How do CSS Scroll-Driven Animations work and what JavaScript patterns do they replace?

Scroll-Driven Animations link CSS animation progress to a scroll timeline rather than wall-clock time. With animation-timeline: scroll(), an animation runs from 0% to 100% as the user scrolls from top to bottom of the page. With animation-timeline: view(), an animation is tied to an element's position within the viewport β€” it progresses as the element enters and exits view. This replaces IntersectionObserver + class toggle patterns that triggered CSS transitions on scroll entry, and replaces requestAnimationFrame loops that manually calculated scroll position and set CSS custom properties. Both previous approaches ran on the main thread β€” scroll events are high-frequency and adding even small JS work on each event can cause dropped frames. Scroll-Driven Animations run entirely off the main thread on the compositor, which is why they remain smooth even when the main thread is under load.

Practical note

A reading progress bar at the top of an article: @keyframes progress { from { transform: scaleX(0); } to { transform: scaleX(1); } } .progress-bar { animation: progress linear; animation-timeline: scroll(root); } β€” zero JavaScript.

Q02Senior

What is the View Transitions API and how does it enable native-app-like page transitions on the web?

The View Transitions API allows you to animate the transition between two DOM states β€” including full page navigations β€” with a simple CSS + JS API. You call document.startViewTransition(() => updateDom()) and the browser automatically captures a snapshot of the current state, performs your DOM update, then animates from the old snapshot to the new state using a cross-fade by default. You can customise the animation with CSS targeting ::view-transition-old and ::view-transition-new pseudo-elements. For multi-page apps (MPAs), the Navigation API extension enables cross-page view transitions without JavaScript β€” the browser handles the transition between full HTML pages. This replaces heavy JavaScript animation libraries that were the only way to achieve smooth page transitions previously. The visual result β€” content morphing seamlessly between pages β€” was previously achievable only in native apps or with complex shared-element transitions in React Native.

Practical note

To animate a specific shared element (a card thumbnail that expands to a detail page), give it view-transition-name: card-thumbnail. The browser will automatically track and animate that element between its positions on the two pages.

Q03Senior

What are CSS Logical Properties and when should you use them over physical properties?

Physical CSS properties use screen-oriented directions: margin-left, padding-top, border-right. Logical properties use writing-mode-relative directions: margin-inline-start, padding-block-start, border-inline-end. In left-to-right languages, margin-inline-start equals margin-left. In right-to-left languages (Arabic, Hebrew), margin-inline-start automatically becomes margin-right β€” no separate direction-specific CSS needed. For products with international audiences, using logical properties from the start eliminates an entire class of RTL (right-to-left) layout bugs. block refers to the block axis (vertical in horizontal writing modes), and inline refers to the inline axis (horizontal in horizontal writing modes). Beyond i18n, logical properties also make code self-documenting: padding-block-start is clearly 'padding at the start of the block direction', which is more meaningful than padding-top in a potential vertical writing mode context.

Practical note

Start the habit on margin, padding, and inset properties first β€” these have the highest RTL impact. text-align: start and end are the most commonly missed: always prefer them over text-align: left/right.

Deep DiveContainer Queries in Practice

Container Queries vs Media Queries

The key insight: a media query asks β€œhow wide is the viewport?”. A container query asks β€œhow wide is my parent?” β€” making components truly portable.

Media Query (component tied to viewport β€” breaks in sidebar)

/* Card only knows about the viewport β€” not its context */
@media (min-width: 600px) {
  .card { display: grid; grid-template-columns: 200px 1fr; }
}
/* Problem: card in a 300px sidebar still gets grid layout */

Container Query (component adapts to its own space β€” always correct)

.card-container {
  container-type: inline-size; /* enable containment */
}

@container (min-width: 400px) {
  .card { display: grid; grid-template-columns: 200px 1fr; }
}
/* Card stacks in sidebar (< 400px), goes side-by-side in main (β‰₯ 400px) */
Browser support

Container Queries have full cross-browser support since Chrome 105, Safari 16, Firefox 110. Safe for production without fallbacks.

Named containers

container-name: sidebar allows @container sidebar (min-width: 300px) β€” targeting a specific ancestor when multiple containment contexts exist.

Style queries

The next frontier: @container style(--theme: dark) lets components react to CSS custom property values on their container β€” conditional theming without class toggling.

HTML & CSS FAQ

What is the difference between Flexbox and CSS Grid?

Flexbox is a one-dimensional layout system for distributing items along a single row or column. CSS Grid is two-dimensional, managing items across both rows and columns simultaneously. Use Flexbox for component-level alignment (nav bars, button groups), Grid for page-level structure (layouts with headers, sidebars, main content). They compose: a Grid layout can contain Flexbox components.

What are CSS Container Queries?

Container Queries allow elements to style themselves based on the size of their parent container rather than the viewport. Declare container-type: inline-size on a parent, then use @container (min-width: 400px) { } to apply styles when that parent reaches 400px. This makes components truly self-contained and reusable across different layout contexts.

Why should you always animate transform and opacity instead of layout properties?

Properties like width, height, margin, and top trigger Layout (reflow), which cascades to siblings and causes the browser to recalculate the entire document flow on every frame. transform and opacity only require the Composite step β€” handled by the GPU, completely bypassing layout and paint. This is why transform animations run at 60fps while width animations cause jank.

What is the CSS :has() selector?

:has() selects an element based on its descendants. .card:has(img) selects cards containing images. form:has(:invalid) styles the form when any input is invalid. It's called the 'parent selector' because it allows styling parents based on their children β€” a pattern previously impossible in CSS without JavaScript. It's available in all major browsers since 2023.

How do CSS Cascade Layers work?

Cascade Layers (@layer) add an explicit priority level to CSS that sits above specificity. Styles in a later-declared layer always win over styles in an earlier layer regardless of selector complexity. @layer reset, components, utilities; means utility styles always override component styles. This eliminates specificity wars and reduces the need for !important in large codebases.

Related Interview Guides

Build with Purpose.

A great UI is clean, fast, and accessible to everyone. Use our Developer Tools to optimise and validate your frontend code.

Last updated June 2026 Β· Covers CSS Baseline 2025+

Feedback

Live