HTML & CSS · cheat sheet

HTML & CSS

Semantic HTML, script loading, forms, accessibility, images, rendering, the cascade, box model, flexbox, grid and responsive CSS: what interviewers probe.

The HTML, accessibility and CSS facts a front-end round keeps coming back to, from defer to why z-index: 9999 does nothing.

Semantic HTML & landmarks

Element Exposed as Use for
<header> banner, only when not inside article, aside, main, nav, section site header, or a section’s intro
<nav> navigation major link groups; name each one if there are several
<main> main the page’s unique content; one visible per page
<aside> complementary (unless unnamed inside article/section) sidebars, related content
<footer> contentinfo, only at top level site footer, or a section’s footer
<section> region, only with an accessible name a thematic group with a heading
<article> article (not a landmark) self-contained: post, comment, card
<form> / <search> form (only when named) / search forms; a search or filter form
  • <div> and <span> mean nothing. <a href> navigates, <button> acts; a clickable div is neither focusable nor announced as a control.
  • Don’t skip heading levels for styling: screen reader users navigate by headings and landmarks.
  • Built-in widgets: <details>/<summary>, <dialog>, <select>, <datalist>, <progress>, <meter>. Also meaningful: <figure>/<figcaption>, <time datetime>, lists, <table> with <caption> and <th scope>.

Document head & resource hints

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Pricing · Acme</title>
  <meta name="description" content="Plans and pricing">
  <link rel="preconnect" href="https://cdn.example.com" crossorigin>
  <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
  <link rel="stylesheet" href="/app.css">
  <script src="/app.js" defer></script>
</head>
HTML
  • <!doctype html> selects standards mode (without it: quirks mode). lang drives pronunciation and hyphenation.
  • The viewport meta stops mobile browsers rendering at desktop width. Never add user-scalable=no or maximum-scale=1: they can block zoom.
Hint Does Use for
dns-prefetch DNS lookup only cheap fallback for third-party origins
preconnect DNS + TCP + TLS now a few critical origins (CDN, fonts, API)
preload fetch for this page, early; as required late-discovered critical files: fonts, hero image
modulepreload fetch and prepare an ES module the critical module graph
prefetch low-priority fetch for a future navigation the next page’s assets
  • Preload fonts with crossorigin, even same-origin, or the font downloads twice. Unused preloads waste bandwidth and log a console warning.

Script loading

Script Fetch Runs Order kept Blocks parser
<script src> immediately as soon as fetched yes yes
async in parallel as soon as fetched no only while executing
defer in parallel after parsing, before DOMContentLoaded yes no
type="module" in parallel, with imports deferred by default yes no
type="module" async in parallel when its graph is ready no no
  • async/defer do nothing on inline classic scripts; defer does nothing on modules. With both, async wins.
  • defer for app code that needs the DOM; async for independent scripts like analytics.
  • Modules are strict, have their own scope, run once even if included twice, and need CORS cross-origin.
  • A parser-blocking script also waits for earlier stylesheets, since it might read styles.

Forms & validation

<form action="/subscribe" method="post">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required autocomplete="email">
  <label for="zip">ZIP code</label>
  <input id="zip" name="zip" inputmode="numeric" pattern="[0-9]{5}" required>
  <button>Subscribe</button>
</form>
HTML
  • Types (email, url, tel, number, range, date, datetime-local, color, file, search, password…) bring the right mobile keyboard and built-in checks; tel has no format check.
  • Validation attributes: required, minlength/maxlength, min/max/step, pattern (must match the whole value). novalidate on the form or formnovalidate on a button skips them.
  • JS: checkValidity(), reportValidity(), el.validity.valueMissing (typeMismatch, patternMismatch, tooShort…), setCustomValidity('msg') (invalid until reset to '').
  • :invalid matches on page load; :user-invalid only after the user interacts.
  • Only named fields submit; unchecked checkboxes and disabled fields are skipped, readonly ones are sent.
  • A <button> in a form defaults to type="submit". Group radios with <fieldset> + <legend>. A placeholder is not a label.

