Back to Notes

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

ValueBehavior
staticDefault — normal document flow, top/left/etc. have no effect
relativeNormal flow, but top/left/etc. offset it from where it would otherwise be
absoluteRemoved from flow, positioned relative to the nearest positioned ancestor (not static)
fixedRemoved from flow, positioned relative to the viewport, stays fixed during scroll
stickyNormal 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

  1. Why does position: absolute sometimes position an element relative to the viewport instead of its intended parent container?absolute positions relative to the nearest ancestor with a non-static position value — if no ancestor has position: relative (or similar) explicitly set, the browser falls back to the initial containing block (effectively the viewport); adding position: relative (with no offsets) to the intended parent fixes this by giving it something to anchor against.
  2. 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.
  3. 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 referencing var(--primary-color) updates immediately, with no rebuild step.

Cross-links

[[React Machine-Coding (Frontend System Design)]] · [[API Design Principles]]