> cs·fundamentals
interview 0% 20m read

DOM, HTML & CSS

DOM manipulation, display:none vs visibility:hidden, HTML semantics, and CSS fundamentals (box model, flexbox, grid, specificity, positioning).

The DOM is the live, in-memory representation of the page that JavaScript reads and mutates; HTML gives that tree meaning; CSS lays it out. This chapter is the working vocabulary for all three — the distinctions that come up daily and the ones interviewers probe.

DOM manipulation, and why it’s slow

The DOM is a tree of objects. JavaScript reshapes the page by creating nodes (createElement), inserting them (append, insertBefore), removing them, or editing their attributes and text. Every structural change can invalidate layout — so the cost isn’t the JS, it’s the reflows it provokes.

The classic optimization is to batch DOM writes so the browser reflows once. Building nodes off-document (in a DocumentFragment) and inserting the whole subtree in a single operation is the canonical pattern.

Batching inserts with a DocumentFragment
// SLOW — 1000 separate insertions, each a potential reflow
for (let i = 0; i < 1000; i++) {
  const li = document.createElement("li");
  li.textContent = `Item ${i}`;
  list.appendChild(li); // touches the live DOM each time
}

// FAST — build off-document, insert once (one reflow)
const frag = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const li = document.createElement("li");
  li.textContent = `Item ${i}`;
  frag.appendChild(li); // not in the live DOM yet
}
list.appendChild(frag); // single insertion of the whole subtree

A DocumentFragment is a lightweight container that isn’t part of the page, so mutating it costs nothing. Appending it moves its children into the DOM in one shot.

display:none vs visibility:hidden vs opacity:0

Three ways to “hide” an element that are not interchangeable — they differ in whether the element occupies space and whether it’s in the render tree at all.

PropertyIn render tree?Occupies space?Clickable?Reachable by tab/AT?
display:noneno (removed)no — collapsesnono
visibility:hiddenyesyes — reserves its boxnono
opacity:0yesyesyes — still receives clicksyes
display:none is the only one that removes the element from layout entirely.

display:none removes the node from the render tree, so it triggers a reflow when toggled and the element is fully gone for layout and accessibility purposes. visibility:hidden keeps the box (and its space) but paints nothing. opacity:0 keeps it fully present and interactive — it’s just transparent — which is a common a11y trap: an opacity:0 button is still focusable and clickable by screen readers.

HTML semantics

Semantic elements carry meaning that a <div> does not. <header>, <nav>, <main>, <article>, <aside>, <section>, and <footer> describe the role of a region, which buys you three things for free:

  • Accessibility. Screen readers expose landmarks (<nav>, <main>) so users can jump straight to the navigation or main content. A <button> is keyboard-focusable and announces its role; a clickable <div> is neither without extra ARIA and tabindex.
  • SEO and machine readability. Search engines and readers use the structure to understand the page.
  • Less code. Native elements come with built-in behavior (a <button> fires on Enter/Space; an <a href> is focusable and navigable) that you’d otherwise reimplement.

The box model

Every element is a rectangular box composed, from the inside out, of content → padding → border → margin. The one thing to get right is how width is measured:

Four nested rectangles labelled margin, border, padding, and content from outside to inside, with brackets showing what content-box and border-box each measure as the width.marginborderpaddingcontentcontent-box widthborder-box width
FIG 1 · the box model Nested rings from the outside in: margin, border, padding, content. content-box measures only the green core; border-box measures out to the border edge.
/* default: width = content only; padding + border add to the rendered size */
.legacy { box-sizing: content-box; width: 200px; padding: 20px; } /* renders 240px wide */

/* sane: width includes padding + border — what you almost always want */
.modern { box-sizing: border-box; width: 200px; padding: 20px; } /* renders 200px wide */

box-sizing: border-box makes width mean the total rendered width (padding and border included), which is why most resets set it globally. Note that vertical margins between adjacent block elements collapse — the larger of the two wins rather than summing.

Flexbox vs Grid

Both are modern layout systems; the rule of thumb is Flexbox for one dimension, Grid for two. The single image that makes it click: Flexbox distributes items along one axis (a row or a column), while Grid positions items into cells defined by two axes at once.

