HTML & CSS Fundamentals
Semantic HTML
Using tags that convey meaning (<nav>, <article>, <header>, <button>) instead of generic <div>/<span> for everything. Why it matters beyond style: screen readers and other assistive tech rely on semantic tags to build a navigable document outline (a <nav> is announced as navigation, a <button> is focusable and keyboard-activatable by default, a styled <div onClick> is neither without extra ARIA work); search engines also weight semantic structure for SEO. A <div> styled to look like a button requires manually re-implementing keyboard focus, Enter/Space activation, and ARIA role — all of which <button> provides free.
Block vs. Inline Elements
Block (<div>, <p>, <h1>): starts on a new line, takes full available width by default, respects width/height. Inline (<span>, <a>, <strong>): flows within a line of text, ignores width/height, only respects horizontal padding/margin visually (vertical padding doesn't affect surrounding line layout). inline-block is the hybrid — flows inline but respects width/height/vertical spacing.
<script> Loading: default / async / defer
<script src="a.js"></script> <!-- blocks HTML parsing until downloaded AND executed -->
<script src="b.js" async></script> <!-- downloads in parallel with parsing, executes AS SOON AS ready (can interrupt parsing) -->
<script src="c.js" defer></script> <!-- downloads in parallel, executes AFTER parsing completes, in document order -->
defer is almost always the right default for scripts that need the DOM to exist and need predictable execution order relative to other scripts — async is appropriate for independent scripts (analytics) that don't depend on or get depended on by DOM state or other script order.
The CSS Box Model
[ margin [ border [ padding [ content ] ] ] ]
.box { box-sizing: border-box; } /* width/height include padding+border, not just content — the modern default */
box-sizing: content-box (the historical default) makes width apply only to content, so padding/border add on top of the specified width — a frequent source of unexpected overflow; border-box is what nearly every modern reset applies globally.
Positioning
| Value | Behavior |
|---|---|
static | Default — normal document flow, top/left/etc. have no effect |
relative | Normal flow, but top/left/etc. offset it from where it would otherwise be |
absolute | Removed from flow, positioned relative to the nearest positioned ancestor (not static) |
fixed | Removed from flow, positioned relative to the viewport, stays fixed during scroll |
sticky | Normal flow until a scroll threshold, then behaves like fixed within its containing block |
The classic gotcha: position: absolute positions relative to the nearest ancestor that is not static — if no ancestor has position: relative/absolute/fixed/sticky set, it positions relative to the initial containing block (usually the viewport), which is rarely what was intended. Setting position: relative on the intended parent, with no offset values, is the standard fix to "anchor" an absolutely-positioned child correctly.
Flexbox
One-dimensional layout (a row OR a column at a time).
.container {
display: flex;
justify-content: space-between; /* main-axis alignment */
align-items: center; /* cross-axis alignment */
flex-direction: row; /* or column */
}
.item { flex: 1; } /* grow/shrink to fill available space equally */
Good for: nav bars, centering content, distributing items along one axis.
CSS Grid
Two-dimensional layout (rows AND columns simultaneously).
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.item { grid-column: span 2; }
Flexbox vs. Grid, the actual decision rule: reach for Flexbox when arranging items along one axis (a toolbar, a list of tags); reach for Grid when the layout genuinely needs to align content across both rows and columns simultaneously (a dashboard layout, a photo gallery with varying spans) — they're frequently used together, Grid for the page-level structure and Flexbox inside individual grid cells.
Specificity
Determines which conflicting CSS rule wins, calculated as a tuple (not simple addition): (inline styles, IDs, classes/attributes/pseudo-classes, elements/pseudo-elements).
#id { } /* specificity: (0,1,0,0) */
.class { } /* specificity: (0,0,1,0) */
div { } /* specificity: (0,0,0,1) */
div.class#id { } /* (0,1,1,1) — combined, not summed into one number */
!important overrides normal specificity entirely — a last-resort escape hatch, not a specificity tier, and genuinely worth avoiding in production CSS since it breaks the predictable cascade for anyone debugging later.
Pseudo-Class vs. Pseudo-Element
Pseudo-class (:hover, :focus, :nth-child()) — targets an element in a particular state or structural position. Pseudo-element (::before, ::after, ::first-line) — targets a sub-part of an element that isn't a real DOM node, most commonly used to inject decorative content without adding markup.
button:hover { background: blue; }
button::before { content: "→ "; }
z-index & Stacking Context
z-index only has effect on a positioned element (position other than static), and only compares meaningfully within the same stacking context. A new stacking context is created by, among other triggers, position: relative/absolute + a z-index value, opacity < 1, or transform. The gotcha: a child with z-index: 9999 can still render behind another element if its parent already created a lower stacking context than that other element's ancestor — z-index values only compete against siblings within the same context, never escaping upward to compare against a different branch's descendants directly.
display: none vs. visibility: hidden
display: none removes the element from layout entirely (no space reserved, not in the accessibility tree). visibility: hidden keeps its layout space reserved (invisible but still occupies room) and is still present for layout purposes, though not visually rendered or focusable.
Units: em / rem / % / vh / vw
em is relative to the current element's font size (compounds when nested — a common source of unexpectedly-scaling text). rem is relative to the root (<html>) font size only — doesn't compound, which is why rem is generally preferred for consistent, predictable sizing. % is relative to the parent's corresponding dimension. vh/vw are relative to the viewport height/width — useful for genuinely viewport-relative sizing (a full-height hero section) but risky for text sizing (can become illegibly small/large on extreme viewport sizes without a clamp() guard).
Media Queries
@media (max-width: 768px) { .sidebar { display: none; } }
Mobile-first practice writes the base (no media query) styles for the smallest viewport, then adds min-width queries to progressively enhance for larger screens — generally more maintainable than writing desktop-first and overriding downward with max-width queries.
CSS Custom Properties (Variables)
:root { --primary-color: #3b82f6; }
.button { background: var(--primary-color); }
Unlike a preprocessor variable (Sass $var, resolved at build time), a CSS custom property is resolved at runtime and is genuinely dynamic — it can be read/changed via JavaScript (element.style.setProperty('--primary-color', 'red')) and cascades/inherits like any other CSS property, which is what makes it the mechanism behind runtime theme switching (light/dark mode) without a full stylesheet swap.
Interview Drills
- Why does
position: absolutesometimes position an element relative to the viewport instead of its intended parent container? —absolutepositions relative to the nearest ancestor with a non-staticpositionvalue — if no ancestor hasposition: relative(or similar) explicitly set, the browser falls back to the initial containing block (effectively the viewport); addingposition: relative(with no offsets) to the intended parent fixes this by giving it something to anchor against. - When would you choose CSS Grid over Flexbox for a layout? — When the layout needs simultaneous alignment across both rows and columns (a dashboard, a photo grid with varying spans) — Flexbox is one-dimensional (a single row or column at a time); for page-level two-dimensional structure, Grid is the more direct tool, often with Flexbox used inside individual grid cells for their own one-dimensional content arrangement.
- Why might a CSS custom property (
--primary-color) be preferred over a Sass variable for implementing a dark-mode toggle? — A Sass variable is resolved entirely at build/compile time — switching themes would require compiling and shipping a second full stylesheet or recompiling; a CSS custom property is resolved at runtime, so JavaScript can update its value directly (or a[data-theme="dark"]selector can override it) and every place referencingvar(--primary-color)updates immediately, with no rebuild step.
Cross-links
[[React Machine-Coding (Frontend System Design)]] · [[API Design Principles]]