Semantic Elements
HTML5 introduced meaningful structural elements that replace generic <div> soup — improving accessibility, SEO, and maintainability.
Page Structure Elements
| Element | Role | Notes |
|---|---|---|
| <header> | Site or section header | Can appear inside <article>, <section> — not just <body> |
| <nav> | Navigation links block | Use for primary/secondary navigation. Not every link group needs this. |
| <main> | Primary page content | Only one per page. Landmark for assistive tech. |
| <article> | Self-contained content | Blog post, news item, widget — makes sense standalone |
| <section> | Thematic grouping | Needs a heading. Use <div> when purely for styling |
| <aside> | Complementary content | Sidebars, pull-quotes, related links |
| <footer> | Closing content | Author, copyright, related links — can nest in sections |
| <figure> | Self-contained figure | Images, charts, code snippets with caption |
| <figcaption> | Caption for figure | First or last child of <figure> |
| <address> | Contact information | For the nearest <article> or <body> author |
| <mark> | Highlighted text | Semantic highlight — not just styling |
| <time> | Date/time | Machine-readable with datetime attribute |
| <details> / <summary> | Disclosure widget | Native accordion — no JS needed |
| <dialog> | Modal/popup | Native modal with .showModal() JS API |
<body>
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
</header>
<main>
<article>
<header>
<h1>Article Title</h1>
<time datetime="2025-05-13">May 13, 2025</time>
</header>
<section>
<h2>Intro</h2>
<p>Content here...</p>
</section>
<figure>
<img src="chart.png" alt="Q3 metrics">
<figcaption>Q3 Performance Chart</figcaption>
</figure>
</article>
<aside>Related posts...</aside>
</main>
<footer>
<address>Written by <a href="mailto:v@tcs.com">Vick</a></address>
</footer>
</body>
<div> wraps.HTML5 Form & Input Types
HTML5 added ~14 new input types with native validation, keyboard hints, and browser UI — replacing much custom JS.
| Type | Purpose | Key Attributes |
|---|---|---|
| Email address | validates @ format, mobile keyboard | |
| tel | Phone number | numeric keyboard on mobile |
| url | Web URL | validates protocol prefix |
| number | Numeric input | min, max, step |
| range | Slider | min, max, step, value |
| date | Date picker | min, max, native picker |
| datetime-local | Date + time | No timezone. Use with care. |
| time | Time only | HH:MM format |
| color | Color picker | Returns hex value |
| search | Search field | Shows × clear button |
| file | File upload | multiple, accept |
| hidden | Hidden value | Submitted with form, not shown |
<form novalidate>
<!-- native email validation -->
<input type="email" required placeholder="you@example.com">
<!-- constrained number -->
<input type="number" min="0" max="100" step="5">
<!-- native date/time -->
<input type="date" min="2025-01-01" max="2025-12-31">
<!-- pattern validation -->
<input type="text" pattern="[A-Z]{3}[0-9]{4}" title="Format: ABC1234">
<!-- datalist autocomplete -->
<input list="langs">
<datalist id="langs">
<option value="TypeScript"><option value="Rust">
</datalist>
</form>
Media & Interactive Elements
Native video with controls, autoplay muted, loop, poster, preload. Use <source> for format fallback.
Native audio player. Same attributes. Supports MP3, OGG, WAV via <source> children.
2D/WebGL drawing surface via JS API. Used for games, charts, image processing.
Art direction — serve different images per breakpoint or format (WebP vs JPEG fallback).
Inert HTML fragment — not rendered until cloned/inserted via JS. Foundation of Web Components.
Web Components API — define reusable encapsulated elements with shadow DOM.
<!-- Responsive image with WebP + JPEG fallback -->
<picture>
<source srcset="img/hero.webp" type="image/webp"
media="(min-width: 800px)">
<source srcset="img/hero-sm.webp" type="image/webp">
<img src="img/hero.jpg" alt="Hero" loading="lazy">
</picture>
<!-- Native details/summary accordion -->
<details>
<summary>Click to expand</summary>
<p>Hidden content shown on click. No JS needed.</p>
</details>
Meta & Document Head
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Page description for SEO (150-160 chars)">
<meta name="theme-color" content="#0a0c10">
<!-- Open Graph (social preview) -->
<meta property="og:title" content="Page Title">
<meta property="og:description" content="...">
<meta property="og:image" content="https://...">
<meta property="og:type" content="website">
<!-- Performance hints -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preload" href="critical.css" as="style">
<link rel="prefetch" href="next-page.html">
<!-- PWA manifest -->
<link rel="manifest" href="/manifest.json">
</head>
Selectors & Specificity
CSS selectors determine what gets styled. Specificity determines which rule wins when multiple rules match.
Selector Types
| Selector | Syntax | Example |
|---|---|---|
| Universal | * | * { box-sizing: border-box } |
| Type | el | p { margin: 0 } |
| Class | .cls | .card { padding: 16px } |
| ID | #id | #app { max-width: 1200px } |
| Attribute | [attr] | [disabled] { opacity: 0.5 } |
| Attribute value | [attr="val"] | [type="submit"] { ... } |
| Attr contains | [attr*="val"] | [class*="btn"] { ... } |
| Attr starts | [attr^="val"] | [href^="https"] { ... } |
| Descendant | A B | nav a { ... } |
| Direct child | A > B | ul > li { ... } |
| Adjacent sibling | A + B | h2 + p { margin-top: 0 } |
| General sibling | A ~ B | label ~ input { ... } |
| :is() | :is(A, B) | :is(h1,h2,h3) { ... } |
| :where() | :where(A) | Like :is() but zero specificity |
| :has() | A:has(B) | div:has(> img) { ... } |
| :not() | :not(A) | li:not(:last-child) { ... } |
Specificity Calculation
Specificity is a 3-part score: (ID, CLASS, TYPE) — higher always wins. Inline styles beat all. !important overrides everything (use sparingly).
| Selector | Score (ID, CLASSorATTR, TYPE) |
|---|---|
| * | (0, 0, 0) — no specificity |
| p | (0, 0, 1) |
| .class | (0, 1, 0) |
| #id | (1, 0, 0) |
| p.card | (0, 1, 1) |
| #nav .link:hover | (1, 2, 0) |
| style="" | (1, 0, 0, 0) — inline beats everything |
| !important | Nuclear option — overrides all specificity |
#app .sidebar ul li a.active create maintenance nightmares. Prefer BEM class-only selectors .sidebar__link--active.Box Model
/* Default: box includes padding + border INSIDE the declared width */
* { box-sizing: border-box; } /* ← always set this globally */
.box {
width: 300px; /* content + padding + border = 300px total */
padding: 20px; /* shorthand: top right bottom left */
border: 2px solid #333;
margin: 16px auto; /* auto horizontally = center block elements */
/* margin collapse: adjacent vertical margins collapse to the larger one */
/* doesn't happen with flex/grid children or inline elements */
}
/* Outline = outside border, doesn't affect layout */
.focused { outline: 2px solid blue; outline-offset: 3px; }
Positioning
| Value | In flow? | Offset relative to | Use case |
|---|---|---|---|
| static | ✓ Yes | — | Default. No top/left/right/bottom effect. |
| relative | ✓ Yes | Itself (original position) | Slight nudge; creates stacking context for children |
| absolute | ✗ No | Nearest positioned ancestor | Tooltips, badges, overlays |
| fixed | ✗ No | Viewport | Sticky headers, floating buttons |
| sticky | ✓ Yes | Nearest scroll container | Section headers that stick while scrolling |
/* Tooltip pattern: parent=relative, tooltip=absolute */
.btn-wrapper { position: relative; }
.tooltip {
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
}
/* Sticky nav */
header { position: sticky; top: 0; z-index: 100; }
/* z-index only works on positioned elements */
/* stacking context created by: position+z-index, opacity<1, transform, will-change */
Custom Properties (CSS Variables)
CSS Custom Properties are live, cascade-aware, and can be changed at runtime — unlike SCSS variables which are compile-time.
/* Define on :root for global scope */
:root {
--color-primary: #38bdf8;
--color-bg: #0a0c10;
--spacing-base: 8px;
--spacing-lg: calc(var(--spacing-base) * 3);
--radius: 8px;
--font-sans: 'DM Sans', system-ui, sans-serif;
}
/* Use with var() */
.card {
background: var(--color-bg);
border-radius: var(--radius);
padding: var(--spacing-lg);
color: var(--color-primary, #fff); /* fallback */
}
/* Scoped override — dark theme via class toggle */
[data-theme="light"] {
--color-bg: #ffffff;
--color-primary: #0369a1;
}
/* Change at runtime with JS */
document.documentElement.style.setProperty('--color-primary', '#f472b6');
Pseudo-classes & Pseudo-elements
| Pseudo-class | Description |
|---|---|
| :hover | Mouse over |
| :focus / :focus-visible | Keyboard focus. Prefer :focus-visible for styling. |
| :active | Being clicked |
| :checked | Checkbox/radio checked |
| :disabled / :enabled | Form element state |
| :valid / :invalid | Native form validation state |
| :nth-child(n) | By position. Supports formulas: 2n, 3n+1, odd, even |
| :nth-of-type(n) | By position among same tag siblings |
| :first-child / :last-child | First/last among siblings |
| :only-child | Single child of parent |
| :empty | No children (incl. text nodes) |
| :not(sel) | Negation |
| :root | Document root — <html> |
/* Pseudo-elements — double colon convention */
.card::before { /* inserts before content */
content: '';
display: block;
/* decorative element */
}
.card::after { content: ''; } /* inserts after content */
p::first-line { font-weight: bold; }
p::first-letter{ font-size: 2em; float: left; }
::selection { background: #38bdf8; color: #000; }
::placeholder { color: #888; font-style: italic; }
/* Zebra rows without JS */
tr:nth-child(even) { background: #f5f5f5; }
Transitions & Animations
/* Transition — smooth property change on state */
.btn {
background: var(--accent);
transition: background 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}
.btn:hover {
background: var(--accent-dark);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0,0,0,0.3);
}
/* transition shorthand: property duration timing-function delay */
/* Keyframe animation */
@keyframes slideInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: slideInUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* animation shorthand: name duration timing fill-mode iteration-count direction */
/* Stagger cards using animation-delay */
.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 80ms; }
.card:nth-child(3) { animation-delay: 160ms; }
/* Respect motion preference */
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
Responsive Design
/* Mobile-first: write base styles for small, then expand up */
/* Common breakpoints */
@media (min-width: 640px) { /* sm — tablet portrait */ }
@media (min-width: 768px) { /* md — tablet landscape */ }
@media (min-width: 1024px) { /* lg — desktop */ }
@media (min-width: 1280px) { /* xl — wide desktop */ }
/* Container query — respond to parent width, not viewport */
.wrapper { container-type: inline-size; container-name: card; }
@container card (min-width: 400px) {
.card__body { flex-direction: row; }
}
/* Fluid typography — scales between min and max */
h1 {
font-size: clamp(1.8rem, 5vw, 3.5rem);
/* clamp(min, preferred, max) */
}
/* Fluid spacing */
.section { padding: clamp(32px, 5vw, 80px); }
/* Feature queries */
@supports (display: grid) {
.layout { display: grid; }
}
Variables, Maps & Architecture
SCSS compiles to CSS. It adds variables, nesting, mixins, functions, and partials — enabling scalable, DRY CSS architecture.
Variables & Maps
// SCSS variables — compile-time, not live like CSS custom properties
$color-primary: #38bdf8;
$color-bg: #0a0c10;
$spacing-base: 8px;
$font-sans: 'DM Sans', sans-serif;
// Maps — key-value collections
$colors: (
'primary': #38bdf8,
'success': #34d399,
'warning': #fbbf24,
'danger': #f87171,
);
$spacing: (
'xs': 4px,
'sm': 8px,
'md': 16px,
'lg': 24px,
'xl': 40px,
);
// Access map values
.alert { color: map.get($colors, 'danger'); }
// Generate utility classes from map
@each $name, $value in $colors {
.text-#{$name} { color: $value; }
.bg-#{$name} { background-color: $value; }
}
Nesting & BEM
// SCSS nesting with BEM methodology
.card {
background: var(--bg);
border-radius: 8px;
overflow: hidden;
// & = parent selector
&:hover {
border-color: var(--accent);
}
&--featured { // .card--featured (modifier)
border: 2px solid var(--accent);
}
&__header { // .card__header (element)
padding: 20px;
border-bottom: 1px solid var(--border);
}
&__title { // .card__title (element)
font-size: 18px;
font-weight: 700;
&--large { // .card__title--large (element+modifier)
font-size: 24px;
}
}
&__body {
padding: 20px;
}
}
// Generates: .card, .card:hover, .card--featured,
// .card__header, .card__title, .card__title--large, .card__body
Mixins & Functions
// Mixin — reusable block of declarations
@mixin flex-center($direction: row) {
display: flex;
align-items: center;
justify-content: center;
flex-direction: $direction;
}
@mixin truncate($lines: 1) {
@if $lines == 1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
@mixin respond-to($bp) {
$breakpoints: ('sm': 640px, 'md': 768px, 'lg': 1024px, 'xl': 1280px);
@media (min-width: map.get($breakpoints, $bp)) { @content; }
}
// Usage
.hero {
@include flex-center(column);
@include respond-to('lg') { flex-direction: row; }
}
.card__title { @include truncate(2); }
// Function — returns a value
@function rem($px, $base: 16) {
@return #{$px / $base}rem;
}
@function spacing($mult) {
@return $mult * 8px;
}
.card { padding: spacing(3); font-size: rem(14); } // → 24px, 0.875rem
// Placeholder extend — shared styles without duplication
%visually-hidden {
position: absolute; width: 1px; height: 1px;
overflow: hidden; clip: rect(0 0 0 0);
}
.sr-only { @extend %visually-hidden; }
Partials & @use Architecture
// Modern SCSS architecture — @use (replaces @import)
// File: _tokens.scss
$color-primary: #38bdf8;
$spacing-unit: 8px;
// File: _mixins.scss
@use 'tokens' as t;
@mixin card-base { padding: t.$spacing-unit * 2; }
// File: components/_card.scss
@use '../tokens' as t;
@use '../mixins';
.card {
@include mixins.card-base;
color: t.$color-primary;
}
// File: main.scss — entry point
@use 'tokens';
@use 'components/card';
@use 'components/button';
@use 'layouts/grid';
Control Flow
// @if / @else
@mixin theme-btn($theme) {
@if $theme == 'primary' {
background: $color-primary;
color: white;
} @else if $theme == 'outline' {
background: transparent;
border: 2px solid $color-primary;
} @else {
background: gray;
}
}
// @for loop
@for $i from 1 through 5 {
.col-#{$i} { flex: 0 0 percentage($i / 12); }
}
// @each loop
$sizes: sm 12px, md 16px, lg 20px, xl 24px;
@each $name, $size in $sizes {
.text-#{$name} { font-size: $size; }
}
// @while loop (rare — prefer @for)
$i: 1;
@while $i <= 3 {
.item-#{$i} { z-index: $i * 10; }
$i: $i + 1;
}
How Floats Work & Float vs Flex
Floats were originally designed for text wrapping around images. They were repurposed for layouts for over a decade — with painful workarounds. Flex has replaced them for layout.
Float Basics
/* Float takes an element out of normal flow */
/* Remaining content wraps around it */
.image-left {
float: left; /* left | right | none | inherit */
margin: 0 16px 8px 0;
width: 40%;
}
/* Problem: parent collapses to 0 height when all children are floated */
.container {
/* background color won't show — parent height = 0 */
}
/* clear stops float wrapping */
.clear-both { clear: both; } /* left | right | both */
Clearfix Patterns
/* Classic clearfix — fixes collapsed parent */
.clearfix::after {
content: "";
display: table;
clear: both;
}
/* Modern alternative — overflow */
.container { overflow: hidden; } /* creates BFC, contains floats */
/* Even more modern — display: flow-root */
.container { display: flow-root; } /* ← cleanest solution today */
/* Two-column float layout (the old way) */
.sidebar { float: left; width: 30%; }
.content { float: right; width: 68%; }
.wrapper { display: flow-root; } /* contains them */
Float vs Flexbox — Comparison
- Designed for text wrapping, hacked for layout
- Parent container collapses — needs clearfix
- Vertical alignment is painful or impossible
- Equal height columns require hacks
- Reordering requires DOM changes
- Complex responsive behavior
- Still valid for: image+text wrap
- Designed specifically for UI layout
- Container automatically sizes to children
- Vertical centering is trivial:
align-items: center - Equal height columns by default
- Visual reorder with
orderproperty - Responsive without media queries via
wrap - Browser support: 98%+ global
Core Concepts & Mental Model
Flexbox is a one-dimensional layout model. It distributes space and aligns items in a single row or column. Understanding axes is the key to mastering it.
The Two Axes
Every flex layout has two axes. Properties target different axes — this is the most common source of confusion.
| Axis | Default direction | Controls |
|---|---|---|
| Main axis | Horizontal (row) | justify-content, flex-grow, flex-shrink |
| Cross axis | Vertical (perpendicular) | align-items, align-self, align-content |
flex-direction: column, the axes swap. The main axis becomes vertical. So justify-content distributes vertically, and align-items aligns horizontally. Always think "main vs cross", not "horizontal vs vertical".Container Properties
Applied to the flex parent element. These control how children are laid out.
display: flex
.container {
display: flex; /* or inline-flex for inline container */
/* All direct children become flex items automatically */
}
flex-direction
Sets the main axis — the direction items flow.
| Value | Description |
|---|---|
| row | Default. Left to right (LTR). Main axis = horizontal. |
| row-reverse | Right to left. Items reversed. |
| column | Top to bottom. Main axis = vertical. |
| column-reverse | Bottom to top. |
flex-wrap
Controls whether items can wrap onto multiple lines.
| Value | Description |
|---|---|
| nowrap | Default. Items shrink to fit. Never wraps. |
| wrap | Items wrap to next line when needed. Most useful. |
| wrap-reverse | Wraps but cross-axis direction reversed. |
/* flex-flow = shorthand for flex-direction + flex-wrap */
.container { flex-flow: row wrap; }
justify-content — Main Axis Alignment
Distributes space along the main axis (between items and around them).
| Value | Behavior |
|---|---|
| flex-start | Default. Pack items to start of main axis. |
| flex-end | Pack items to end of main axis. |
| center | Center items along main axis. |
| space-between | First item at start, last at end, equal space between. |
| space-around | Equal space around each item (half-space at edges). |
| space-evenly | Equal space between all items AND at edges. |
| start / end | Respects writing-mode direction. |
align-items — Cross Axis Alignment
Aligns items along the cross axis within each line.
| Value | Behavior |
|---|---|
| stretch | Default. Items stretch to fill container height. |
| flex-start | Align to cross-axis start (top for row). |
| flex-end | Align to cross-axis end (bottom for row). |
| center | Center along cross axis. |
| baseline | Align by text baseline. Useful for mixed font sizes. |
| first baseline / last baseline | Baseline of first/last line of text. |
align-content — Multi-line Cross Axis
Only has effect when there are multiple flex lines (with wrap). Distributes space between lines.
| Value | Behavior |
|---|---|
| stretch | Default. Lines stretch to fill container. |
| flex-start / flex-end | Lines packed to start/end. |
| center | Lines centered. |
| space-between | First line at top, last at bottom, space in between. |
| space-around / space-evenly | Even spacing around/between lines. |
gap
The modern, clean way to add spacing between flex items. No margin hacks needed.
.container {
display: flex;
gap: 16px; /* uniform gap between items */
gap: 16px 24px; /* row-gap column-gap */
row-gap: 12px; /* between wrapped lines */
column-gap: 20px; /* between items in a row */
}
gap, developers used margin-right on items and :last-child { margin-right: 0 }. The gap property is supported in all modern browsers and is far cleaner.Item Properties
Applied to individual flex children. These override container alignment and control sizing behavior.
order
Changes visual position without changing DOM order. Default is 0. Lower values appear first.
.first { order: -1; } /* appears before all order:0 items */
.second { order: 0; } /* default */
.last { order: 1; } /* pushed to end */
/* Reorder sidebar on mobile */
@media (max-width: 768px) {
.sidebar { order: 2; } /* sidebar goes below content on mobile */
.content { order: 1; }
}
align-self
Overrides align-items for a single item.
.container { align-items: flex-start; }
.special-item { align-self: flex-end; } /* this item aligns to bottom */
/* Values: auto | flex-start | flex-end | center | baseline | stretch */
flex-grow, flex-shrink, flex-basis
The three sizing properties that control how items grow, shrink, and start out. Understanding these deeply is the key to confident flex layouts.
flex-basis — Initial Size
/* Sets the initial main-axis size of an item, BEFORE distributing free space */
.item { flex-basis: auto; } /* default: use item's width/height */
.item { flex-basis: 0; } /* start at zero, let grow fill */
.item { flex-basis: 200px; } /* fixed starting size */
.item { flex-basis: 30%; } /* percentage of container */
.item { flex-basis: max-content; }/* size to content */
flex-grow — Growing
/* How much of the EXTRA space an item claims, relative to siblings */
/* Default: 0 — items don't grow */
.item-a { flex-grow: 1; } /* claims 1 share of free space */
.item-b { flex-grow: 2; } /* claims 2 shares — gets TWICE as much extra */
.item-c { flex-grow: 0; } /* stays at flex-basis, no growth */
/* With 3 items of flex-grow: 1 — they split free space equally */
/* With 1, 2, 1 — middle item gets twice as much free space */
flex-shrink — Shrinking
/* How much an item shrinks when space is TIGHT, relative to siblings */
/* Default: 1 — items shrink equally */
.item-fixed { flex-shrink: 0; } /* NEVER shrink — stays at flex-basis */
.item-fluid { flex-shrink: 1; } /* shrinks if needed */
.item-fast { flex-shrink: 3; } /* shrinks 3x faster than siblings */
flex — Shorthand
/* flex: grow shrink basis */
.item { flex: 0 0 auto; } /* no grow, no shrink, auto size — "rigid" */
.item { flex: 1 1 0; } /* grow, shrink, start at 0 — "fluid" */
.item { flex: 1; } /* same as: flex: 1 1 0 */
.item { flex: auto; } /* same as: flex: 1 1 auto */
.item { flex: none; } /* same as: flex: 0 0 auto — rigid */
/* Common: sidebar + fluid content */
.sidebar { flex: 0 0 260px; } /* fixed width sidebar */
.content { flex: 1; } /* takes all remaining space */
flex: 0 0 80px
flex: none
Alignment Deep Dive
/* The centering one-liner — works in any direction */
.center-everything {
display: flex;
justify-content: center;
align-items: center;
}
/* Space between with cross-axis stretch */
.spread {
display: flex;
justify-content: space-between;
align-items: stretch; /* children fill height */
}
/* Push last item to far right — the "auto margin" trick */
.nav { display: flex; align-items: center; }
.nav .logo { }
.nav .links { }
.nav .cta { margin-left: auto; } /* consumes all remaining space */
/* Auto margin = flexible spacer */
/* Can push to any direction: margin-top: auto in column pushes item to bottom */
/* Sticky footer pattern */
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main { flex: 1; } /* grows to push footer down */
footer { /* stays at bottom always */ }
Complete Cheat Sheet
All flex properties at a glance, organized by container vs item.
Container Properties — Quick Reference
| Property | Values | Notes |
|---|---|---|
| display | flex | inline-flex | Activates flex context |
| flex-direction | row | row-reverse | column | column-reverse | Sets main axis |
| flex-wrap | nowrap | wrap | wrap-reverse | Multi-line wrapping |
| flex-flow | [direction] [wrap] | Shorthand for both above |
| justify-content | flex-start | flex-end | center | space-between | space-around | space-evenly | Main axis alignment |
| align-items | stretch | flex-start | flex-end | center | baseline | Cross axis, single line |
| align-content | same as justify-content + stretch | Cross axis, multi-line only |
| gap | [row-gap] [col-gap] | Space between items |
| row-gap | <length> | Between wrapped rows |
| column-gap | <length> | Between items in a row |
Item Properties — Quick Reference
| Property | Default | Values | Notes |
|---|---|---|---|
| order | 0 | <integer> | Visual sort order |
| flex-grow | 0 | <number> | Growth ratio. 0 = don't grow |
| flex-shrink | 1 | <number> | Shrink ratio. 0 = don't shrink |
| flex-basis | auto | auto | <length> | 0 | Initial size before grow/shrink |
| flex | 0 1 auto | [grow] [shrink] [basis] | Shorthand. prefer using this |
| align-self | auto | auto | flex-start | flex-end | center | baseline | stretch | Overrides align-items |
| min-width | auto | 0 | <length> | Set to 0 to allow shrinking below content |
Common Flex Patterns — One-Liners
/* Perfect center */
.center { display: flex; justify-content: center; align-items: center; }
/* Space between header items */
.header { display: flex; justify-content: space-between; align-items: center; }
/* Equal-width columns */
.equal-cols > * { flex: 1; }
/* Fixed sidebar + fluid main */
.layout { display: flex; }
.sidebar { flex: 0 0 260px; }
.main { flex: 1; min-width: 0; }
/* Responsive wrap */
.grid { display: flex; flex-wrap: wrap; gap: 16px; }
.grid > * { flex: 1 1 240px; }
/* Sticky footer */
body { display: flex; flex-direction: column; min-height: 100vh; }
main { flex: 1; }
/* Push item to end (auto margin trick) */
.nav .cta { margin-left: auto; }
.col .cta { margin-top: auto; } /* push to bottom of column */
/* Inline icon + text */
.btn { display: inline-flex; align-items: center; gap: 8px; }
/* Card with footer pinned to bottom */
.card { display: flex; flex-direction: column; height: 100%; }
.card__body { flex: 1; }
.card__footer { margin-top: auto; }
Common Gotchas & Fixes
| Problem | Cause | Fix |
|---|---|---|
| Item overflows container | flex-shrink: 1 but min-width: auto prevents shrinking | Add min-width: 0 to the item |
| Images stretch weirdly | align-items: stretch stretches img to full height | align-items: flex-start or align-self: flex-start on image |
| Items not equal height | Not using flex on the row | Parent gets display: flex, children default to align-items: stretch |
| Gap not working | Old browser | Use margin + :last-child { margin: 0 } as fallback |
| justify-content: center not centering | Child has width: 100% | Remove full width from child, or use text-align: center instead |
| flex: 1 not sharing equally | Items have different content sizes and flex-basis: auto | Use flex: 1 1 0 (basis 0) to share from zero |
| Vertical scroll inside flex child | Flex child has no explicit height limit | Add overflow-y: auto; min-height: 0 to the child |