On the left, a Flexbox row with three boxes along one horizontal main axis. On the right, a Grid with a 3-by-2 matrix of cells along two axes.Flexbox — 1 axismain axisGrid — 2 axescolumns →rows ↓
FIG 2 · one axis vs two Flexbox lays items along a single main axis. Grid defines a two-dimensional matrix of rows and columns up front, then places items into cells.
FlexboxGrid
Dimensionsone axis (a row or a column)two axes (rows and columns at once)
Driven bycontent — items size to their content, then flexthe container — you define the track template up front
Best fortoolbars, nav bars, centering, distributing a row of itemspage layouts, dashboards, card galleries, any real grid
Key propsjustify-content, align-items, flex, gapgrid-template-columns, grid-template-rows, gap, grid-area
They compose freely — grid items are often themselves flex containers.

Specificity

When two rules set the same property on the same element, specificity decides the winner. Compute it as a tuple (inline, ids, classes, elements) and compare left to right — like comparing version numbers, where a single higher digit on the left beats any number of digits on the right:

Four stacked weight columns from lowest to highest: element selectors, class selectors, id selectors, and inline styles, with an !important flag floating above everything.element(0,0,0,1)class(0,0,1,0)id(0,1,0,0)inline(1,0,0,0)!important overrides everything above
FIG 3 · specificity weights Four columns of increasing weight. Compare left-to-right: one id (0,1,0,0) beats any number of classes/elements; inline beats any selector; only !important sits above all of it.
Selector(inline, id, class, element)Wins against
div p(0, 0, 0, 2)almost everything
.menu a(0, 0, 1, 1)any pure element selector
#nav(0, 1, 0, 0)any number of classes/elements
style="…" (inline)(1, 0, 0, 0)any selector — short of !important
Higher tuple wins; on an exact tie, the later rule in source order wins.

Positioning

position controls how an element is placed and what top/right/bottom/left mean relative to.

ValuePositioned relative toIn normal flow?Use for
staticnothing (the default; offsets ignored)yesordinary document flow
relativeits own original positionyes — keeps its spacenudging an element; creating a positioning context for children
absolutenearest positioned ancestorno — removed from flowtooltips, badges, overlays anchored to a parent
fixedthe viewportno — removed from flowsticky headers, modals, back-to-top buttons
stickyscroll position (relative until a threshold, then fixed)yes, then pinssection headers that stick while their section is in view
absolute resolves against the nearest ancestor with a non-static position — a very common gotcha.

An absolute element looks for the nearest ancestor that is itself positioned (relative/absolute/fixed/sticky); if none exists, it anchors to the initial containing block. The standard idiom is to set position:relative on a parent precisely to give its absolute children something to anchor to.

01 Learning objectives

0 / 3 done

02 Knowledge check

knowledge check2 questions · pass ≥ 70%
  1. 01easy

    display:none vs visibility:hidden — which still occupies layout space?

  2. 02medium

    Higher CSS specificity is given to:

03 Interview questions

browse all ↗

