Go2X
Go2X

India's leading training and placement platform offering hands-on learning, powered by 200+ IITian and industry experts, connecting students to 1,000+ hiring and referral partners.

Let's Go2X

Stay updated with Go2X

Get course updates, interview tips, and career insights delivered to your inbox.

Contact Us

Address

1st Floor, Plot No 332, Phase IV, Udyog Vihar,
Sector 19, Gurugram, Haryana 122015

Email

support@go2x.live

Phone

+91 94107 10085

© 2025 Go2X Private Limited. All rights reserved.

Made with 🧡 and a lot of late nights.

Interview ExperiencesBlogsAbout UsPrivacy PolicyTerms of ServiceRefund Policy

On This Page:

Frontend

HTML & CSS Interview Questions

60+ HTML and CSS interview questions and answers - covering semantic markup, accessibility, forms, responsive images, the box model, Flexbox, Grid, custom properties, cascade layers, container queries, and browser rendering - organized into Beginner, Intermediate, and Advanced levels.

Jan 30, 2026
50 mins read

I. Beginner Level

1. What happens in the browser from the moment you write <!DOCTYPE html> to the page actually rendering, and why does that first line matter?

Think of <!DOCTYPE html> as the browser asking, "what era am I in?" before it does anything else. If it's missing, the browser assumes it might be dealing with old, broken markup and switches into quirks mode - a legacy compatibility mode that calculates box sizes and layouts differently from modern standards mode.
That's a real bug source: perfectly normal CSS can render subtly wrong just because someone deleted the doctype line.
Once that's decided, rendering is a pipeline: HTML is parsed into the DOM, CSS is parsed into the CSSOM, the DOM and CSSOM are combined into a render tree, then layout figures out where everything goes, and paint fills in the pixels.

2. How would you structure a blog post (title, author, date, content) using semantic tags instead of wrapping everything in <div>s?

You could build the whole page out of <div>s, but you'd be throwing away functionality the browser gives you for free. Use <article> for the post itself, a heading tag for the title, <time datetime="..."> for the date, and <p> for the body.
The payoff: <time> is machine-readable (a search engine can show "posted 3 days ago" accurately), and <article> tells a screen reader "this is one self-contained thing." With <div>s, you'd have to manually add ARIA roles to recover meaning that's already free with semantic tags.

html
1<article>
2  <header>
3    <h1>Understanding Web Standards</h1>
4    <p>By <address>Alex Rivera</address> on
5      <time datetime="2026-03-15">March 15, 2026</time></p>
6  </header>
7  <p>Blog post content goes here...</p>
8</article>

3. What's the actual difference between <span> and <div>?

Imagine building a sidebar of stacked cards and accidentally wrapping each card in <span> instead of <div>. Spans are inline - they don't respect width/height and don't force a new line. Instead of three cards stacked vertically, all your card content would run together like one paragraph, ignoring your CSS width rules, until you override it with display: block.

4. Are <b> and <strong> the same thing? What about <i> versus <em>?

Both pairs look visually identical, but semantically they are different:

  • <b> vs <strong>: <b> bolds text without adding importance. <strong> indicates strong importance, urgency, or warning, and screen readers often emphasize it with an altered voice pitch.
  • <i> vs <em>: <i> italicizes text (used for technical terms, foreign phrases, thoughts). <em> adds verbal stress emphasis, altering how the sentence is meant to be read.

5. What attributes would you add to a sign-up form so the browser validates it before it even hits the server?

Use required on fields, type="email" for format checking, type="password" to mask input, minlength/maxlength for length rules, and pattern for any custom requirement (like "must contain a number"). The browser blocks submission and shows a native error bubble before any JavaScript or server round-trip runs.

html
1<form>
2  <input type="text" name="username" required minlength="3">
3  <input type="email" name="useremail" required>
4  <input type="password" name="userpass" required minlength="8"
5    pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}">
6  <button type="submit">Sign Up</button>
7</form>

6. What's the purpose of the alt attribute on an <img> tag?

alt is the image's stand-in for anyone who can't see it. Skip it entirely, and a screen reader might just read out the filename (like IMG_4821_final_v2.jpg).

  • alt="Golden Retriever running": the screen reader announces the description, and the browser displays that text if the image fails to load.
  • alt="" (empty): marks the image as purely decorative, telling screen readers to skip it.