Accessibility

  • Rules of ARIA use (W3C): prefer native HTML; don’t change native semantics unless you must; interactive ARIA controls must work by keyboard; never put role="presentation" or aria-hidden="true" on a focusable element. Every interactive element needs an accessible name.
  • ARIA changes what assistive tech is told, never behavior: a div role="button" still needs tabindex="0", Enter and Space handlers, and a focus style.
  • Common roles: landmarks, button, checkbox, switch, tab/tabpanel, combobox, listbox/option, menu, dialog; live regions status (polite) and alert (assertive).
  • Common states: aria-expanded, aria-selected, aria-checked, aria-pressed, aria-current="page", aria-invalid, aria-disabled, aria-controls, aria-describedby.
  • Name precedence: aria-labelledby > aria-label > native label (<label>, alt, <legend>) > text content > title/placeholder.
  • Keyboard: Tab between widgets; arrow keys inside composite widgets (tabs, menus, listboxes) via roving tabindex or aria-activedescendant; Enter activates links and buttons, Space buttons and checkboxes; Esc closes dialogs and menus.
  • Focus: tabindex="0" joins the tab order, -1 is focusable by script only, positive values scramble order. Style :focus-visible. Move focus into a dialog on open and back to the trigger on close; showModal() makes the page behind inert and closes on Esc.
  • Alt text: the image’s purpose, not its pixels. Decorative: alt="" (with no alt, the file name may be read out). Icon-only link or button: describe the action (“Search”).
  • Contrast (WCAG AA): 4.5:1 for text, 3:1 for large text (24px, or about 18.7px bold) and for UI components and focus indicators.
  • Don’t rely on color alone; honor prefers-reduced-motion; announce async updates with a live region.

Images

<img src="card-800.jpg"
     srcset="card-400.jpg 400w, card-800.jpg 800w, card-1600.jpg 1600w"
     sizes="(min-width: 60em) 33vw, 100vw"
     width="800" height="600" alt="Team at the offsite" loading="lazy">
<picture>
  <source type="image/avif" srcset="hero.avif">
  <img src="hero.jpg" alt="…" width="1600" height="900" fetchpriority="high">
</picture>
HTML
  • srcset with w descriptors + sizes = resolution switching: the browser picks by slot width × pixel ratio (no sizes means 100vw). x descriptors suit fixed-size images.
  • <picture> = art direction (<source media>) or format fallback (<source type>); the first match wins, and the required <img> carries alt and sizing.
  • Set width and height (or aspect-ratio) to reserve space and avoid layout shift.
  • loading="lazy" below the fold only; the LCP image gets fetchpriority="high" instead.

Rendering path, reflow & repaint

  • Critical rendering path: HTML → DOM, CSS → CSSOM → render tree (visible nodes) → layout (geometry) → paint → composite layers.
  • CSS is render-blocking; synchronous scripts are parser-blocking. The preload scanner finds resources early, but not those referenced in CSS or injected by JS.
  • Reflow (layout): size, margin, font or DOM changes, resize; it can ripple to other elements. Repaint only: color, background, box-shadow, visibility. Composite only: transform, opacity on their own layer.
  • Reading layout (offsetHeight, getBoundingClientRect(), getComputedStyle) after a write forces a synchronous layout; in a loop that’s layout thrashing.
// Bad: each read after a write forces a fresh layout
items.forEach((el) => { el.style.width = box.offsetWidth + 'px'; });
// Good: read once, then write (or batch writes in requestAnimationFrame)
const w = box.offsetWidth;
items.forEach((el) => { el.style.width = w + 'px'; });
JavaScript

Cascade & specificity

  • Order of comparison: origin & importance → inline style → @layer → specificity → order of appearance (the cascade, explained).
  • Unlayered styles beat layered ones; among layers the last declared wins. !important flips this: the first layer wins, and layered !important beats unlayered.
  • Specificity = (IDs, classes/attributes/pseudo-classes, types/pseudo-elements), compared left to right. * and combinators add nothing.
  • :where() adds 0; :is(), :not() and :has() take their most specific argument.
  • Inherited values have no specificity: any rule targeting the element beats them. Text properties inherit (color, font-*, custom properties); box properties don’t.
  • Keywords: inherit, initial, unset, revert (browser default), revert-layer.