What gets asked on this topic — tap a card for how to approach it, the follow-ups, and the trap. Company tags are best-effort & sourced.

  • Commonly asked junior concept very common What is the difference between display:none and visibility:hidden?

    display:none removes the element from the render tree entirely: it occupies no space, is not painted, and is skipped by most assistive tech. Toggling it triggers reflow.

    visibility:hidden keeps the element in layout — it still occupies its box and affects siblings — but is not painted (invisible). It is not interactive.

    A third option, opacity:0, is fully painted and still interactive (clickable) and laid out; it just renders transparent.

    Red flag Saying visibility:hidden removes the element from layout — it still occupies space. Confusing opacity:0 (still clickable) with display:none.

    source: MDN — visibility ↗
  • Commonly asked junior concept very common Explain the CSS box model and the box-sizing property.

    Every element is a box with four areas, from inside out: content, padding, border, margin.

    With the default box-sizing: content-box, the width you set applies to the content only; padding and border are added on top, so the rendered box is wider than width.

    With box-sizing: border-box, width includes content + padding + border, so the element stays the size you set. This is why many resets apply * { box-sizing: border-box; }.

    Red flag Forgetting that margin is always outside the box (never part of width), or not knowing border-box folds padding/border into the declared width.

    source: MDN — The box model ↗
  • Commonly asked mid concept common How does CSS specificity work, and what wins between an ID selector and 10 classes?

    Specificity is scored as a tuple (inline, IDs, classes/attributes/pseudo-classes, elements). Higher tuples win; ties are broken by source order (last wins).

    An ID is (0,1,0,0). Ten classes is (0,0,10,0). The ID still wins because the ID column outranks the class column regardless of count — it is not a base-10 sum where 10 classes overflow into the ID column.

    !important overrides normal specificity; inline styles outrank selectors. Use these sparingly.

    Red flag Treating specificity as a single base-10 number so '10 classes beat 1 ID' — columns do not carry over.

    source: MDN — Specificity ↗
  • Commonly asked mid concept common When would you use Flexbox versus CSS Grid?

    Flexbox is for one-dimensional layout — a row or a column — where you distribute space along a single axis (nav bars, toolbars, centering, equal-height items in a row).

    Grid is for two-dimensional layout — rows and columns together — where you place items into a defined grid (page layouts, card galleries, dashboards).

    They compose: a grid cell can itself be a flex container. Reach for Grid when you care about both axes at once; Flexbox when content drives a single axis.

    Red flag Calling Grid 'just for grids of images' or Flexbox 'two-dimensional'. The key distinction is 1D vs 2D.

    source: MDN — Relationship of grid layout to other layout methods ↗
  • Commonly asked mid concept common Explain CSS position values: static, relative, absolute, fixed, and sticky.

    static — default; in normal flow, top/left ignored.

    relative — stays in flow but offset from its normal spot; becomes a positioning context for absolute children.

    absolute — removed from flow; positioned relative to the nearest positioned ancestor (else the initial containing block).

    fixed — removed from flow; positioned relative to the viewport, so it stays put on scroll.

    sticky — a hybrid: behaves like relative until it crosses a scroll threshold, then sticks like fixed within its container.

    Red flag Saying absolute is relative to the viewport (that is fixed), or that sticky is independent of its containing block.

    source: MDN — position ↗
  • Commonly asked junior concept common Why does semantic HTML matter? Give examples beyond <div> and <span>.

    Semantic elements describe meaning, not just appearance, which benefits accessibility, SEO, and maintainability.

    Elements like <header>, <nav>, <main>, <article>, <section>, <aside>, <footer> create landmarks that screen readers and the accessibility tree expose, letting users jump between regions. <button>, <a>, <label>, <input> come with built-in keyboard behavior and roles.

    A <div> with a click handler has none of that — you would have to re-add role, tabindex, and key handling manually.

    Red flag Treating semantics as purely cosmetic, or reinventing a button from a div without role/tabindex/keyboard support.

    source: MDN — HTML: A good basis for accessibility ↗
  • Commonly asked mid concept occasional What is the difference between the HTML attribute and the DOM property (e.g. input value)?

    The attribute is what is written in the HTML source; the property is the live value on the DOM object. They are linked at parse time but can diverge.

    For an <input value="hi">: getAttribute("value") returns the original "hi" (the default), while inputEl.value reflects what the user has currently typed. Editing the box changes the property, not the attribute.

    Some attributes are reflected (id, className), others are not symmetric (value, checked). This is why React tracks value as state.

    Red flag Assuming attribute and property always stay in sync. For value/checked the attribute is just the initial default.

    source: MDN — Attributes ↗
  • Amazon mid coding common How would you efficiently insert 1,000 DOM nodes without causing 1,000 reflows?

    Build the nodes off the live DOM and insert once, so layout is recomputed a single time.

    Use a DocumentFragment:

    const frag = document.createDocumentFragment();
    for (const item of items) { const li = document.createElement("li"); li.textContent = item; frag.appendChild(li); }
    list.appendChild(frag);

    Appending to the fragment does not touch the rendered tree; the single appendChild(frag) inserts all children in one operation. Avoid innerHTML += in a loop (re-parses everything each time) and avoid appending one-by-one to the live list.

    Red flag Appending each node directly to the live DOM in the loop, or using innerHTML += which re-parses the whole list every iteration.

    source: MDN — DocumentFragment ↗
  • Commonly asked senior concept common What is a stacking context, and why might a higher z-index element still appear behind a lower one?

    z-index only orders elements within the same stacking context. A stacking context is a self-contained layer: once formed, its children are painted as a unit, and their z-index values cannot escape it.

    So if element A (z-index: 9999) lives inside a parent that forms a stacking context with a low z-index, and element B (z-index: 1) is in a *sibling* context with a higher one, B paints on top — A's huge z-index is meaningless across contexts.

    New stacking contexts are created by more than position + z-index: opacity < 1, transform, filter, will-change, isolation: isolate, and being a flex/grid child with a z-index, among others. This is the usual cause of 'my z-index isn't working'.

    What a strong answer covers
    • z-index is only comparable within one stacking context, never across them.

    • Once an ancestor forms a context, a child's z-index is trapped inside it.

    • Contexts are created by position+z-index but also opacity < 1, transform, filter, will-change, isolation: isolate.

    • A z-index:9999 child of a low context loses to a z-index:1 element in a higher-ranked sibling context.

    Quick self-check

    Which of these does NOT, by itself, create a new stacking context?

    Red flag Assuming z-index is globally comparable. A larger z-index loses if its element sits in a lower-ranked ancestor stacking context.

    source: MDN — Stacking context ↗
  • Commonly asked mid concept common What does flex: 1 actually mean? Break down flex-grow, flex-shrink, and flex-basis.

    flex is shorthand for three properties:

    - flex-grow — how much a item grows to fill leftover free space, relative to siblings.
    - flex-shrink — how much it shrinks when there isn't enough space.
    - flex-basis — the item's starting size before grow/shrink (its 'ideal' main size).

    flex: 1 expands to flex: 1 1 0% — grow 1, shrink 1, basis 0%. Because basis is 0, items size purely by their grow ratio, so equal flex: 1 items become equal width regardless of content. Contrast flex: auto (1 1 auto), where content size is the starting point, so items differ by content length.

    What a strong answer covers
    • flex: <grow> <shrink> <basis>; flex: 1 = 1 1 0%.

    • flex-grow distributes free space; flex-shrink distributes overflow; flex-basis is the pre-grow size.

    • flex: 1 on siblings gives equal sizes (basis 0); flex: auto (basis auto) sizes from content first.

    • flex-basis takes priority over width for the main-axis starting size.

    Red flag Thinking flex: 1 sets a width directly, or confusing flex: 1 (basis 0, equal sizes) with flex: auto (basis auto, content-driven sizes).

    source: MDN — flex ↗
  • Commonly asked junior coding very common How would you center a div both horizontally and vertically? Give more than one approach.

    Flexbox (most common): on the parent,

    display: flex; align-items: center; justify-content: center;

    Grid (terse): on the parent, display: grid; place-items: center;

    Absolute + transform (no flex/grid): on the child,

    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);

    The transform trick offsets by the element's own size (the 50% in translate is relative to the element), so it centers regardless of dimensions. Flexbox/grid are preferred for in-flow content; absolute centering suits overlays where the child is taken out of flow.

    What a strong answer covers
    • Flexbox: align-items: center; justify-content: center; on the container.

    • Grid: place-items: center; — the shortest form.

    • Absolute + translate(-50%, -50%) centers without knowing the element's size.

    • Prefer flex/grid for in-flow content; absolute centering for overlays/modals.

    Red flag Using `margin: auto` for vertical centering on a block (works horizontally, not vertically without flex), or forgetting the parent needs a height for flex centering to be visible.

    source: MDN — Box alignment (centering) ↗
  • Commonly asked mid concept occasional Why and when do vertical margins collapse between block elements?

    Margin collapsing is when adjacent vertical margins combine into a single margin equal to the largest of them, rather than summing. It applies only to block-level boxes in normal flow along the block (vertical) axis — never horizontal margins.

    Three cases: adjacent siblings (the bottom margin of one and top margin of the next collapse); a parent and its first/last child (if no border/padding/content separates them); and an empty block (its own top and bottom margins collapse).

    It does not happen for flex/grid items, floated or absolutely-positioned elements, or when a border, padding, or overflow: auto separates the boxes. This trips people up when a child's margin unexpectedly pushes the parent.

    What a strong answer covers
    • Collapsing takes the max of the two margins, not the sum — vertical only.

    • Happens between siblings, parent/first-or-last child, and within empty blocks.

    • Prevented by a border, padding, overflow other than visible, or a BFC.

    • Does not apply to flex/grid items, floats, or absolutely positioned boxes.

    Red flag Expecting margins to add up, or thinking collapsing applies to flex/grid items (it doesn't) or to horizontal margins (it doesn't).

    source: MDN — Mastering margin collapsing ↗
  • Commonly asked mid concept common What is the difference between rem, em, %, vw/vh, and px? When would you reach for each?

    px is an absolute (device-independent) pixel — fixed, predictable, but ignores user font preferences.

    em is relative to the current element's font-size (for most properties), so it compounds when nested. rem is relative to the root <html> font-size — no compounding, which makes it the go-to for scalable, accessible typography and spacing.

    % is relative to the parent's corresponding dimension. vw/vh are 1% of the viewport's width/height, useful for full-screen sections.

    Practical default: rem for type and spacing (respects user zoom/root size), %/fr/vw for fluid layout, px for hairline borders.

    What a strong answer covers
    • px: absolute and fixed; doesn't scale with user font settings.

    • em: relative to the element's own font-size — compounds when nested.

    • rem: relative to the root font-size — no compounding; best for accessible, scalable type.

    • % is relative to the parent; vw/vh are 1% of viewport width/height.

    Red flag Confusing em (element-relative, compounds) with rem (root-relative), or using px for font sizes and breaking user zoom/font-size preferences.

    source: MDN — CSS values and units (length) ↗
  • Commonly asked mid concept common What is the difference between event.target and event.currentTarget on a bubbling event?

    event.target is the element where the event originated — the deepest node that was actually clicked/typed-in. event.currentTarget is the element whose listener is currently running — i.e. the element you called addEventListener on.

    During bubbling, target stays constant as the event travels up, while currentTarget changes at each ancestor whose listener fires. In a delegated handler on a <ul>, currentTarget is the <ul>, and target is the specific <li> (or a child of it, which is why you often use target.closest('li')).

    Note: in an arrow function this won't be the element, but event.currentTarget always is.

    What a strong answer covers
    • target = where the event started (deepest element); constant through bubbling.

    • currentTarget = the element whose listener is running now; changes per ancestor.

    • In delegation, currentTarget is the parent you bound to; target is the actual descendant.

    • currentTarget equals this in a normal function handler, but not in an arrow function.

    Quick self-check

    A click listener is on a <ul>. The user clicks a <span> inside an <li>. Inside the handler, what are target and currentTarget?

    Red flag Swapping the two: target is the origin, currentTarget is the listening element. In delegation, acting on target directly can grab a nested child instead of the row.

    source: MDN — Event: currentTarget ↗
  • Commonly asked mid trick common What is the difference between stopPropagation and preventDefault?

    They are orthogonal. preventDefault() cancels the browser's default action for the event — following a link, submitting a form, checking a checkbox — but the event still propagates to other listeners.

    stopPropagation() stops the event from traveling further through the capture/bubble phases to other elements, but does not cancel the default action. (stopImmediatePropagation() additionally prevents other listeners on the *same* element.)

    So: prevent the default behavior with preventDefault; stop the event reaching parents with stopPropagation. Returning false from a jQuery handler does both, but in plain DOM return false only works in inline on* attributes.

    What a strong answer covers
    • preventDefault() cancels the default browser action; propagation still happens.

    • stopPropagation() halts travel to other elements; default action still happens.

    • stopImmediatePropagation() also blocks other listeners on the same element.

    • They're independent — you sometimes call both, sometimes one.

    Quick self-check

    A form submit handler calls only event.stopPropagation(). What happens?

    Red flag Believing stopPropagation also cancels the default action (it doesn't), or that preventDefault stops bubbling (it doesn't).

    source: MDN — Event: preventDefault() ↗
  • Commonly asked senior concept occasional What is a block formatting context (BFC), and name two ways to create one. Why is it useful?

    A block formatting context is a self-contained region of layout where block boxes lay out and floats are managed independently of the outside. Inside a BFC, vertical margins don't collapse with elements outside it, and the BFC contains its floated children.

    Ways to create one: overflow other than visible (e.g. overflow: hidden/auto), display: flow-root (the purpose-built, side-effect-free option), being a flex/grid item, display: inline-block, or floating/absolute positioning.

    Classic uses: clearing floats (a floated child no longer overflows its parent's height), and stopping margin collapse between a parent and child. display: flow-root is the modern, intention-revealing way to do both.

    What a strong answer covers
    • A BFC is an isolated layout region; floats and margins inside don't leak out.

    • Create with display: flow-root, overflow ≠ visible, flex/grid item, float, or absolute.

    • Contains floats (no parent collapse) and blocks external margin collapsing.

    • display: flow-root is the clean, side-effect-free way to establish one.

    Red flag Using `overflow: hidden` to clear floats and accidentally clipping content or scrollbars; `display: flow-root` avoids those side effects.

    source: MDN — Block formatting context ↗