Selectors
Attribute & Pseudo-class Selectors
CSS3 introduced powerful attribute selectors and pseudo-classes. ^= matches prefix, $= matches suffix, *= matches substring. nth-child enables zebra striping without JavaScript.
/* attribute selectors */
input[type="text"] { border: 1px solid blue; }
a[href^="https"] { color: green; }
a[href$=".pdf"] { color: red; }
a[href*="example"] { font-weight: bold; }
/* pseudo-classes */
a:visited { color: purple; }
li:first-child { font-weight: bold; }
li:nth-child(odd) { background: #f0f0f0; }
input:focus { outline: 2px solid blue; }Structural Pseudo-classes
Structural pseudo-classes select elements by their position in the DOM. :first-child / :last-child target edges. nth-child(an+b) supports formulas: 2n is even, 2n+1 is odd, 3n is every third. :nth-of-type counts only siblings of the same element type. :empty matches elements with no children.
/* positional pseudo-classes */
li:first-child { color: green; }
li:last-child { color: red; }
li:only-child { font-weight: bold; }
/* nth variants */
li:nth-child(3) { color: blue; }
li:nth-child(2n+1) { background: #eee; } /* odd */
li:nth-last-child(2) { color: orange; }
/* type-based */
p:first-of-type { font-size: 1.2em; }
p:nth-of-type(even) { color: gray; }
div:empty { display: none; }:is, :where, and :has
:is() and :where() group selectors for brevity. The difference: :is() takes the highest specificity of its arguments, while :where() always has zero specificity — ideal for low-override base styles. :has() is the long-awaited parent/relational selector, letting you style an element based on its descendants.
/* :is() - matches any selector in the list */
:is(h1, h2, h3) { color: navy; }
:is(.btn, .link):hover { opacity: 0.8; }
/* :where() - same as :is but zero specificity */
:where(article, section) p { line-height: 1.6; }
/* :has() - parent selector (selects element that has a match) */
div:has(> img) { padding: 0; }
card:has(.badge) { border-color: gold; }
form:has(input:invalid) button { opacity: 0.5; }Negation & Form Pseudo-classes
:not() negates a selector and now accepts a list. Form pseudo-classes (:required, :valid, :invalid, :checked, :placeholder-shown, :in-range) enable styling based on input state without JavaScript — great for inline validation feedback and custom checkbox/radio styling.
/* :not() excludes matching elements */
input:not([type="checkbox"]) { display: block; }
li:not(:last-child) { border-bottom: 1px solid #ddd; }
/* form state pseudo-classes */
input:required { border-color: red; }
input:optional { border-color: #ccc; }
input:valid { border-color: green; }
input:invalid { border-color: red; }
input:in-range { color: black; }
input:out-of-range { color: red; }
input:placeholder-shown { background: #fafafa; }
input:checked + label { font-weight: bold; }Specificity & Cascade
Specificity decides which rule applies when selectors conflict. Count IDs, classes, and elements as (a,b,c). !important overrides the cascade but breaks maintainability — avoid it. When specificity ties, the later rule in source order wins. DevTools shows the computed cascade.
/* specificity ranking: inline > ID > class/attr/pseudo > type/element */
#nav .item { color: red; } /* 1,1,0 */
.menu .item { color: blue; } /* 0,2,0 */
li.item { color: green; } /* 0,1,1 */
/* !important overrides normal declarations */
.btn { color: white !important; }
/* source order wins on equal specificity */
.late { color: purple; } /* wins over .early */Box Model
box-sizing
box-sizing: border-box includes padding and border within the declared width/height, making sizing predictable. content-box (default) adds them on top. Applying border-box globally is the most important CSS reset — it eliminates the most common layout math headaches.
/* default: width = content only */
.box-content { box-sizing: content-box; width: 200px; padding: 20px; } /* total 240px */
/* padding & border inside width */
.box-border { box-sizing: border-box; width: 200px; padding: 20px; border: 2px solid; } /* total 200px */
/* universal reset - apply everywhere */
*, *::before, *::after {
box-sizing: border-box;
}Margin & Padding
Padding is inside the border (inherits background); margin is outside (transparent). Vertical margins between adjacent block elements collapse to the larger value — horizontal margins never collapse. margin: 0 auto centers a block element with a defined width. Negative margins can pull content but use sparingly.
.box {
/* top right bottom left (clockwise) */
margin: 10px 20px 15px 25px;
/* 2 values: vertical horizontal */
padding: 10px 20px;
/* single value: all sides */
margin: 0 auto; /* horizontal centering for fixed-width block */
}
/* margin collapse (vertical only) */
.a { margin-bottom: 30px; }
.b { margin-top: 20px; }
/* gap between .a and .b = 30px, not 50px (collapsed to larger) */
/* negative margins pull elements together */
.pull-left { margin-left: -10px; }box-shadow
box-shadow syntax: offset-x offset-y blur-radius spread-radius color. Positive offsets push shadow right/down. inset creates an inner shadow. Multiple shadows layer with the first on top. Use rgba for translucent, natural shadows. Hard shadows (zero blur) create a flat, bold aesthetic.
/* offset-x offset-y blur spread color */
.card { box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
/* inset shadow (inside the box) */
.inset { box-shadow: inset 0 2px 4px rgba(0,0,0,0.2); }
/* multiple shadows (first = top layer) */
.layered { box-shadow: 0 1px 2px #000, 0 8px 24px rgba(0,0,0,0.15); }
/* no blur, hard shadow (neo-brutalism) */
.hard { box-shadow: 6px 6px 0 #000; }
/* glow effect */
.glow { box-shadow: 0 0 20px rgba(0,150,255,0.6); }Display Types
display: none removes the element from layout (no space reserved). block elements take full width and stack vertically; inline elements flow with text and ignore width/height. inline-block combines inline flow with block sizing. flow-root clears floats without clearfix hacks. contents makes the box vanish while keeping children in the layout.
/* block: full width, new line */
div, p { display: block; }
/* inline: flows with text, ignores width/height */
span, a { display: inline; }
/* inline-block: flows inline but accepts width/height */
.tag { display: inline-block; width: 100px; }
/* none: removed from layout entirely */
.hidden { display: none; }
/* modern display values */
.flow { display: flow-root; } /* new block formatting context */
.contents { display: contents; } /* element's box disappears, children participate */Overflow
overflow controls how content exceeding the box is handled. visible (default) lets it spill; hidden clips it; auto adds scrollbars when needed; scroll always shows them. clip (newer) is like hidden but prevents any scrolling, even via JS. Use overflow-y/x to control axes independently. Setting overflow to anything but visible creates a new formatting context.
.scrollable {
overflow: auto; /* scrollbars when needed */
overflow-y: scroll; /* always show vertical scrollbar */
overflow-x: hidden; /* hide horizontal overflow */
}
/* visible (default) - content spills out */
.spill { overflow: visible; }
/* clip - like hidden but no programmatic scroll */
.clipped { overflow: clip; }
/* clip to a shape */
.custom-clip {
overflow: clip;
overflow-clip-margin: 20px;
}
/* longhand for both axes */
.both { overflow-x: hidden; overflow-y: auto; }Flexbox
Flex Container
display: flex turns an element into a flex container. justify-content aligns items along the main axis (horizontal in row); align-items aligns along the cross axis. flex-wrap lets items wrap to new lines. gap replaces the margin-based spacing hack. Flexbox excels at one-dimensional layouts (rows or columns).
.container {
display: flex;
flex-direction: row; /* row | row-reverse | column | column-reverse */
justify-content: center; /* main axis: flex-start | center | space-between | space-around | space-evenly */
align-items: center; /* cross axis: stretch | flex-start | center | baseline */
flex-wrap: wrap; /* nowrap | wrap | wrap-reverse */
gap: 16px; /* spacing between items */
align-content: space-between; /* multi-line spacing */
}Flex Items
flex-grow controls how an item expands to fill extra space (1 = equal share). flex-shrink controls shrinking. flex-basis is the initial size. The shorthand flex: 1 means grow equally from zero basis — perfect for equal columns. align-self overrides the container's align-items for a single item. order reorders visually without changing DOM order.
.item {
flex-grow: 1; /* grow to fill extra space (0 = don't grow) */
flex-shrink: 1; /* shrink when space is tight (0 = don't shrink) */
flex-basis: 200px; /* initial size before growing/shrinking */
/* shorthand: flex: grow shrink basis */
flex: 1 1 200px; /* flexible from 200px */
flex: 1; /* = 1 1 0% - equal distribution */
flex: 0 0 250px; /* fixed width, no grow/shrink */
align-self: flex-end; /* override align-items for this item */
order: 2; /* visual reordering (default 0) */
}Flex Alignment Patterns
Flexbox makes centering trivial: justify-content: center + align-items: center. A sticky footer uses a column flex body with min-height: 100vh and the main content set to flex: 1. For navigation bars, space-between pushes the first and last items to opposite edges. Auto margins (margin-left: auto) are a flexible alternative to justify-content for pushing items.
/* perfect centering */
.center {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
/* sticky footer: body is flex column */
body { display: flex; flex-direction: column; min-height: 100vh; }
main { flex: 1; } /* grows to fill, pushing footer down */
/* navbar: space between */
.nav { display: flex; justify-content: space-between; }
/* auto margins absorb space */
.push-right { margin-left: auto; }Flex Wrap & Responsive Grid
flex-wrap: wrap lets items flow to new lines when they run out of space. flex: 1 1 280px creates cards that start at 280px and grow to fill the row. This makes a responsive card grid without media queries. Beware: the last row's items stretch to fill, which may look uneven — for consistent card grids, CSS Grid is often a better choice.
.cards {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.card {
/* start at 280px, grow & shrink fluidly */
flex: 1 1 280px;
/* prevent stretching beyond readable width */
max-width: 100%;
}
/* OR fixed-width cards that wrap */
.card-fixed {
flex: 0 0 300px;
}
/* last row items stretch to fill - to avoid, use grid instead */
/* gap wraps with items automatically */Flex Direction & Axes
flex-direction determines the main axis. In row (default), justify-content controls horizontal alignment; in column, it controls vertical alignment — the axes swap. Always ask 'which is the main axis?' when aligning. row-reverse and column-reverse flip item order, which also affects how justify-content distributes space.
/* row: main axis = horizontal */
.row { display: flex; flex-direction: row; justify-content: center; }
/* column: main axis = vertical (axes swap!) */
.col { display: flex; flex-direction: column; align-items: center; }
/* reverse order */
.reverse { display: flex; flex-direction: row-reverse; }
/* centering in column: justify-content centers vertically */
.col-center {
display: flex;
flex-direction: column;
justify-content: center; /* vertical center */
align-items: center; /* horizontal center */
}Grid Layout
Grid Container
display: grid creates a two-dimensional layout. grid-template-columns defines column tracks; the fr unit distributes free space proportionally. repeat(auto-fit, minmax(200px, 1fr)) is the magic recipe for responsive grids — columns auto-create and resize without media queries. gap adds space between tracks cleanly.
.grid {
display: grid;
/* fixed columns */
grid-template-columns: 200px 1fr 200px;
/* responsive auto columns */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-template-rows: auto 1fr auto;
gap: 20px; /* row + column gap */
row-gap: 20px; /* gap between rows */
column-gap: 16px; /* gap between columns */
}Grid Template Areas
grid-template-areas lets you draw the layout visually with named regions — extremely readable. Each quoted string is a row; names place items, and a dot marks an empty cell. An item with grid-area: name fills that region. This is the clearest way to build page-level layouts like app shells.
.layout {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* empty cell with a dot */
grid-template-areas: "header header" "sidebar .";Grid Item Placement
Grid items can be placed explicitly by line numbers. grid-column: 1 / 3 spans from line 1 to line 3 (covering 2 columns). The span keyword sizes by track count. Lines can also be named in grid-template-columns for readability. Without explicit placement, items auto-flow into available cells.
.item {
/* place by line numbers: row-start / col-start / row-end / col-end */
grid-column: 1 / 3; /* span columns 1 to 3 */
grid-row: 1 / 2;
/* span keyword */
grid-column: span 2; /* span 2 columns */
grid-row: 2 / span 3;
/* shorthand: grid-area: row-start / col-start / row-end / col-end */
grid-area: 1 / 1 / 3 / 4;
/* name a line and reference it */
grid-column-start: content-start;
}Grid Alignment
Grid alignment works on two levels. justify-content/align-content position the entire grid within the container when there's leftover space. justify-items/align-items align items inside their cells (stretch by default). justify-self/align-self override alignment per item. 'justify' = inline/main axis, 'align' = block/cross axis.
.grid {
display: grid;
grid-template-columns: repeat(3, 100px);
/* align tracks within container */
justify-content: center; /* center the grid horizontally */
align-content: center; /* center vertically (needs fixed height) */
/* align items within their cells */
justify-items: stretch; /* fill cell width (default) */
align-items: center; /* center within cell height */
}
.item {
justify-self: start; /* override justify-items for one item */
align-self: end;
}auto-fit vs auto-fill
Both create as many tracks as fit, but behave differently with few items. auto-fit collapses unused tracks and stretches existing items to fill the row. auto-fill keeps empty tracks, so items stay their min size. Use auto-fit when you want items to grow and fill space; use auto-fill when you want a fixed, consistent item size.
/* auto-fit: empty tracks collapse, items stretch to fill */
.autofit {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
/* auto-fill: empty tracks remain, items keep their size */
.autofill {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
/* with few items:
auto-fit -> items stretch to fill the row
auto-fill -> items stay small, leaving empty columns */Positioning
Position Values
static is the default. relative offsets an element from its normal flow position (space is preserved). absolute removes it from flow and positions it relative to the nearest positioned ancestor (or viewport). fixed is relative to the browser viewport. sticky toggles between relative and fixed based on scroll position — ideal for sticky headers.
/* static - default, flows normally */
.static { position: static; }
/* relative - offset from its normal position */
.relative { position: relative; top: 10px; left: 20px; }
/* absolute - positioned relative to nearest positioned ancestor */
.absolute { position: absolute; top: 0; right: 0; }
/* fixed - positioned relative to viewport (stays on scroll) */
.fixed { position: fixed; bottom: 0; left: 0; }
/* sticky - relative until scroll threshold, then fixed */
.sticky { position: sticky; top: 0; }Absolute Centering
Several ways to absolutely center. The translate(-50%, -50%) trick works on elements of any size. The inset: 0 + margin: auto technique requires explicit dimensions. The modern approach is display: grid with place-items: center (shorthand for align-items + justify-items) — no positioning math needed and the simplest method.
/* classic: position absolute + transform */
.center {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
/* inset: 0 + margin: auto (needs width/height) */
.center-auto {
position: absolute;
inset: 0; /* top/right/bottom/left: 0 */
margin: auto;
width: 300px;
height: 200px;
}
/* modern: no positioning needed */
.center-grid { display: grid; place-items: center; }Sticky Positioning
position: sticky keeps an element in normal flow until it reaches a threshold (e.g., top: 0), then it sticks like fixed. Perfect for sticky headers or table-of-contents sidebars. Gotcha: any ancestor with overflow: hidden/auto/scroll (in the scroll direction) becomes the containing block and breaks expected behavior. Always set a background and z-index on sticky elements.
.header {
position: sticky;
top: 0; /* sticks when header hits top of viewport */
z-index: 100;
background: white;
}
/* sticky sidebar that scrolls with content until threshold */
.sidebar {
position: sticky;
top: 60px; /* offset below the fixed header */
max-height: calc(100vh - 60px);
overflow-y: auto;
}
/* NOTE: a parent with overflow: hidden breaks sticky */z-index & Stacking Contexts
z-index only works on positioned (non-static) elements. A stacking context is created by position + z-index, but also by opacity < 1, transform, filter, will-change, and isolation: isolate. Inside a stacking context, child z-index values are isolated — a child can never render above a sibling of an ancestor. Understanding this prevents z-index escalation wars.
.modal { position: fixed; z-index: 1000; }
.overlay { position: fixed; z-index: 999; }
.card { position: relative; z-index: 1; }
/* creating a stacking context (isolates z-index) */
.context {
position: relative;
z-index: 0; /* children's z-index only matters within this */
}
/* other properties that create stacking contexts:
opacity < 1, transform, filter, will-change, isolation: isolate */
.isolated { isolation: isolate; }inset Shorthand
inset is the logical shorthand for top, right, bottom, and left — like margin/padding it takes 1, 2, or 4 values. inset: 0 stretches an absolutely-positioned element to fill its containing block. It's cleaner than declaring four separate properties and pairs well with margin: auto for centering.