@layer reset, base, components; /* order fixed here, last wins */
@layer components { .btn { color: blue; } }
#app .btn         {}  /* (1,1,0) */
ul li:first-child {}  /* (0,1,2) */
:is(#app, .x) a   {}  /* (1,0,1) strongest argument */
:where(#app) a    {}  /* (0,0,1) */
a:not(.ghost)     {}  /* (0,1,1) */
CSS

Box model, margins & BFC

  • Box = content + padding + border + margin. content-box (default): width is content only; border-box: width includes padding and border. Reset: *, *::before, *::after { box-sizing: border-box; }.
  • outline and box-shadow take no space. Percentage padding and margin, even vertical, resolve against the containing block’s width.
  • Margin collapsing is vertical and block-layout only: adjacent siblings, a parent with its first/last child (no border, padding or content between), empty blocks. Result: the largest margin (with negatives, largest positive + most negative).
  • No collapsing for floats, absolute boxes, flex or grid children, or across a BFC.
  • A block formatting context contains its floats, avoids outside floats and stops margin collapsing. Made by display: flow-root (cleanest), overflow other than visible/clip, floats, absolute positioning, inline-block, flex/grid items, contain: layout/paint.

Display, position & stacking

display Behavior
block new line, fills available width
inline flows with text; ignores width, height, vertical margins
inline-block in the line, but honors width, height, margins
flex / grid container for flex or grid children (inline-* variants)
flow-root block with a new BFC
contents own box disappears; children take its place
none no box, gone from the accessibility tree
position In flow? Offsets relative to
static yes none: top and z-index ignored
relative yes, space kept its normal position; anchors absolute children
absolute no nearest positioned ancestor, else the initial containing block
fixed no viewport, unless an ancestor has transform, filter or perspective
sticky yes relative until a threshold (top: 0) in its nearest scroll container
  • z-index applies to positioned elements and flex/grid items, and only orders boxes within one stacking context: a child can’t rise above its parent’s context.
  • Stacking contexts come from: the root; positioned + z-index not auto; fixed/sticky; flex/grid items with z-index; opacity < 1; transform, filter, clip-path, mask; isolation: isolate; will-change on those; contain: paint; the top layer.
  • Paint order inside one: background, negative z-index, blocks, floats, inline content, z-index: 0/auto positioned, positive z-index.
  • Fix it by finding the ancestor that made the context; isolation: isolate creates one on purpose; <dialog> and popover use the top layer.

Flexbox

Container Values (default first) Item Default / meaning
flex-direction row, row-reverse, column, column-reverse flex-grow 0: share of free space
flex-wrap nowrap, wrap flex-shrink 1: share of overflow absorbed
justify-content (main axis) start, center, space-between, space-evenly flex-basis auto: size before flexing
align-items (cross axis) stretch, center, baseline, flex-end align-self per-item align-items
align-content spacing of wrapped lines order 0: visual order only
gap space between items margin-left: auto pushes the item to the end
  • flex: initial = 0 1 auto (default); flex: 1 = grow 1, shrink 1, basis 0 (equal widths); flex: auto = 1 1 auto; flex: none = 0 0 auto.
  • Flex items default to min-width: auto and won’t shrink below their content; set min-width: 0 to stop overflow.
  • Flexbox is one-dimensional (content-out); grid is two-dimensional (layout-in).

Grid & centering

.page {
  display: grid;
  grid-template-columns: 16rem minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  grid-template-areas: "head head" "side main" "foot foot";
  gap: 1rem;
}
.sidebar { grid-area: side; }
.hero { grid-column: 1 / -1; } /* every explicit column */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }
CSS
Container Item
grid-template-columns/rows: 200px 1fr, repeat(3, 1fr) grid-column: 1 / 3, span 2, 1 / -1
grid-template-areas grid-area: main
grid-auto-rows, grid-auto-flow: row | column | dense justify-self, align-self, place-self
place-items (items in cells), place-content (tracks in container) subgrid to reuse the parent’s tracks
  • fr shares out space left over after fixed tracks. 1fr is minmax(auto, 1fr), so long content widens a column; minmax(0, 1fr) keeps columns equal.
  • auto-fill keeps empty tracks; auto-fit collapses them so items stretch. They differ only when items don’t fill a row.
  • Centering: display: grid; place-items: center · flex with justify-content + align-items: center · margin-inline: auto on a block with a width · absolute + inset: 0; margin: auto + a size · absolute + top: 50%; left: 50%; translate: -50% -50% · text-align: center for inline content.

Units & responsive design

Unit Relative to Use for
px the CSS pixel (not a device pixel) borders, shadows
em element’s font size (parent’s, inside font-size) spacing that scales with text; compounds
rem root font size (usually 16px) type and spacing; respects user settings
% the containing block (varies by property) fluid widths
vw / vh 1% of viewport width / height full-bleed sections
svh / lvh / dvh small / large / dynamic viewport mobile full height: 100dvh
ch advance width of “0” line length: max-width: 65ch
  • On mobile vh equals lvh (toolbars retracted), so 100vh can hide content under browser UI.
  • Mobile-first: base styles for small screens, then @media (min-width: 48em) adds more. Break where the content breaks.
  • Container queries: container-type: inline-size on a wrapper, then @container (min-width: 30rem) {…} styles descendants by that container’s width. An element can’t query itself.
  • Preference queries: prefers-color-scheme, prefers-reduced-motion, (hover: hover), (pointer: coarse).
  • Custom properties (--brand: #0a7, var(--brand, fallback)) inherit, cascade and can change per subtree, in media queries or from JS (style.setProperty); Sass variables compile away. @property registers typed, animatable ones.
  • clamp(MIN, VAL, MAX) = max(MIN, min(VAL, MAX)): font-size: clamp(1rem, 0.8rem + 1vw, 1.5rem); keep a rem term so zoom works. width: min(100% - 2rem, 70ch).

Motion & hiding elements

Transition Animation
Starts on a property change (:hover, class) as soon as applied
Steps start → end any @keyframes
Loop, reverse, pause no yes
Best for UI state changes spinners, entrances
  • Animate transform and opacity (no layout or paint); width, top or margin reflow every frame. Use will-change sparingly and avoid transition: all.
Technique Keeps space Focusable / clickable Screen readers
display: none no no hidden
visibility: hidden yes no hidden (children can be visible)
opacity: 0 yes yes still read
hidden attribute no (display: none, loses to CSS display) no hidden
aria-hidden="true" yes, still visible yes hidden
.sr-only (1px, clipped) no yes read
inert yes, still visible no hidden

Quick answers

  • async vs defer? Both fetch in parallel; defer runs in order after parsing, async whenever it arrives.
  • em vs rem? em follows the element’s font size and compounds; rem follows the root.
  • display: none vs visibility: hidden? Both hide from everyone; only visibility keeps the space.
  • Why doesn’t z-index work? The element isn’t positioned or a flex/grid item, or an ancestor’s stacking context traps it.
  • Flexbox or grid? Flex for a line of self-sizing items; grid for aligned rows and columns.
  • Center a div? display: grid; place-items: center on the parent.
  • What does border-box do? width includes padding and border, so padding doesn’t grow the box.
  • Contain floats? display: flow-root on the parent.
  • Reflow vs repaint? Reflow recomputes geometry (costly); repaint redraws pixels; transform/opacity skip both.
  • Pseudo-class vs pseudo-element? :hover is a state; ::before is a part.
  • srcset vs <picture>? srcset lets the browser choose a size; <picture> dictates crop or format.
  • When to use ARIA? Only when no native element fits, with full keyboard support.
  • Media vs container queries? Viewport size vs a component’s container size.

Gotchas & traps

  • height: 100% needs a parent with a definite height; prefer min-height: 100dvh, flex or grid.
  • A child’s margin-top can collapse through its parent; add padding, a border or flow-root.
  • transform on an ancestor re-anchors position: fixed to it and creates a stacking context.
  • sticky fails without top, under an overflow: hidden/auto ancestor, or in a parent no taller than itself.
  • order, row-reverse and grid placement change visual order only; focus and reading order follow the DOM.
  • outline: none without a :focus-visible replacement breaks keyboard use.
  • An invalid var() value doesn’t fall back to an earlier declaration: the property becomes unset.
  • Missing image dimensions cause CLS; loading="lazy" on the hero delays LCP.
  • :hover sticks after a tap on touch screens; wrap it in @media (hover: hover).
  • type="number" is for quantities (spinners, accepts e, arrow keys change it); use inputmode="numeric" for ZIP codes, cards and OTPs.
esc