7. What's the difference between a relative and an absolute file path, and when would you choose one over the other?

  • Absolute path: the full path, either from the domain root (/images/logo.png) or a complete URL (https://example.com/images/logo.png). It always points to the same place no matter where the referencing file lives.
  • Relative path: describes the location relative to the current file - e.g. images/logo.png (same folder or subfolder), ../css/style.css (up one directory, then into css), or ./style.css (same folder; ./ is optional).

When to use which:

  • Relative for anything inside your own project. If you move the whole project to a new domain or folder, relative links keep working since they don't hardcode a domain - the default choice for local CSS, images, and internal links.
  • Absolute when linking to a resource on a different domain (a CDN-hosted font, an external image, an API endpoint) - there's no other option there. Also useful as root-relative paths like /images/logo.png in larger projects, since the link won't break no matter how deep the current page is nested.

8. How would you make a numbered list start from 5 instead of 1, in plain HTML?

Use the start attribute on <ol>.

html
1<ol start="5">
2  <li>Fifth item</li>
3  <li>Sixth item</li>
4  <li>Seventh item</li>
5</ol>

9. What's the difference between id and class attributes, and why pick one over the other?

  • id: must be unique - only one element per page can have a given id. Used for styling a single specific element, as a JavaScript hook (getElementById), or as an in-page anchor target (#section).
  • class: can be applied to multiple elements, and one element can have multiple classes. Used for styling groups of elements that share the same look or behavior.

Specificity: an id selector (#header) is more specific than a class selector (.header), so id styles override class styles when both apply - which makes CSS harder to override later and is a common source of "why won't my style apply" bugs, so most developers avoid styling with ids for this reason.
Reusability: classes are meant to be reused across many elements (e.g. .btn on every button); ids are inherently one-off, so using an id for something you'll want to reuse forces you to duplicate the id, which is invalid HTML.
Convention: the common pattern is classes for styling and ids reserved for JS hooks, unique page landmarks, or anchor links - keeping CSS specificity flat and predictable.

10. What are void (self-closing) elements in HTML? Can you name a few?

Void elements are HTML elements that can never have children and don't need a closing tag, because there's no content to close around. Writing a closing tag or a self-closing slash on them is optional in HTML (though XHTML required the slash) - the browser treats <br> and <br /> identically.

  • <br> - line break
  • <hr> - thematic break/horizontal rule
  • <img> - image
  • <input> - form input
  • <meta> - document metadata
  • <link> - external resource link
  • <source> - media source for <picture>/<video>/<audio>

11. What are some common <input> types, and when would you use each?

  • text - free-form single-line text, like a name field
  • email - validates a basic email shape and shows an email-optimized mobile keyboard
  • password - masks the input visually
  • number - restricts input to numeric values and shows a numeric spinner/keyboard
  • checkbox - independent on/off toggles; more than one can be selected in a group
  • radio - mutually exclusive options within the same name group; only one can be selected
  • date - a native date picker
  • file - lets the user pick a file to upload

Picking the right type isn't just semantic - it changes which mobile keyboard shows up, what built-in validation the browser applies, and how assistive technology announces the field.

12. What are meta tags? What do charset and description do?

Meta tags live in the <head> and provide metadata about the document that isn't rendered on the page itself, but is used by the browser, search engines, or social platforms.

html
1<meta charset="UTF-8">
2<meta name="description" content="A short summary of the page for search results.">
  • charset: tells the browser which character encoding to use when decoding the document's bytes into text. UTF-8 covers virtually every character in every language, so leaving it out or getting it wrong can turn quotes, accents, or emoji into garbled text.
  • description: a short summary search engines often display under the page title in results - it doesn't affect ranking directly, but a good one can improve click-through rate.

13. If three rules target the same paragraph - one by tag, one by class, one by ID - which one wins, and why?

The rule targeting the ID wins because of specificity - a weighted scoring system CSS uses to decide which rule applies when multiple rules target the same element. Think of it as a point system:

  • Tag selector (p) → specificity 0,0,0,1
  • Class selector (.highlight) → specificity 0,0,1,0
  • ID selector (#intro) → specificity 0,1,0,0

Higher tuple wins, compared left to right. An ID always beats a class, which always beats a tag, regardless of source order - the browser doesn't care which rule was written last unless there's a tie. (An inline style="" attribute beats all three, and !important overrides everything, but that's a separate escape hatch, not "normal" specificity.)

14. Can you explain the CSS box model? What are the four layers, and which affect visible size?

Every HTML element on a page is a rectangular box, and the box model defines the layers of that box. Imagine it like a framed photograph on a wall:

  • Content - the photo itself (text, image, or child elements), controlled by width and height.
  • Padding - the matting around the photo, creating breathing room between the art and the frame.
  • Border - the physical frame around the matting and photo.
  • Margin - the empty wall space outside the frame, pushing other pictures away.

By default, the visible size of an element - the physical space it occupies on screen - is Content + Padding + Border. Margin affects layout by pushing other elements away, but doesn't change the box's own size.

15. What's the actual difference between margin and padding, and can you think of a bug caused by mixing them up?

  • Padding: space inside the border, part of the element itself. Adds to the background/click area.
  • Margin: space outside the border, between the element and its siblings. Not clickable, no background.

A classic bug: if you use margin to make a button larger instead of padding, the visual space between buttons grows, but the actual clickable area (the content box) stays tiny. Users click the empty space inside the button's visual footprint and nothing happens, because margins aren't interactive. Switching that spacing to padding expands the clickable target.

16. What does box-sizing: border-box do, and why do so many developers add it globally?

box-sizing: border-box changes the box-model math, forcing the browser to include padding and borders within an element's declared width and height.
By default, CSS uses box-sizing: content-box. If you set a <div> to 100% wide and then add 20px of padding, it becomes 100% + 40px wide, blowing past its container and causing horizontal scrolling.
With border-box, that same 100% width becomes the absolute maximum size - add 20px of padding and the browser shrinks the inner content area to make room for it instead. Developers apply this globally because it makes sizing predictable: a 300px box stays exactly 300px wide, no matter how much padding or border you add.

17. What's the difference between inline, block, and inline-block elements, and how would you make a <span> behave like a block element?

  • Block (display: block): takes up the full available width and starts on a new line (like <p> or <div>). You can set explicit widths and heights.
  • Inline (display: inline): only takes up as much width as its content requires and sits on the same line as surrounding text (like <a> or <span>). Inline elements ignore vertical margin, top/bottom padding layout effects, and explicit width/height.
  • Inline-block (display: inline-block): the best of both worlds - sits inline with text so it doesn't break onto a new line, but internally behaves like a block, letting you set explicit width, height, and vertical spacing.
css
1span {
2  display: block;
3}

18. What happens when two elements have vertical margins next to each other? Explain margin collapsing.

When two vertical margins meet - say, the bottom margin of one block element and the top margin of the next sibling - they don't add together. Instead, the larger of the two wins and becomes the actual gap.
Example: element A has margin-bottom: 30px, element B has margin-top: 20px. You might expect a 50px gap, but you get 30px instead.

This only happens with vertical margins in normal document flow, between:

  • adjacent siblings
  • a parent and its first/last child (if no padding/border/content separates them)
  • an empty element's own top and bottom margin

It doesn't happen with horizontal margins, padding, elements with overflow other than visible, flex/grid items, or absolutely positioned elements - one reason people reach for flexbox with gap instead of margins for spacing control.

19. What's the difference between a class selector and a pseudo-class like :hover?

A class selector (like .btn-primary) applies styling statically, based on the HTML structure you authored - the browser applies it as soon as it reads the class name in the DOM. A pseudo-class (like :hover, :focus, or :active) applies styling dynamically, based on the element's state or how the user is interacting with it, letting CSS act conditionally without JavaScript. You'd need :hover to give visual feedback that an element is interactive, such as darkening a button's background when the mouse moves over it.

css
1.btn {
2  background-color: blue;
3  transition: background-color 0.3s;
4}
5
6/* Changes the background to dark blue only when the mouse is over it */
7.btn:hover {
8  background-color: darkblue;
9}

20. What's the difference between display: none and visibility: hidden? If QA says an element is "invisible but still clickable," which one would you suspect?

  • display: none completely removes the element from the document flow - it's invisible, takes up zero space, and surrounding elements close the gap as if it never existed.
  • visibility: hidden makes the element invisible but leaves its physical box in the document flow - it still takes up its exact width and height, preserving the layout.

So for "invisible but still clickable," neither of those is actually the culprit. The more likely suspects are:

  • opacity: 0 - fully transparent, but still occupies space and remains clickable (this is the classic match for that exact symptom)
  • A correctly positioned element with a z-index/stacking issue, sitting invisibly on top of another clickable element
  • color: transparent on text with a visible/clickable container behind it

21. What's the difference between a pseudo-element like ::before and a pseudo-class like :hover?

A pseudo-class selects an existing element based on its state or position - :hover, :focus, :first-child, :nth-child(2) - it targets something already in the DOM. A pseudo-element lets you style, or even create, a specific sub-part of an element that doesn't exist as its own DOM node - ::before and ::after insert generated content before or after an element's actual content, and ::first-line or ::placeholder target a portion of an element's rendering.

css
1.required-field::after {
2  content: " *";
3  color: red;
4}

Syntax convention: modern CSS uses a single colon for pseudo-classes (:hover) and a double colon for pseudo-elements (::before), though browsers still accept the old single-colon form for the original four pseudo-elements for backward compatibility.

22. What are CSS combinators - descendant, child, adjacent sibling, and general sibling selectors?

Combinators describe the relationship between two selectors, letting you target elements based on their position relative to another element:

css
1.card p { }      /* descendant: any <p> anywhere inside .card */
2.card > p { }    /* child: only <p> that are direct children of .card */
3h2 + p { }       /* adjacent sibling: the <p> immediately after an h2 */
4h2 ~ p { }       /* general sibling: every <p> that follows an h2, same parent */
  • Descendant (space): matches an element nested anywhere inside another, at any depth.
  • Child (>): matches only direct children, one level deep - narrower and usually faster to reason about than a descendant selector.
  • Adjacent sibling (+): matches an element that comes immediately after another, sharing the same parent.
  • General sibling (~): matches any element that comes after another and shares the same parent, not just the immediate next one.

23. What are the different ways to specify color in CSS?

  • Named colors: keywords like red, steelblue, or transparent - readable, but a limited palette.
  • Hex: #ff0000 (or the shorthand #f00) - compact, and the most common format pulled from design tools.
  • RGB / RGBA: rgb(255, 0, 0) specifies red, green, and blue channels directly; the alpha variant, rgba(255, 0, 0, 0.5), adds transparency.
  • HSL / HSLA: hsl(0, 100%, 50%) specifies hue, saturation, and lightness - often more intuitive for tweaking a color's shade or brightness than RGB, since you can adjust lightness without recalculating all three RGB channels.

Modern CSS also supports space-separated syntax without commas, e.g. rgb(255 0 0 / 50%), which makes it easier to add a transparency value to an existing color function.

II. Intermediate Level

1. How would you use semantic tags like <header>, <nav>, <main>, and <footer> to help a screen reader user navigate efficiently?

Screen reader users don't read a page top-to-bottom the way sighted users skim it - they navigate by landmarks. Most screen readers (NVDA, JAWS, VoiceOver) let you jump between regions with a keypress: "next landmark," "next heading." If everything is <div class="header">, <div class="nav">, that shortcut does nothing - the assistive technology has no idea those divs mean anything semantically.

With real elements:

  • <header> → announced as a "banner" landmark, jumpable directly
  • <nav> → announced as "navigation"; label multiple navs, e.g. <nav aria-label="Main">, <nav aria-label="Footer">
  • <main> → announced as "main" - lets a screen reader user skip straight past header/nav to the actual content on every page load, instead of tabbing through 20 links first
  • <footer> → the "contentinfo" landmark

In practice, a screen reader user can bring up a landmarks list - like a sighted user's mental map of the page - and jump directly to "main" or "navigation" instead of linearly tabbing through everything. <div>s give them nothing to jump to; they're forced to read serially.
One gotcha: <header> and <footer> only count as landmarks when they're not nested inside <article> or <section> - a <header> inside a card component is just a header for that card, not a page banner.

2. How would you handle a data table with merged cells (like a schedule) and keep it accessible for screen readers?

Native HTML tables handle merged cells with rowspan/colspan, but a spanning cell breaks the simple "one header row per column" association screen readers rely on, so you need to be explicit about which headers apply to which cell.

html
1<table>
2  <thead>
3    <tr>
4      <th id="time">Time</th>
5      <th id="mon">Monday</th>
6      <th id="tue">Tuesday</th>
7    </tr>
8  </thead>
9  <tbody>
10    <tr>
11      <th id="t1" scope="row">9-10am</th>
12      <td headers="t1 mon" rowspan="2">Team Standup</td>
13      <td headers="t1 tue">Design Review</td>
14    </tr>
15    <tr>
16      <th id="t2" scope="row">10-11am</th>
17      <td headers="t2 tue">1:1</td>
18    </tr>
19  </tbody>
20</table>

When rowspan or colspan merges cells, the visual grid breaks its linear predictability, so you must explicitly link data cells to their headers:

  • Scope attributes: for simpler merges, use <th scope="row"> and <th scope="col"> to tell the screen reader which direction the header applies to. Use scope="rowgroup" or scope="colgroup" for grouped rows/columns.
  • ID and headers attributes: for complex schedules where a cell relates to multiple disjointed headers, give every <th> a unique id, then add headers="id1 id2" on the merged <td>. When a screen reader user lands in that cell, it reads out the associated header text, no matter how the visual table is drawn.

3. What's the difference between <section> and <article>?

The test: can this content stand alone, be pulled out, and still make sense? If yes → <article>. If it's a thematic grouping that only makes sense in context → <section>.

On a news site, for example:

  • Each individual news story is an <article> - it has its own headline, byline, and body, and would still make sense if syndicated to another site or an RSS reader
  • A "Top Stories" block on the homepage that groups multiple <article>s is a <section> - it's not independently meaningful content, it's a themed container
  • Inside one article, distinct parts like "Analysis" and "Reader Comments" could be <section>s within the <article>, since they only make sense as a piece of that story

Practical signal: articles get their own heading and are things you could self-syndicate; sections are how you organize content that stays put.

4. How do you make form validation error messages actually announced to a screen reader, not just shown visually?

Red text alone is invisible to a screen reader user - the error needs to be programmatically tied to the field and actively announced.

  • Link the error to the input: give the error message container a unique ID (e.g. id="email-error"), and add aria-describedby="email-error" on the <input>. When focused, the screen reader reads the label, the input type, then the error.
  • Announce dynamically: if the error appears without a page reload (e.g. on blur), wrap it in a container with aria-live="polite" so it's announced as soon as the user finishes their current action.
  • Invalid state: add aria-invalid="true" to the input so the screen reader explicitly announces the current value is incorrect.

5. What do the defer and async attributes do on a <script> tag, and why does script placement matter?

By default, when the browser hits a <script> tag, it stops parsing HTML, downloads the script, and executes it before continuing - a render-blocking bottleneck.

  • async: the script downloads in the background while HTML continues parsing, but the moment it finishes downloading, HTML parsing pauses to execute it. Execution order isn't guaranteed. Best for independent scripts like analytics.
  • defer: the script downloads in the background, but execution waits until the entire HTML document is parsed, and scripts run in the exact order they appear. Best for scripts that rely on the DOM or on each other.

6. If you build a custom dropdown instead of using a native <select>, what accessibility features do you need to replicate manually?

A native <select> gives you keyboard support and screen reader context for free. Building a custom dropdown means manually rebuilding:

  • Roles: role="combobox" on the container, role="listbox" on the menu, role="option" on each item.
  • Keyboard navigation: JavaScript listening for ArrowDown/ArrowUp to move focus between options, Enter or Space to select, and Escape to close the menu.
  • Focus management: either physically move DOM focus to the option using tabindex="-1", or use aria-activedescendant on the combobox to point to the currently focused option's ID.
  • State attributes: toggle aria-expanded="true/false" on the combobox when it opens/closes, and aria-selected="true/false" on the active option.

7. What's the purpose of <meta name="viewport">, and what breaks on mobile without it?

html
1<meta name="viewport" content="width=device-width, initial-scale=1">

Without it, mobile browsers assume the page was built for desktop and render it at a fixed virtual width (often 980px), then shrink the whole thing to fit the screen. The result: tiny, unreadable text; users pinching and panning to read anything; layouts that don't respond to the real device width. Responsive media queries become useless too, since the browser is still evaluating them against that fake 980px viewport, not the real device width. This line tells the browser "render at the device's real width, at 1:1 scale" - the switch that makes responsive design possible at all on mobile.

8. How would you structure a multi-step form like a checkout flow using fieldset and legend, and why does it matter for accessibility?

html
1<form>
2  <fieldset>
3    <legend>Shipping Address</legend>
4    <label for="street">Street</label>
5    <input id="street" name="street">
6    <label for="city">City</label>
7    <input id="city" name="city">
8  </fieldset>
9
10  <fieldset>
11    <legend>Payment Details</legend>
12    <label for="card">Card Number</label>
13    <input id="card" name="card">
14  </fieldset>
15
16  <fieldset>
17    <legend>Delivery Method</legend>
18    <label><input type="radio" name="delivery" value="standard"> Standard</label>
19    <label><input type="radio" name="delivery" value="express"> Express</label>
20  </fieldset>
21</form>

<fieldset> groups related controls, and <legend> gives that group an accessible name. When a screen reader user tabs into a radio button inside that fieldset, it announces the legend along with the field label, so a lone "Standard" becomes "Delivery Method: Standard radio button, 1 of 2" - otherwise "Standard" alone is meaningless out of visual context. It's the multi-field equivalent of <label for> for a single input.

For a genuinely multi-step flow (separate screens, not just visual sections), also add:

  • An announcement of the current step, e.g. a visually-hidden or aria-live region saying "Step 2 of 4: Payment"
  • Moving focus to the new step's heading when the step changes, so screen reader users aren't left focused on a "Next" button that's no longer relevant

9. What's the difference between localStorage, sessionStorage, and cookies?

  • localStorage: stores data permanently on the user's device (up to ~5MB) until explicitly cleared. Entirely client-side. Use case: non-sensitive preferences like a dark mode toggle, or saving an unfinished cart.
  • sessionStorage: works like localStorage, but is wiped the moment the user closes the browser tab or window. Use case: intermediate state of a multi-step form, or preserving UI state during a single browsing session.
  • Cookies: store small amounts of data (up to 4KB) and are sent back to the server via HTTP headers with every request to that domain. Use case: session IDs or authentication tokens, so the server knows who the user is on subsequent page loads.

10. What's the difference between innerHTML, textContent, and innerText?

  • innerHTML: gets or sets an element's content as an HTML string, so it parses any tags you assign to it. Assigning untrusted user input to innerHTML is a classic XSS vector, since embedded <script> or event-handler attributes can execute.
  • textContent: gets or sets the raw text of an element and all its descendants, ignoring styling entirely, including text inside hidden elements. Assigning to it treats the value as plain text, so it's the safe choice when inserting user-provided content.
  • innerText: similar to textContent, but it's aware of rendered styling - it won't return text from elements hidden with display: none, and it respects things like line breaks introduced by CSS. It's also more expensive, since computing it can trigger a reflow.

11. How would you serve responsive images using srcset, sizes, and <picture>?

srcset lets the browser choose the best image resolution for the current device out of a list of candidates, instead of forcing every device to download the same large file.

html
1<img
2  src="photo-800.jpg"
3  srcset="photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w"
4  sizes="(max-width: 600px) 100vw, 50vw"
5  alt="Mountain landscape at sunset"
6  loading="lazy"
7>
  • srcset lists candidate images with their intrinsic widths (400w, 800w); sizes tells the browser how wide the image will actually be displayed at different viewport widths, so it can pick the smallest candidate that's still sharp enough.
  • <picture> goes further, letting you swap the image source entirely - not just resolution - based on media conditions or format support, useful for serving a modern format like WebP with a fallback.
  • loading="lazy" tells the browser to defer loading an offscreen image until the user scrolls near it, which is a free, native performance win for long pages.

12. What is ARIA, and when should you actually use it?

ARIA (Accessible Rich Internet Applications) is a set of attributes - roles, states, and properties - that let you describe the semantics of an element to assistive technology when native HTML can't express it on its own, such as a custom-built tab panel or a live-updating notification region.

html
1<div role="tablist">
2  <button role="tab" aria-selected="true">Profile</button>
3  <button role="tab" aria-selected="false">Settings</button>
4</div>

The first rule of ARIA is to not use ARIA if a native element already gives you the behavior for free - a <button> already has the right role, keyboard support, and focus behavior built in, so re-implementing that with a <div role="button"> just to override styling adds work and risk without benefit. ARIA is for filling genuine semantic gaps, and applying it incorrectly (like a wrong role or a state that never updates) can actively make a page less accessible than having no ARIA at all.

13. How do Flexbox's justify-content and align-items differ, and what happens when flex-direction changes from row to column?

Flexbox works along two perpendicular lines: the main axis (the direction flex items flow) and the cross axis (perpendicular to that flow). justify-content controls alignment along the main axis; align-items controls alignment along the cross axis.

By default, flex-direction: row means the main axis is horizontal:

css
1.container {
2  display: flex;
3  flex-direction: row;
4  justify-content: center; /* horizontal centering */
5  align-items: center;     /* vertical centering */
6}

The confusing part: switch to flex-direction: column and the axes swap. The main axis becomes vertical and the cross axis becomes horizontal - so now justify-content controls vertical spacing (top/bottom distribution), and align-items controls horizontal alignment (left/right).

14. How would you build a responsive 3-column layout that collapses to a single column on mobile - Flexbox, Grid, or media queries?

For this specific case, CSS Grid - it's built exactly for "columns and rows" layouts. Reasons to prefer it over Flexbox here:

  • Grid is designed for structural page layouts where you want explicit control over columns across a container.
  • Flexbox is better suited to 1D alignments, like navbars or button groups.
  • Media queries give precise, explicit control over the exact breakpoint where the layout transforms.
css
1.grid {
2  display: grid;
3  grid-template-columns: repeat(3, 1fr);
4  gap: 1rem;
5}
6
7@media (max-width: 600px) {
8  .grid {
9    grid-template-columns: 1fr;
10  }
11}

15. What's the difference between position: relative, absolute, fixed, and sticky? When would you use sticky?

  • position: relative - stays in normal document flow, but you can offset it with top/bottom/left/right without affecting surrounding elements. Also creates a positioning boundary for its absolute children.
  • position: absolute - removed from normal flow entirely (takes up zero space). Positions relative to its nearest ancestor with a position value other than static.
  • position: fixed - removed from flow and anchored relative to the viewport; stays in the same spot even while scrolling.
  • position: sticky - a hybrid; acts like relative until you scroll past a specified boundary (e.g. top: 0), then pins itself like fixed until its parent container ends.

A real UI pattern for sticky: table headers. In a long pricing or order-history table, you want the header row to stay visible while the user scrolls through rows.

css
1thead th {
2  position: sticky;
3  top: 0;
4  background: white;
5  z-index: 1;
6}

Another everyday example: a docs page's section nav sidebar that scrolls with you until it hits the top, then stays fixed while the main content keeps scrolling underneath.

16. Between .nav .link.active and #header a, which selector wins, and why?

Specificity is a weighted score, roughly counted as (IDs, classes, elements), compared left to right like version numbers:

  • .nav .link.active → 0 IDs, 3 classes/pseudo-classes (.nav, .link, .active), 0 elements → specificity (0, 3, 0)
  • #header a → 1 ID (#header), 0 classes, 1 element (a) → specificity (1, 0, 1)

IDs are the most powerful category, so #header a wins, even though it looks like the "simpler" selector - a single ID outranks any number of classes. This is exactly why people get bitten by specificity bugs: they keep stacking classes onto a selector to try to win, not realizing an ID anywhere in the competing rule beats them regardless of how many classes they pile on. It's also why many teams avoid using IDs for styling altogether - once an ID selector is in your CSS, it's disproportionately hard to override later without resorting to !important, which is generally best avoided.

17. What's the difference between em, rem, %, and vh/vw units, and how do you decide which to use for font sizes versus container widths?

  • em - relative to the font-size of the parent element (or the element itself, for font-size specifically). This compounds - nested em-based font sizes can snowball unexpectedly.
  • rem - relative to the root (html) font-size, always. No compounding surprises: if root is 16px, 1.5rem is always 24px, no matter how deeply nested.
  • % - relative to the parent element's corresponding property (a width: 50% child is half its parent's width).
  • vh/vw - relative to the viewport (1vh = 1% of viewport height, 1vw = 1% of viewport width). Great for "full screen" sections.

For font sizes, default to rem - it's predictable and respects the user's browser font-size settings (an accessibility win), without em's compounding weirdness. Use em selectively when you actually want something to scale relative to its own context, like padding inside a button that should scale with the button's font-size.
For container widths, % is usually right when you want fluid layouts relative to a parent (a sidebar that's always 25% of its container). Use vw/vh sparingly - great for full-bleed hero banners (100vw) or full-height splash screens (100vh), but avoid them for general margins or text, since mobile viewport height can shift as address bars expand or collapse.

18. What is CSS Grid, and how does its two-dimensional approach differ from Flexbox's one-dimensional approach?

CSS Grid is a layout system built around rows and columns, making it easy to create complex, responsive layouts without relying on floats or manual positioning.

  • Flexbox is one-dimensional: it arranges items along a single axis at a time (a row or a column). When items wrap onto a second line, that line acts as an independent flex container - items on row 2 have no alignment awareness of items on row 1.
  • CSS Grid is two-dimensional: it works with rows and columns simultaneously. You build a strict grid structure, and items are locked into both horizontal and vertical tracks at once.

19. What's the difference between CSS Grid's fr unit and using percentages for column widths?

% widths are calculated relative to the container and don't account for other CSS like gap, so percentage columns plus a gap can overflow the container if you're not careful - percentages don't automatically shrink to make room for gap space. The fr unit ("fraction") is Grid-native and smarter: it distributes space after fixed-size tracks and gaps are already subtracted.

css
1/* PROBLEM: percentages overflow with gap */
2.grid-percentage {
3  display: grid;
4  grid-template-columns: 50% 50%;
5  gap: 20px; /* Result: content overflows container width by 20px! */
6}
7
8/* SOLUTION: fr handles gap math automatically */
9.grid-fr {
10  display: grid;
11  grid-template-columns: 1fr 1fr;
12  gap: 20px; /* Result: container is exactly 100% wide, gap subtracted first */
13}

20. What is a stacking context, and can you describe a bug where z-index just doesn't seem to work?

A stacking context is a self-contained "layer" for z-index purposes. Certain CSS properties create a new stacking context - position combined with a z-index value, opacity less than 1, transform, filter, and a few others.

The classic bug: an element with z-index: 9999 still renders behind something with a much lower z-index. Why? z-index values only compete within the same stacking context. If a parent element accidentally created its own stacking context - say it has opacity: 0.99 for a transition effect, or a transform for an animation - then everything inside that parent is trapped in that context. No matter how high you set the z-index inside it, it can't climb above elements outside that parent's stacking context, because it isn't even competing in the same layer.
Real example: a modal with z-index: 1000 rendered inside a card component that has transform: scale(1) for a hover effect. That transform quietly created a new stacking context on the card, so the modal - even with a huge z-index - is boxed inside the card's stacking layer and can never appear above a fixed navbar that lives outside it.

21. What are CSS custom properties (variables), and how are they different from Sass variables?

css
1:root {
2  --brand-color: #3b82f6;
3  --spacing-unit: 8px;
4}
5
6.button {
7  background: var(--brand-color);
8  padding: calc(var(--spacing-unit) * 2);
9}

A custom property (--brand-color) is a real CSS value, resolved live in the browser, not compiled away like a Sass variable. That difference matters practically: a custom property can be read and overridden by JavaScript (element.style.setProperty), can be redefined at different points in the DOM tree so a component picks up whatever value is in scope for it, and can be changed at runtime - inside a media query, on :hover, or via a theme toggle - without recompiling any stylesheet. A Sass variable, by contrast, is substituted once at build time into a fixed value; it can't respond to runtime conditions like a user's theme choice or an element's ancestor.

22. What do flex-grow, flex-shrink, and flex-basis actually control?

  • flex-basis: the item's starting size along the main axis, before any growing or shrinking is applied - think of it as an initial width/height suggestion.
  • flex-grow: a unitless number that controls how much of the container's leftover space this item should absorb, relative to its siblings. flex-grow: 2 on one item and 1 on another means the first takes twice as much of the extra space.
  • flex-shrink: controls how much this item shrinks, relative to siblings, when the container is too small to fit everything at its basis size. flex-shrink: 0 opts an item out of shrinking entirely.
css
1.item {
2  flex: 1 1 200px; /* shorthand: grow shrink basis */
3}

The flex shorthand combines all three in that order, and flex: 1 (a common shortcut) expands to flex: 1 1 0%, meaning the item ignores its content size and grows/shrinks purely based on the ratio to its siblings.

23. What's the difference between CSS transitions and animations?

A transition smoothly interpolates a property between two states - its starting value and an ending value triggered by something else, like a :hover or a class toggle. It needs an explicit trigger and only ever plays once per state change, from A to B.

css
1.box {
2  transition: transform 0.3s ease;
3}
4.box:hover {
5  transform: scale(1.1);
6}

An animation, defined with @keyframes, describes a full sequence of intermediate states over time, can run automatically without a trigger, and can loop indefinitely (animation-iteration-count: infinite) - useful for a loading spinner or an attention-grabbing pulse that isn't tied to any user interaction.

css
1@keyframes spin {
2  from { transform: rotate(0deg); }
3  to { transform: rotate(360deg); }
4}
5.spinner {
6  animation: spin 1s linear infinite;
7}

III. Advanced Level

1. What is responsive web design, and why is the HTML viewport meta tag its crucial starting point?

Responsive web design (RWD) is the practice of building a site so its layout fluidly adapts to whatever screen it's viewed on, like water taking the shape of its container.

The viewport meta tag is the crucial starting point because of how mobile browsers historically behave: by default they assume a page was designed for desktop, so they render it at a fake desktop width (usually 980px) and shrink the whole thing to fit the phone screen - like photographing a poster from across the room. Everything is technically "there," but tiny and unreadable, and your media queries never even fire because the browser thinks it has 980px of space.

html
1<meta name="viewport" content="width=device-width, initial-scale=1">

width=device-width tells the browser to use the actual physical width of the device as the layout width (375px on an iPhone, say, instead of 980px). initial-scale=1.0 sets the initial zoom to 1:1. Without this one line, every media query you write is essentially dead code on mobile - which is why it's the starting point, not just a nice-to-have.

2. What's the difference between the Shadow DOM and the regular DOM, and why would you use a Web Component with Shadow DOM in a large-scale application?

The regular DOM is like a public park - any global CSS on your page can affect any element in it; a rule like button { background: red; } turns every button on the page red. The Shadow DOM is like a private, walled garden inside that park: it encapsulates HTML, CSS, and JavaScript so they don't leak out into the rest of the app, and global styles don't leak in.

Why it matters at scale: imagine an enterprise company where 50 teams build one shared dashboard. If Team A builds a Calendar Web Component using the Shadow DOM, and Team B later deploys a sloppy global rule like div { margin: 50px; }, the Calendar component stays completely unaffected. Shadow DOM prevents CSS naming collisions and makes UI components truly portable.

3. How would you architect the HTML for a data-heavy dashboard to stay performant, and how does lazy-loading intersect with accessibility?

A high-performance dashboard has to fight "DOM bloat" - too many HTML nodes on the page at once, which can crash browser memory.

  • Performance and lazy loading: never render thousands of rows at once. Use DOM virtualization (or windowing) - only render the ~30 rows currently visible, and as the user scrolls, JavaScript reuses those same nodes, swapping the data inside them.
  • The accessibility intersection: this is where lazy-loading gets dangerous. Build a massive data grid out of generic <div>s and just swap data in and out, and a screen reader user loses all context of which column they're in. You have to keep semantic markup: use proper <table>, <tr>, and <th> tags, paired with attributes like aria-rowcount="10000" and aria-rowindex="50" so the screen reader can announce "you're looking at row 50 out of 10,000," even though only 30 rows actually exist in the HTML.

4. How does the browser parse malformed HTML like an unclosed <div>, and why can that "forgiving" behavior cause subtle production bugs?

If you write malformed HTML - like leaving a <div> unclosed - the browser's parser doesn't crash the way a script would. Instead it tries to guess what you meant and quietly auto-closes the tag for you, like a contractor building a wall where they think it belongs to keep the roof from caving in.

Why it causes subtle production bugs:

  • Layout explosions: the browser might assume your unclosed <div> was meant to wrap the next three sections of the site. Elements meant to be siblings end up nested inside each other, and your Grid or Flexbox layout breaks.
  • Hydration mismatches: with a framework like React or Vue, the server sends HTML and JavaScript then "hydrates" it to make it interactive. If the forgiving parser altered the HTML structure to fix your unclosed tag, React wakes up, sees the DOM doesn't match what it expects, and throws a fatal hydration error - often wiping the UI and re-rendering it from scratch.

5. What's the difference between client-side rendering, server-side rendering, and hydration?

  • Client-Side Rendering (CSR): the server sends a mostly empty HTML shell plus a JavaScript bundle; the browser runs that JavaScript to build the actual DOM and fetch data. First paint is fast to arrive but the page is blank/unstyled until JS downloads, parses, and runs.
  • Server-Side Rendering (SSR): the server renders the full HTML for the requested page up front, so the browser can paint real content immediately, before any JavaScript has even loaded - better for perceived performance and for search engines that don't execute JS well.
  • Hydration: the step after SSR where the client-side JavaScript "wakes up" the already-rendered static HTML, attaching event listeners and internal state so it becomes a fully interactive app, without re-creating the DOM from scratch.

The trade-off: SSR gives a faster first paint and better SEO out of the box, but the page can look ready before hydration finishes, so clicking too early may do nothing - a gap sometimes called the "uncanny valley" of SSR, where the browser detects a mismatch between server-rendered and client-expected markup and has to re-render.

6. What is the Same-Origin Policy, and how does CORS relate to it?

The Same-Origin Policy (SOP) is a browser security rule that, by default, blocks a script running on one origin (scheme + domain + port) from reading the response of a request made to a different origin. Without it, a malicious page could silently make authenticated requests to your bank's site using your logged-in session cookies, and read the response.

CORS (Cross-Origin Resource Sharing) is the mechanism that lets a server deliberately relax that restriction for specific origins, by sending back response headers like Access-Control-Allow-Origin, telling the browser "it's fine for scripts from this other origin to read this response." The request itself still reaches the server either way - CORS controls whether the browser lets the calling script read the response, not whether the server receives the request.

7. What are service workers, and what do they enable?

A service worker is a JavaScript file that runs in a background thread, separate from the page, and can intercept network requests the page makes. Once registered, it keeps running (with some lifecycle limits) even after the page that registered it closes, letting it act as a programmable network proxy sitting between the app and the network.

  • Offline support: caching key assets and API responses so the app still loads, or degrades gracefully, without a network connection.
  • Push notifications: receiving and displaying push messages even when the page isn't open.
  • Background sync: deferring an action (like sending a message) until connectivity is restored.
  • Performance: serving cached responses instantly for repeat visits, instead of hitting the network every time.

Service workers are a core building block of Progressive Web Apps, and they only run over HTTPS (or localhost), since a script with this much control over network traffic would be a serious security risk over plain HTTP.

8. How does the CSS containment property (contain) work, and how would you use it to improve rendering performance?

The contain property is a promise you make to the browser: "changes inside this element won't affect anything outside it." That promise lets the browser skip work it would otherwise do defensively.
Without it, the browser's default assumption is that any change on the page - text updating, an element resizing - might ripple across the entire page's layout, paint, or even DOM structure, so a naive implementation has to consider recalculating everything. On a page built from many independent widgets, applying contain: layout paint (or contain: content) to each widget lets the browser scope its recalculation work to just that widget instead of the whole page, which can meaningfully cut down layout and paint costs.

9. What's the difference between CSS Grid's implicit and explicit grid, and how could not understanding that cause a layout bug?

  • Explicit grid: the literal blueprint you wrote in CSS. Declaring grid-template-rows: 100px 100px asks for exactly two rows.
  • Implicit grid: the overflow "parking lot." If you define a 2-row grid but hand the browser 3 rows worth of content, it auto-generates a third row to hold the extras.
css
1.grid {
2  display: grid;
3  grid-template-columns: 200px 200px 200px; /* explicit: exactly 3 columns */
4  grid-template-rows: 100px;                /* explicit: exactly 1 row defined */
5}

If you only have 3 items, they fit perfectly. But add a 4th item dynamically, and since there's no room in row 1, the grid auto-generates an implicit row to hold it - and because grid-auto-rows was never defined, that implicit row defaults to auto sizing, fitting its content instead of matching your explicit row's height.

Real bug scenario: a photo gallery grid with grid-template-rows: 250px, expecting a uniform 250px thumbnail strip on every row, but populated dynamically from an API with an unpredictable item count. The first row looks perfect. But once there are more photos than fit in one row, every subsequent row silently ignores the 250px rule - because implicit rows don't inherit grid-template-rows - and auto-sizes based on content instead, producing a jumbled, inconsistent-height mess. It looks like the CSS randomly breaks for larger datasets, when the real fix is simply defining grid-auto-rows too:

css
1.grid {
2  grid-template-columns: repeat(4, 1fr);
3  grid-auto-rows: 250px; /* now implicit rows match explicit sizing too */
4}

10. How does the browser's critical rendering path work with CSS, and why is CSS considered render-blocking?

The critical rendering path is the sequence of steps the browser takes from "received HTML bytes" to "pixels on screen":

  • Parse HTML → build the DOM tree
  • Parse CSS → build the CSSOM tree
  • Combine DOM + CSSOM → build the render tree (only visible nodes, with computed styles)
  • Layout (reflow) - calculate the exact position/size of every element
  • Paint - fill in pixels
  • Composite - combine layers onto the screen

Why CSS is render-blocking: step 3 needs both the DOM and the complete CSSOM before it can build the render tree, because the browser genuinely can't know what an element should look like - hidden? what size? what color? - until it has processed all applicable CSS, since a later rule (even later in the same file) could override an earlier one. By spec, the browser blocks rendering entirely until it has fully downloaded and parsed all render-blocking CSS, which is why a <link rel="stylesheet"> in <head> without media conditions holds up the very first paint, even if the file is huge and mostly irrelevant to above-the-fold content.
This is a deliberate, correct trade-off - the alternative (painting with incomplete styles, then repainting once more CSS arrives) is exactly the Flash of Unstyled Content (FOUC) browsers try to avoid. On a slow connection, the practical fix is to keep the critical, above-the-fold CSS small and inline it, defer or split out non-critical stylesheets (e.g. with media="print" tricks or loading them asynchronously), and avoid @import chains that add extra round-trips before the CSSOM can be completed.

11. How do CSS media queries work to adapt layouts across different device screen sizes?

Media queries act as conditional logic for styles. They monitor the device's environment - screen width, height, orientation, or system preferences like dark mode - and apply specific CSS rules only when those conditions are true. Instead of separate stylesheets for phones and desktops, you write a base mobile layout, then add a query like @media (min-width: 768px) { ... }; once the screen stretches past 768px, the browser applies those new rules, transforming a stacked mobile menu into a horizontal desktop navbar.

css
1/* Mobile-first base styles apply to everyone by default */
2.container {
3  display: flex;
4  flex-direction: column;
5  padding: 12px;
6}
7
8/* Then progressively override for larger screens */
9@media (min-width: 768px) {
10  .container {
11    flex-direction: row;
12    padding: 24px;
13  }
14}
15
16@media (min-width: 1200px) {
17  .container {
18    max-width: 1140px;
19    margin: 0 auto;
20  }
21}

Mobile-first (min-width) is generally preferred over desktop-first (max-width): you write the base, unadorned styles for the smallest case, then layer on complexity as screen space increases, which mirrors how CSS's cascade naturally works and tends to cause less override-fighting than starting with a complex desktop layout and trying to strip it down for mobile.
Beyond width, media queries can target other characteristics too:

css
1@media (prefers-color-scheme: dark) { /* dark mode */ }
2@media (orientation: landscape) { /* phone rotated sideways */ }
3@media (hover: hover) { /* device actually supports hover, i.e. not touch-only */ }

12. For a layout supporting both LTR and RTL languages, how do logical properties like margin-inline-start change your approach compared to physical properties like margin-left?

Physical properties (margin-left, padding-right, left, border-left) refer to a fixed physical direction on screen, regardless of the content's reading direction. Logical properties (margin-inline-start, padding-inline-end, inset-inline-start) refer to direction relative to the text's writing mode, and automatically flip depending on whether the document is LTR (English, most Latin-script languages) or RTL (Arabic, Hebrew).

The concrete problem with physical properties:

css
1/* Written for English (LTR) */
2.card {
3  margin-left: 16px;         /* pushes content away from the left edge -- fine for LTR */
4  border-left: 3px solid blue; /* a visual accent flush with the "start" edge */
5}

Ship that same product in Arabic (RTL), and text now reads right-to-left, so the "start" of the content is visually the right edge - but the CSS still says margin-left, so the spacing sits on the wrong side, and the accent border meant to mark the leading edge ends up on the trailing edge instead. Nothing crashes; it just looks subtly wrong, easy to miss in review if nobody tests in RTL.

With logical properties, you write intent once and the browser handles the flip:

css
1.card {
2  margin-inline-start: 16px; /* "start" = left in LTR, right in RTL -- automatically */
3  border-inline-start: 3px solid blue;
4  padding-inline: 12px 20px; /* shorthand: start then end */
5  padding-block: 8px;        /* top/bottom -- "block" direction, for vertical writing modes too */
6}

Just flip the document's dir attribute (<html dir="rtl">) and every logical property re-resolves automatically - no duplicate stylesheet, no JS-based direction detection, no [dir="rtl"] { margin-left: 0; margin-right: 16px; } override chains that grow unmaintainable as a component library scales.

This changes the architectural approach: default to logical properties everywhere from day one in a design system meant to support multiple languages - not just LTR/RTL, since block/inline terminology also generalizes correctly to vertical writing modes like Japanese vertical text, without needing another special case. The only place physical properties still make sense is when something is genuinely tied to a fixed screen direction regardless of language - like a "scroll to top" arrow that should always point visually upward, or a box-shadow simulating a fixed real-world light source. Everything about text flow, spacing, and reading-order layout, though, goes logical. By default, that turns RTL support from a huge retrofit project with a parallel stylesheet into flipping one attribute and having it mostly just work.

13. What are CSS cascade layers (@layer), and what problem do they solve?

Cascade layers let you group CSS rules into named layers and control which layer wins in a conflict, independent of specificity or source order. Without layers, if a third-party component library's CSS happens to use a more specific selector than your override, you're stuck fighting it with even more specific selectors or !important. With @layer, you can declare your layer order up front so your own styles always beat the library's, or vice versa, regardless of how specific either selector is.

css
1@layer reset, base, components, utilities;
2
3@layer base {
4  a { color: blue; }
5}
6
7@layer components {
8  .btn { color: green; } /* wins over @layer base's `a`, even if `a` were more specific, because components comes later */
9}

Layers are resolved before specificity: a rule in a later-declared layer always beats a rule in an earlier layer, no matter how specific the earlier rule's selector is. Only within the same layer (or for unlayered styles, which win over any layer) does normal specificity and source order still apply.

14. What are CSS container queries, and how are they different from media queries?

A media query bases its condition on the viewport's size - it has no idea how much space a given component actually has, only how big the browser window is. A container query instead bases its condition on the size of a specific containing element, letting a component adapt to the space it's actually placed in, regardless of the viewport.

css
1.sidebar {
2  container-type: inline-size;
3  container-name: sidebar;
4}
5
6@container sidebar (min-width: 400px) {
7  .card {
8    flex-direction: row;
9  }
10}

This matters for genuinely reusable components: the same .card component might sit in a wide main content area on one page and a narrow sidebar on another. A media query can't tell those two placements apart, since the viewport width is identical in both cases - but a container query can, because it responds to the card's actual available width, not the screen's.

15. What do the :is() and :where() pseudo-classes do, and how do they differ?

Both let you group a list of selectors into one, avoiding repetition:

css
1/* Instead of repeating the descendant selector three times */
2header p, main p, footer p { margin: 0; }
3
4/* Write it once */
5:is(header, main, footer) p { margin: 0; }

The difference is specificity: :is() takes on the specificity of its most specific argument, so :is(#header, .nav) p behaves as if it had an ID's specificity. :where() always contributes zero specificity, regardless of what's inside it, which makes it useful for writing default or reset-style styles that are trivially easy for anyone downstream to override without fighting specificity at all.

Found this helpful?

Share it with your network

Related Articles

Frontend

React JS

Prepare for your React interview with the most asked questions for freshers and experienced developers. Covers hooks, lifecycle, performance optimization, and real-world scenarios.

Frontend

JavaScript

Prepare for your next tech interview with the most asked JavaScript interview questions and answers. It includes basic to advanced concepts, coding problems, and real-world scenarios for freshers and experienced developers.