Skip to content

CSS Cheatsheet

Style sheet language for describing the presentation of web pages.

01

Selectors

Basic Selectors

CSS selectors target HTML elements for styling. Type selectors match element names (p, div). Class selectors (.) match class attributes and can be reused. ID selectors (#) match a single element and should be unique. The universal selector (*) matches everything. Group selectors with commas to share styles.

css
/* Type selector - all <p> elements */
p { color: #333; }

/* Class selector - elements with class="btn" */
.btn { padding: 8px 16px; }

/* ID selector - element with id="header" */
#header { background: #fff; }

/* Universal selector - all elements */
* { box-sizing: border-box; }

/* Grouping selector */
h1, h2, h3 { font-family: Arial, sans-serif; }

Combinators

Combinators define relationships between elements. Descendant (space) matches any nested element. Child (>) matches only direct children. Adjacent sibling (+) matches the element immediately after. General sibling (~) matches all siblings after. Understanding combinators is essential for precise styling without extra classes.

css
/* Descendant - any <a> inside .nav */
.nav a { text-decoration: none; }

/* Child - direct <li> children of <ul> */
ul > li { list-style: none; }

/* Adjacent sibling - <p> right after <h1> */
h1 + p { font-size: 1.2em; }

/* General sibling - all <p> after <h1> */
h1 ~ p { color: gray; }

Attribute Selectors

Attribute selectors match elements based on attribute values. [attr] matches presence. [attr=val] matches exact value. ^= matches prefix, $= matches suffix, *= matches substring. These are powerful for styling form inputs, links by type, or elements with data attributes without adding extra classes.

css
/* Elements with type attribute */
[type] { border: 1px solid #ccc; }

/* Exact match */
[type="text"] { padding: 4px; }

/* Starts with */
a[href^="https"] { color: green; }

/* Ends with */
a[href$=".pdf"] { color: red; }

/* Contains */
a[href*="example"] { font-weight: bold; }

Pseudo-classes

Pseudo-classes select elements based on state or position. Interactive: :hover, :focus, :active, :visited, :disabled. Structural: :first-child, :last-child, :nth-child(n), :nth-of-type(n). :not() negates a selector. :nth-child(odd/even) creates zebra stripes. :nth-child(3n) selects every 3rd element. These reduce the need for extra classes.

css
/* Interactive states */
a:hover { color: red; }
a:visited { color: purple; }
input:focus { border-color: blue; }
input:disabled { background: #eee; }
button:active { transform: scale(0.98); }

/* Structural */
li:first-child { font-weight: bold; }
li:last-child { border: none; }
li:nth-child(odd) { background: #f9f9f9; }
li:nth-child(3n) { color: blue; }
p:not(.highlight) { color: #333; }

Pseudo-elements

Pseudo-elements (::double-colon) style specific parts of elements. ::before and ::after insert generated content (requires content property). ::first-letter and ::first-line style text portions. ::selection styles highlighted text. ::placeholder styles input placeholders. Note: ::before/::after are inline by default — set display:block for block behavior.

css
/* First line of a paragraph */
p::first-line { font-weight: bold; }

/* First letter (drop cap) */
p::first-letter { font-size: 3em; float: left; }

/* Insert content before/after */
.quote::before { content: "\201C"; }
.quote::after { content: "\201D"; }

/* Selection styling */
::selection { background: yellow; color: black; }

/* Placeholder styling */
input::placeholder { color: #999; }

Specificity & !important

Specificity determines which rule applies when selectors conflict. Inline styles > IDs > classes > types. When specificity ties, the last rule wins. !important overrides everything but breaks the cascade — avoid it. Use DevTools to inspect specificity. Prefer class-based selectors for maintainability. Increasing specificity by repeating classes (.btn.btn) is a hack — avoid it.

css
/* Specificity hierarchy (low to high):
   1. Type selectors (p, div)         = 0,0,0,1
   2. Class selectors (.btn)          = 0,0,1,0
   3. ID selectors (#header)          = 0,1,0,0
   4. Inline styles (style="...")     = 1,0,0,0
   5. !important                      = overrides all
*/

/* ID beats class */
#nav .link { color: red; }   /* wins */
.nav .link { color: blue; }

/* Avoid !important unless necessary */
.critical { color: red !important; }
02

Box Model

Margin & Padding

Every element is a box with content, padding, border, and margin. Padding is inside the border (affects background), margin is outside (transparent). Shorthand: 1 value = all sides, 2 values = top/bottom left/right, 3 = top left/right bottom, 4 = top right bottom left (clockwise). margin: 0 auto centers block elements with a defined width.

css
.box {
  /* TRBL: top right bottom left */
  margin: 10px 20px 10px 20px;

  /* Shorthand: top/bottom  left/right */
  margin: 10px 20px;

  /* All sides */
  padding: 15px;

  /* Individual sides */
  margin-top: 0;
  padding-left: 24px;

  /* Center horizontally (block) */
  margin: 0 auto;
}

Border & Outline

border adds a line around padding, affecting layout. outline draws outside the border without affecting layout — useful for focus indicators. border-radius rounds corners (single value or per-corner: top-left top-right bottom-right bottom-left). outline-offset adds space between border and outline. Always provide visible focus styles for accessibility.

css
.box {
  /* Shorthand: width style color */
  border: 2px solid #333;

  /* Individual sides */
  border-bottom: 3px dashed red;
  border-radius: 8px;

  /* Outline (doesn't affect layout) */
  outline: 2px solid blue;
  outline-offset: 4px;
}

/* Remove default focus, add custom */
button:focus {
  outline: none;
  box-shadow: 0 0 0 3px rgba(0,123,255,0.5);
}

Box Sizing

box-sizing: content-box (default) means width/height apply only to content — padding and border add to the total size. box-sizing: border-box includes padding and border in the width/height, making sizing predictable. Always set border-box globally for consistent layouts. This is one of the most important CSS resets.

css
/* Default: width = content only */
.content-box {
  box-sizing: content-box;
  width: 200px;
  padding: 20px;
  border: 5px solid;
  /* Total width: 200 + 40 + 10 = 250px */
}

/* Recommended: width includes padding+border */
.border-box {
  box-sizing: border-box;
  width: 200px;
  padding: 20px;
  border: 5px solid;
  /* Total width: 200px */
}

/* Apply globally */
*, *::before, *::after {
  box-sizing: border-box;
}

Box Shadow

box-shadow adds shadow effects: offset-x offset-y blur-radius spread-radius color. Positive offsets move shadow right/down. inset creates an inner shadow. Multiple shadows are layered (first = top). Use rgba for semi-transparent shadows that blend naturally. Shadows are great for depth but overuse hurts performance — prefer subtle shadows.

css
/* offset-x offset-y blur spread color */
.card {
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

/* Multiple shadows */
.button {
  box-shadow:
    0 1px 2px rgba(0,0,0,0.1),
    0 4px 8px rgba(0,0,0,0.05);
}

/* Inset shadow */
.input:focus {
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
}

/* No shadow */
.no-shadow { box-shadow: none; }

Display & Visibility

display: none removes the element from layout entirely (no space). visibility: hidden hides it but keeps its space. opacity: 0 makes it transparent but still interactive. block elements take full width and break lines. inline elements flow with text. inline-block combines inline flow with block dimensions (width, height). Use display: none for toggling content.

css
/* Display types */
div { display: block; }           /* full width, line break */
span { display: inline; }         /* content width, no break */
.inline-block { display: inline-block; } /* inline but block props */
.flex { display: flex; }          /* flex container */
.grid { display: grid; }          /* grid container */
.none { display: none; }          /* removed from layout */

/* Visibility (keeps space) */
.hidden { visibility: hidden; }

/* Opacity (keeps space, can interact) */
.fade { opacity: 0; }
03

Colors & Units

Color Formats

CSS supports multiple color formats. Hex (#rrggbb) is most common. RGB/RGBA adds alpha transparency. HSL (Hue 0-360, Saturation%, Lightness%) is intuitive — change hue to shift color, lightness for shades. currentColor inherits the element's color property. Modern CSS also supports oklch() and color() for wider gamuts. Use rgba/hsla for transparency.

css
/* Named colors */
.color { color: red; background: tomato; }

/* Hexadecimal */
.hex { color: #ff6600; }        /* 6-digit */
.hex-short { color: #f60; }     /* 3-digit shorthand */

/* RGB / RGBA */
.rgb { color: rgb(255, 102, 0); }
.rgba { background: rgba(0, 0, 0, 0.5); }

/* HSL / HSLA (hue, saturation, lightness) */
.hsl { color: hsl(20, 100%, 50%); }
.hsla { background: hsla(120, 100%, 50%, 0.3); }

/* Current color keyword */
.box { color: blue; border: 2px solid currentColor; }

Length Units

px is absolute and predictable. em is relative to the parent's font-size (compounds when nested). rem is relative to the root (html) font-size — consistent and preferred for accessibility. % is relative to the parent. vh/vw are relative to the viewport (100vh = full height). Use rem for font sizes, % for layouts, and px for borders/fine details.

css
/* Absolute units */
.px { font-size: 16px; }     /* 1px = 1/96 inch */
.pt { font-size: 12pt; }     /* 1pt = 1/72 inch */

/* Relative to parent font-size */
.em { font-size: 1.5em; }    /* 1.5x parent */

/* Relative to root font-size (html) */
.rem { font-size: 1.2rem; }  /* 1.2x root (usually 16px) */

/* Relative to parent dimensions */
.pct { width: 50%; }         /* 50% of parent */

/* Viewport units */
.vh { height: 100vh; }       /* 100% of viewport height */
.vw { width: 50vw; }         /* 50% of viewport width */
.vmin { font-size: 2vmin; }  /* 2% of smaller dimension */

CSS Functions

calc() performs math with mixed units (e.g., 100% - 250px). min() picks the smallest value (great for responsive max-widths). max() picks the largest. clamp(min, preferred, max) creates fluid values that scale between bounds — perfect for responsive typography. var() references custom properties with an optional fallback. These functions make CSS dynamic without media queries.

css
/* calc() - mathematical calculations */
.sidebar { width: calc(100% - 250px); }
.gap { margin: calc(1rem + 10px); }

/* min() / max() - choose smaller/larger */
.responsive { width: min(90%, 1200px); }
.min-width { width: max(300px, 50%); }

/* clamp() - fluid sizing with bounds */
.title { font-size: clamp(1.5rem, 4vw, 3rem); }

/* var() - use custom properties */
.btn { color: var(--primary, #007bff); }

Gradients

Gradients create smooth color transitions. linear-gradient(direction, color1, color2) goes in a direction (to right, 45deg). radial-gradient expands from a center point. conic-gradient rotates around a center. You can add color stops with positions: linear-gradient(to right, red 0%, blue 50%, green 100%). Gradients are images, not colors, and can be used anywhere background-image is accepted.

css
/* Linear gradient */
.bg1 {
  background: linear-gradient(to right, #ff0000, #0000ff);
}

/* Diagonal with angle */
.bg2 {
  background: linear-gradient(45deg, #ff0000, #00ff00, #0000ff);
}

/* Radial gradient */
.bg3 {
  background: radial-gradient(circle, #ff0000, #0000ff);
}

/* Conic gradient */
.bg4 {
  background: conic-gradient(red, yellow, green, blue, red);
}

Filters & Blend Modes

filter applies visual effects: blur(), brightness(), contrast(), grayscale(), sepia(), hue-rotate(), invert(), opacity(), saturate(). Multiple filters chain together. mix-blend-mode blends an element with what's behind it (multiply, screen, overlay, etc.). backdrop-filter applies filters to the area behind an element (great for glassmorphism). Filters can impact performance — use sparingly.

css
/* Filters */
img.bw { filter: grayscale(100%); }
img.blur { filter: blur(5px); }
img.bright { filter: brightness(1.5); }
img.sepia { filter: sepia(0.8); }

/* Multiple filters */
img.vintage {
  filter: sepia(0.5) contrast(1.2) brightness(0.9);
}

/* Blend mode */
.overlay {
  background: rgba(255,0,0,0.5);
  mix-blend-mode: multiply;
}
04

Typography

Font Properties

font-family accepts a fallback list — the browser uses the first available. Always end with a generic family (serif, sans-serif, monospace). font-weight ranges from 100 (thin) to 900 (black). line-height: 1.5 means 1.5x the font size — 1.4-1.6 is ideal for body text readability. The font shorthand must include size and family at minimum.

css
body {
  /* Font shorthand: style weight size/line-height family */
  font: italic bold 16px/1.5 Arial, sans-serif;

  /* Individual properties */
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  font-weight: 400;  /* 100-900, normal=400, bold=700 */
  font-style: italic; /* normal, italic, oblique */
  line-height: 1.5;
}

Text Styling

text-align controls horizontal alignment. text-decoration adds lines (underline, overline, line-through). text-transform changes casing. letter-spacing (tracking) and word-spacing adjust spacing between characters and words. white-space: nowrap prevents text wrapping. For vertical alignment, use vertical-align (inline) or flexbox/grid alignment (block).

css
p {
  text-align: justify;        /* left, right, center, justify */
  text-decoration: underline; /* none, overline, line-through */
  text-transform: capitalize; /* uppercase, lowercase, capitalize */
  text-indent: 2em;           /* indent first line */
  letter-spacing: 0.05em;     /* tracking */
  word-spacing: 0.1em;
  white-space: nowrap;        /* prevent wrapping */
}

a {
  text-decoration: none;
  color: #0066cc;
}

Text Overflow & Wrapping

To truncate text with an ellipsis, you need three properties: white-space: nowrap, overflow: hidden, and text-overflow: ellipsis. overflow-wrap: break-word breaks long words to prevent overflow. word-break: break-all breaks at any character. text-wrap: balance (newer) balances line lengths for headings. Always set a max-width or width for truncation to work.

css
/* Truncate with ellipsis */
.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 200px;
}

/* Break long words */
.break-word {
  overflow-wrap: break-word;
  word-break: break-all;
}

/* Balance text wrapping (newer) */
h1 {
  text-wrap: balance;
}

Web Fonts (@font-face)

@font-face loads custom fonts. woff2 is the modern format (best compression). Provide woff as fallback. font-display: swap shows fallback text immediately, then swaps when the font loads (prevents invisible text). Always declare a fallback font family. Google Fonts provides hosted fonts via @import or <link>. Preload critical fonts for performance.

css
@font-face {
  font-family: 'MyCustomFont';
  src: url('fonts/custom.woff2') format('woff2'),
       url('fonts/custom.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: 'MyCustomFont', Arial, sans-serif;
}

/* Google Fonts import */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');

List & Link Styling

list-style: none removes default bullets for custom navigation or styled lists. list-style-type changes the bullet (disc, circle, square, decimal, none). list-style-position: inside puts bullets inside the content area. For links, the LVHA order matters: :link, :visited, :hover, :active. This ensures proper cascade of link states.

css
/* Reset list styles */
ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* Custom bullets */
ul.custom {
  list-style-type: square;
  list-style-position: inside;
}

/* Image bullets */
ul.image {
  list-style-image: url('bullet.png');
}

/* Link states (order matters!) */
a:link { color: blue; }
a:visited { color: purple; }
a:hover { color: red; text-decoration: underline; }
a:active { color: orange; }
05

Flexbox Layout

Flex Container

display: flex creates a flex container. justify-content aligns items along the main axis (horizontal in row). align-items aligns along the cross axis (vertical). flex-direction changes the main axis. flex-wrap allows items to wrap to new lines. gap sets spacing between items (replaces margin hacks). Flexbox is ideal for 1D layouts (rows OR columns).

css
.container {
  display: flex;

  /* Main axis alignment */
  justify-content: space-between;
  /* flex-start, center, flex-end, space-around, space-evenly */

  /* Cross axis alignment */
  align-items: center;
  /* flex-start, center, flex-end, stretch, baseline */

  /* Direction */
  flex-direction: row;
  /* row, row-reverse, column, column-reverse */

  /* Wrapping */
  flex-wrap: wrap;
  /* nowrap, wrap, wrap-reverse */

  /* Gap between items */
  gap: 16px;
}

Flex Items

flex-grow controls how items share extra space (0 = don't grow, 1 = grow equally). flex-shrink controls how items shrink when space is limited. flex-basis sets the initial size. The shorthand flex: 1 means flex-grow:1, flex-shrink:1, flex-basis:0%. align-self overrides the container's align-items for one item. order reorders items visually without changing DOM.

css
.item {
  /* Grow: how much space to take (0 = don't grow) */
  flex-grow: 1;

  /* Shrink: how much to shrink when space is tight */
  flex-shrink: 0;

  /* Basis: initial size before growing/shrinking */
  flex-basis: 200px;

  /* Shorthand: flex: grow shrink basis */
  flex: 1 0 200px;

  /* Override align-items for this item */
  align-self: flex-end;

  /* Order (default 0, lower = earlier) */
  order: -1;
}

Common Flexbox Patterns

Flexbox makes centering trivial: justify-content: center + align-items: center. For a sticky footer, make body a flex column with min-height: 100vh and let the main content flex: 1. For navbars, justify-content: space-between pushes the first and last items to opposite ends. These patterns solve common layout problems that were difficult before flexbox.

css
/* Center anything */
.center {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

/* Sticky footer */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.main { flex: 1; }

/* Navbar: logo left, links right */
.nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

Flex Direction & Wrap

flex-direction: column creates vertical layouts (useful for stacking). flex-wrap: wrap allows items to flow to new lines when they run out of space. flex: 1 1 300px means items start at 300px, grow to fill space, and shrink as needed. align-content controls spacing between wrapped lines (only works with wrap). This pattern creates responsive card grids.

css
/* Horizontal (default) */
.row { display: flex; flex-direction: row; }

/* Vertical */
.column { display: flex; flex-direction: column; }

/* Reverse */
.row-rev { display: flex; flex-direction: row-reverse; }

/* Wrap to new lines */
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}
.card { flex: 1 1 300px; } /* grow, shrink, basis */

/* Align wrapped lines */
.cards { align-content: space-between; }

Flexbox Alignment Deep Dive

In flex-direction: column, the main axis is vertical and cross axis is horizontal — justify-content and align-items swap roles. margin: auto on a flex item absorbs all available space, centering it perfectly. This is an alternative to justify-content/align-items. Understanding which axis is 'main' vs 'cross' is key to mastering flexbox alignment.

css
.container {
  display: flex;
  flex-direction: column;

  /* Main axis = vertical (column) */
  justify-content: flex-start;
  /* push items to top, center, bottom, or spread */

  /* Cross axis = horizontal (column) */
  align-items: stretch;
  /* stretch items to full width */
}

/* Center a single item perfectly */
.wrapper {
  display: flex;
  min-height: 100vh;
}
.centered {
  margin: auto; /* centers in both axes */
}
06

CSS Grid Layout

Grid Container

display: grid creates a 2D layout. grid-template-columns defines column sizes: fr units distribute free space proportionally. repeat(3, 1fr) creates 3 equal columns. repeat(auto-fit, minmax(250px, 1fr)) creates a responsive grid that auto-adjusts column count — this is the 'holy grail' of responsive grids. gap replaces margins for spacing.

css
.grid {
  display: grid;

  /* Define columns */
  grid-template-columns: 200px 1fr 200px;
  /* fixed, flexible, fixed */

  /* Or repeat */
  grid-template-columns: repeat(3, 1fr);
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));

  /* Define rows */
  grid-template-rows: auto 1fr auto;

  /* Gap (formerly grid-gap) */
  gap: 20px;
  row-gap: 10px;
  column-gap: 20px;
}

Grid Item Placement

Grid items can span multiple cells with grid-column: span N. You can also use line numbers: grid-column: 1 / 3 means start at line 1, end at line 3. Lines are numbered from 1 (left/top) to N+1 (right/bottom). grid-area assigns items to named areas. Grid allows precise 2D placement — items can span both rows and columns simultaneously.

css
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}

/* Span multiple columns */
.featured {
  grid-column: span 2;
}

/* Explicit placement */
.sidebar {
  grid-column: 1 / 3;  /* start at line 1, end at line 3 */
  grid-row: 1 / 4;
}

/* Named areas */
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }

Grid Template Areas

grid-template-areas creates a visual map of the layout using named strings. Each string represents a row; each name represents a column. Items are placed by setting grid-area to the matching name. This is the most readable way to create complex layouts. Use '.' for empty cells. The same name can span multiple cells. Change areas in media queries for responsive layouts.

css
.layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  gap: 10px;
  min-height: 100vh;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

Grid Alignment

Grid has 6 alignment properties. align-items/justify-items align items within their grid cells. align-content/justify-content align the entire grid track within the container (only visible if grid is smaller). align-self/justify-self override per-item. place-items: center is shorthand for align-items + justify-items. place-content: center combines both content alignments.

css
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);

  /* Align items within their cell (cross axis) */
  align-items: center;  /* start, center, end, stretch */

  /* Justify items within their cell (main axis) */
  justify-items: center;

  /* Align the entire grid (if smaller than container) */
  align-content: center;
  justify-content: center;
}

/* Override for individual item */
.item {
  align-self: end;
  justify-self: start;
}

Responsive Grid (Auto-fit)

repeat(auto-fit, minmax(300px, 1fr)) creates a grid that automatically adjusts column count based on available space — items are at least 300px and grow to fill. This eliminates media queries for card layouts. auto-fill keeps empty columns (items stay left), auto-fit collapses them (items stretch). Use auto-fit for most cases. Combine with max-width on the container for optimal results.

css
/* Auto-responsive grid - no media queries needed! */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
}

/* Fixed number of columns per breakpoint */
@media (min-width: 768px) {
  .cards {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1024px) {
  .cards {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Auto-fill vs auto-fit:
   auto-fit collapses empty tracks
   auto-fill keeps empty tracks */
07

Positioning

Position Types

static is the default (normal document flow). relative offsets from its normal position without affecting other elements (creates positioning context). absolute removes from flow and positions relative to nearest positioned ancestor. fixed positions relative to the viewport (stays on scroll). sticky toggles between relative and fixed — scrolls normally then sticks at the threshold.

css
/* Static (default) - normal flow */
.static { position: static; }

/* Relative - offset from 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 */
.fixed {
  position: fixed;
  bottom: 0;
  width: 100%;
}

/* Sticky - scrolls then sticks */
.sticky {
  position: sticky;
  top: 0;
}

Absolute Positioning

absolute positioning requires a positioned ancestor (relative, absolute, fixed, or sticky). Without one, it positions relative to the viewport. The top/right/bottom/left properties offset from the corresponding edge. To center an absolutely positioned element, use top:50%, left:50% with transform: translate(-50%,-50%). Absolute positioning removes elements from normal flow.

css
/* Parent must be positioned */
.container {
  position: relative;
  width: 400px;
  height: 300px;
}

/* Position relative to container */
.badge {
  position: absolute;
  top: 10px;
  right: 10px;
}

/* Center absolutely positioned element */
.modal {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

Sticky Positioning

position: sticky is a hybrid — elements scroll normally until they reach a threshold (e.g., top: 0), then stick. Great for sticky headers, sidebars, and table headers. The element sticks within its parent container (stops sticking when the parent scrolls past). Sticky doesn't work if any ancestor has overflow: hidden/auto/scroll. Always set a z-index to prevent overlap issues.

css
/* Sticky header */
.header {
  position: sticky;
  top: 0;
  background: white;
  z-index: 100;
  padding: 1rem;
}

/* Sticky sidebar */
.sidebar {
  position: sticky;
  top: 80px; /* offset for fixed header */
  height: calc(100vh - 80px);
  overflow-y: auto;
}

/* Note: sticky doesn't work if a parent has
   overflow: hidden or overflow: auto */

Z-index & Stacking Context

z-index controls stacking order of positioned elements (higher = on top). It only works on positioned elements (not static). A stacking context is created by positioned elements with z-index, opacity < 1, transform, or filter. Within a stacking context, child z-index values are relative to that context — a child can never appear above a sibling of its parent with higher z-index. This is a common source of confusion.

css
/* Higher z-index = closer to viewer */
.modal-overlay {
  position: fixed;
  z-index: 9999;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,0.5);
}

.modal {
  position: fixed;
  z-index: 10000; /* above overlay */
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
}

/* Stacking context: z-index only works
   within the same stacking context */
.parent { position: relative; z-index: 1; }
.child { position: absolute; z-index: 9999; }
/* child can't exceed parent's stacking level */

Float & Clear (Legacy)

float was the primary layout method before flexbox/grid. It removes elements from normal flow and pushes them left/right, with text wrapping around. clear prevents elements from appearing next to floats. The clearfix hack forces a container to enclose floated children. Today, use float only for its intended purpose: wrapping text around images. For layouts, use flexbox or grid.

css
/* Float text around an image */
img.float-left {
  float: left;
  margin: 0 1rem 1rem 0;
}

/* Clear floats */
.clear { clear: both; }
.clear-left { clear: left; }
.clear-right { clear: right; }

/* Clearfix hack for containers */
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}

/* Modern alternative: use flexbox or grid */
/* Float is mainly for text wrapping now */
08

Responsive Design

Media Queries

Media queries apply styles based on conditions. min-width (mobile-first) is preferred — base styles target mobile, then enhance for larger screens. max-width (desktop-first) is the reverse. Other conditions: prefers-color-scheme (dark/light), print, orientation (portrait/landscape), prefers-reduced-motion. Always set a viewport meta tag in HTML for media queries to work on mobile.

css
/* Mobile-first: base styles first, then min-width */

/* Base (mobile) */
.container { flex-direction: column; }

/* Tablet and up */
@media (min-width: 768px) {
  .container { flex-direction: row; }
}

/* Desktop and up */
@media (min-width: 1024px) {
  .container { max-width: 1200px; margin: 0 auto; }
}

/* Print styles */
@media print {
  .no-print { display: none; }
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  body { background: #1a1a1a; color: #eee; }
}

Fluid Typography

clamp(min, preferred, max) creates fluid typography that scales smoothly with viewport size — no media queries needed. The preferred value (often vw-based) scales between min and max bounds. min() picks the smaller value (great for responsive max-widths). This approach reduces the number of media queries needed and creates smoother scaling. Always set reasonable min/max for readability.

css
/* clamp() for fluid font sizes */
h1 {
  font-size: clamp(1.5rem, 5vw, 3rem);
  /* min: 1.5rem, preferred: 5vw, max: 3rem */
}

/* Fluid spacing */
.container {
  padding: clamp(1rem, 3vw, 3rem);
}

/* Fluid width */
.content {
  width: min(90%, 1200px);
  margin: 0 auto;
}

/* Viewport-based sizing */
.hero {
  height: 100vh;
  font-size: clamp(2rem, 8vw, 5rem);
}

Container Queries

Container queries (CSS Containment Module Level 3) let you style elements based on their parent container's size, not the viewport. container-type: inline-size declares a container. @container applies styles when the container meets the condition. This is more modular than media queries — components adapt to their container, not the screen. Great for reusable components in different layout contexts.

css
/* Define a container */
.card-container {
  container-type: inline-size;
  /* or: container: sidebar / inline-size; */
}

/* Query the container's size */
@container (min-width: 400px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}

@container (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

Responsive Images & Media

max-width: 100% + height: auto makes images responsive (scale down but never up). The padding-bottom hack creates responsive 16:9 video embeds. The modern aspect-ratio property is cleaner — set the ratio and the browser calculates height. Always set width and height attributes on images to prevent layout shift (CLS). Use object-fit: cover to crop images within fixed dimensions.

css
/* Responsive image */
img {
  max-width: 100%;
  height: auto;
}

/* Responsive video (16:9 aspect ratio) */
.video-wrapper {
  position: relative;
  padding-bottom: 56.25%; /* 9/16 */
  height: 0;
}
.video-wrapper iframe {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 100%;
}

/* Aspect ratio property */
.box {
  aspect-ratio: 16 / 9;
  width: 100%;
}

Mobile-First Strategy

Mobile-first means writing mobile styles as the base, then progressively enhancing for larger screens with min-width media queries. This ensures mobile users (often on slower connections) download less CSS. Common breakpoints: 768px (tablet), 1024px (desktop), 1440px (large). Use relative units (rem, %, vw) instead of fixed px for better scalability. Test on real devices, not just browser DevTools.

css
/* 1. Start with mobile base styles */
.nav { display: none; } /* hidden on mobile */
.menu-toggle { display: block; }

/* 2. Enhance for larger screens */
@media (min-width: 768px) {
  .nav { display: flex; }
  .menu-toggle { display: none; }
}

/* 3. Common breakpoints */
/* Mobile: < 768px */
/* Tablet: 768px - 1023px */
/* Desktop: 1024px - 1439px */
/* Large: >= 1440px */

/* 4. Use relative units */
body { font-size: 1rem; } /* not 16px */
09

Transitions & Animations

Transitions

transition creates smooth changes between property values. Syntax: transition: property duration timing-function delay. Timing functions: ease (default), linear, ease-in, ease-out, ease-in-out, cubic-bezier(). Only animatable properties transition. Avoid transition: all (performance). Transitions trigger on pseudo-class changes (:hover, :focus) or class changes via JavaScript.

css
.button {
  background: #007bff;
  transition: background 0.3s ease, transform 0.2s ease;
}

.button:hover {
  background: #0056b3;
  transform: translateY(-2px);
}

/* Transition all properties (use sparingly) */
.card {
  transition: all 0.3s ease;
}

/* Shorthand: property duration timing-function delay */
.box {
  transition: opacity 0.5s ease-in 0.2s;
}

Transform

transform modifies an element without affecting layout (unlike margin/position). translate moves it (translateX, translateY, or translate(x,y)). scale resizes (1 = 100%). rotate turns it (deg, rad, turn). skew distorts it. Multiple transforms chain in order. Transforms are GPU-accelerated — great for performance. Always pair with transition for smooth effects. transform-origin changes the pivot point.

css
.box {
  transition: transform 0.3s ease;
}

/* Translate (move) */
.box:hover { transform: translate(10px, 20px); }
.box:hover { transform: translateX(50%); }

/* Scale (resize) */
.box:hover { transform: scale(1.1); }

/* Rotate */
.box:hover { transform: rotate(45deg); }

/* Skew */
.box:hover { transform: skew(10deg, 5deg); }

/* Combine transforms */
.box:hover {
  transform: translate(10px, 0) scale(1.1) rotate(5deg);
}

Keyframe Animations

@keyframes defines animation steps. 0%/from is the start, 100%/to is the end. You can add intermediate steps (25%, 50%, 75%). The animation shorthand: name duration timing-function delay iteration-count direction fill-mode. infinite loops forever. direction: alternate reverses on even iterations. fill-mode: forwards keeps the final state. Animations run automatically, unlike transitions.

css
@keyframes bounce {
  0%   { transform: translateY(0); }
  50%  { transform: translateY(-20px); }
  100% { transform: translateY(0); }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.ball {
  animation: bounce 0.5s ease infinite;
}

.element {
  animation: fadeIn 1s ease forwards;
  /* name duration timing delay iteration direction fill-mode */
}

Animation Properties

Individual animation properties give fine control. animation-iteration-count can be a number or infinite. animation-direction: alternate plays forward then backward (great for ping-pong effects). animation-fill-mode: forwards keeps the end state after finishing. animation-play-state: paused freezes the animation — useful for hover-to-pause. Multiple animations comma-separate: animation: spin 1s, fade 2s;.

css
.spinner {
  animation-name: spin;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-delay: 0s;
  animation-iteration-count: infinite;
  animation-direction: normal; /* alternate, reverse */
  animation-fill-mode: none;   /* forwards, backwards, both */
  animation-play-state: running; /* paused */
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Pause on hover */
.spinner:hover {
  animation-play-state: paused;
}

Performance & Reduced Motion

For smooth 60fps animations, only animate transform and opacity — they're GPU-accelerated and don't trigger layout (reflow). Animating width, margin, top, etc. forces the browser to recalculate layout for every frame, causing jank. Always respect prefers-reduced-motion: reduce — some users experience motion sickness or have vestibular disorders. Provide instant transitions or disable animations for these users.

css
/* Only animate transform and opacity for performance */
.good {
  transition: transform 0.3s, opacity 0.3s;
}

/* Avoid animating these (cause reflow/repaint): */
/* margin, padding, width, height, top, left */

/* Respect user's motion preference */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}
10

Variables & Advanced Functions

Custom Properties (Variables)

Custom properties (CSS variables) store reusable values. Define them in :root for global access. Reference with var(--name). They cascade and can be overridden in any scope — perfect for theming (dark/light mode). Unlike preprocessor variables, they're dynamic (change at runtime) and can be manipulated with JavaScript. Always provide fallbacks: var(--primary, #007bff). This is the modern way to manage design tokens.

css
:root {
  /* Colors */
  --primary: #007bff;
  --primary-dark: #0056b3;
  --text: #333;
  --bg: #fff;

  /* Spacing scale */
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 2rem;

  /* Font sizes */
  --font-base: 16px;
  --font-lg: 1.5rem;
}

.button {
  background: var(--primary);
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-base);
  color: white;
}

/* Override in a scope */
.dark-theme {
  --bg: #1a1a1a;
  --text: #eee;
}

calc() & Math Functions

calc() performs math with any units, including mixing px, %, em, rem, vw. It's essential for responsive layouts. You can nest calc() but it's unnecessary — calc(100% - 2rem) / 3 works without inner calc. min() returns the smallest value, max() the largest, clamp() bounds a value between min and max. These functions make CSS dynamic without JavaScript or media queries.

css
/* Mixed unit calculations */
.sidebar {
  width: calc(100% - 250px);
}

/* With variables */
.box {
  padding: calc(var(--space-md) + 10px);
}

/* Nested calc */
.responsive {
  width: calc(calc(100% - 2rem) / 3);
}

/* min(), max(), clamp() */
.fluid {
  width: min(90%, 1200px);
  font-size: clamp(1rem, 2vw + 1rem, 2rem);
  height: max(200px, 50vh);
}

Object Fit & Position

object-fit controls how images/video fill their container (like background-size for <img>). cover fills the container and crops overflow (great for avatars/thumbnails). contain fits entirely without cropping (may leave empty space). fill stretches (distorts). object-position adjusts alignment (like background-position). This replaces the need for background-image hacks for responsive images.

css
/* Cover: fill container, crop overflow */
.avatar {
  width: 100px;
  height: 100px;
  object-fit: cover;
  border-radius: 50%;
}

/* Contain: fit entirely, may letterbox */
.preview {
  width: 200px;
  height: 200px;
  object-fit: contain;
  background: #eee;
}

/* Position the image within the frame */
.image {
  object-fit: cover;
  object-position: top center;
}

Background Properties

background is a powerful shorthand. background-size: cover fills the container (cropping); contain fits without cropping. background-attachment: fixed creates a parallax effect (image stays still during scroll). Multiple backgrounds layer with commas (first = top layer). Use linear-gradient as a background for overlays: background: linear-gradient(rgba(0,0,0,0.5), transparent), url('image.jpg').

css
.hero {
  /* Shorthand */
  background: url('hero.jpg') center/cover no-repeat fixed;

  /* Individual properties */
  background-image: url('hero.jpg');
  background-size: cover;     /* cover, contain, or px/% */
  background-position: center; /* top, bottom, left, right, or x y */
  background-repeat: no-repeat;
  background-attachment: fixed; /* scroll, fixed, local */

  /* Multiple backgrounds */
  background:
    url('overlay.png') no-repeat center,
    url('hero.jpg') center/cover;
}

CSS Nesting (Modern)

Modern CSS supports native nesting (like Sass/Less). Use & to reference the parent selector. Nesting improves readability and reduces repetition. Media queries can be nested directly inside rules. Browser support is good in modern browsers (2023+). The & is required for pseudo-classes (&:hover) and combinators (& > .child). Avoid deep nesting (3+ levels) as it increases specificity and reduces maintainability.

css
/* Native CSS nesting (no preprocessor needed) */
.card {
  background: white;
  border-radius: 8px;

  & .title {
    font-size: 1.5rem;
    font-weight: bold;
  }

  & .body {
    padding: 1rem;

    & p {
      line-height: 1.6;
    }
  }

  &:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  }

  @media (max-width: 768px) {
    padding: 0.5rem;
  }
}
11

CSS Custom Properties (Variables) Deep Dive

Defining & Using Variables

CSS custom properties (variables) are defined with --name and used with var(). Define global variables in :root for theme values. Unlike Sass variables, CSS variables are live — changing them updates all uses instantly. They are scoped to the element they're defined on and inherited by descendants. var() accepts a fallback value as the second argument, and fallbacks can be chained. This makes them far more powerful than preprocessor variables for dynamic theming.

css
:root {
  --primary: #3498db;
  --spacing: 16px;
  --radius: 8px;
  --max-width: 1200px;
}

.button {
  background: var(--primary);
  padding: var(--spacing);
  border-radius: var(--radius);
  max-width: var(--max-width);
}

/* Fallback values */
color: var(--text-color, #333);
/* Chained fallbacks */
color: var(--text, var(--default, black));

Dynamic Theming with JavaScript

CSS variables enable runtime theming that preprocessors can't do. Set data-theme on <html> and override variables per theme. JavaScript can read (getComputedStyle) and set (style.setProperty) variables at runtime, enabling dynamic color pickers, user preferences, and live previews. This is the standard approach for dark mode, brand customization, and user-selectable themes. Variables cascade and inherit, so overriding in a child element only affects that subtree.

css
/* Define theme variables */
:root {
  --bg: #ffffff;
  --text: #333333;
}

[data-theme="dark"] {
  --bg: #1a1a1a;
  --text: #e0e0e0;
}

body {
  background: var(--bg);
  color: var(--text);
}

/* Toggle theme with JS */
<script>
document.documentElement.setAttribute(
  "data-theme",
  isDark ? "dark" : "light"
);

/* Or set a single variable directly */
document.documentElement.style
  .setProperty("--primary", "#e74c3c");
</script>

Scoped Variables & Inheritance

CSS variables inherit like other properties. A variable defined on :root is available everywhere; one defined on .card only affects .card and its descendants. This scoping enables component-level customization. You can override variables in media queries to create responsive variable values — the variable itself can't be used in @media conditions, but you can change its value inside a media query block. This pattern is powerful for responsive design without repeating property declarations.

css
:root {
  --padding: 20px;
}

.card {
  --padding: 12px; /* overrides only for .card subtree */
  padding: var(--padding);
}

.card .inner {
  padding: var(--padding); /* inherits 12px from .card */
}

/* Variables in media queries don't work, but... */
.sidebar {
  --width: 250px;
  width: var(--width);
}
@media (max-width: 768px) {
  .sidebar { --width: 100%; }
}

Variables with calc()

Combining CSS variables with calc() creates powerful design systems. Define a base size and scale factor, then compute derived sizes. This enables consistent typographic scales and spacing systems. calc() works with mixed units (px, %, em, vw) and variables. You can even use variables without fallbacks (var(--nav-h, 60px) provides a default). This approach is the foundation of modern design systems — change one variable and the entire scale adjusts proportionally.

css
:root {
  --base-size: 16px;
  --scale: 1.25;
}

h1 { font-size: calc(var(--base-size) * var(--scale) * 2); }
h2 { font-size: calc(var(--base-size) * var(--scale) * 1.5); }
p  { font-size: var(--base-size); }

/* Spacing scale */
:root {
  --space: 8px;
}
.margin { margin: calc(var(--space) * 2); }
.padding { padding: calc(var(--space) * 3); }

/* Mixed units */
.header { height: calc(var(--nav-h, 60px) + var(--content-h)); }

Registering Custom Properties (@property)

@property registers custom properties with a type (syntax), initial value, and inheritance flag. This enables CSS variables to be animated and transitioned — without @property, variables are treated as strings and can't be interpolated. syntax accepts types like <angle>, <color>, <length>, <number>, <percentage>, or '*'. This unlocks smooth animations of gradients, transforms, and colors driven by variables. Browser support is good in Chromium and Safari, with Firefox adding support. A major advancement for CSS animation capabilities.

css
/* Type-safe custom properties */
@property --angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

@property --color {
  syntax: "<color>";
  initial-value: #3498db;
  inherits: false;
}

/* Now animatable! */
@keyframes spin {
  to { --angle: 360deg; }
}

.loader {
  --angle: 0deg;
  animation: spin 1s linear infinite;
  background: conic-gradient(
    from var(--angle),
    var(--color), transparent
  );
}
12

3D Transforms

3D Transform Functions

3D transforms add depth to elements. perspective() as a transform function applies to a single element; the perspective property on a parent applies to all children uniformly. rotateX/rotateY/rotateZ rotate around axes; translateZ moves along the Z-axis (toward/away from viewer). Lower perspective values create more dramatic 3D effects (closer viewpoint). Always set perspective on the parent container for consistent 3D space across multiple transformed children. Combine multiple transforms for complex 3D positioning.

css
.card {
  transform: perspective(1000px) rotateY(45deg) rotateX(15deg);
}

/* Using the perspective property instead */
.container {
  perspective: 1000px;
}
.container .card {
  transform: rotateY(45deg) rotateX(15deg);
}

/* translateZ moves toward/away from viewer */
.layer {
  transform: translateZ(100px);
}

/* scale3d for 3D scaling */
.box {
  transform: scale3d(1.5, 1.5, 1.5);
}

transform-style & backface-visibility

transform-style: preserve-3d maintains the 3D space for child elements (flat is the default, which flattens children). backface-visibility: hidden hides the back side of an element when rotated — essential for flip card effects. The classic flip card uses two absolutely-positioned faces: front faces forward, back is pre-rotated 180deg. On hover, the parent rotates 180deg, swapping which face is visible. This is one of the most popular 3D CSS patterns.

css
.container {
  perspective: 1000px;
}

.flip-card {
  transform-style: preserve-3d;
  transition: transform 0.6s;
  position: relative;
}

.flip-card:hover {
  transform: rotateY(180deg);
}

.flip-card .front,
.flip-card .back {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}

.flip-card .back {
  transform: rotateY(180deg);
}

3D Cube

A 3D cube is built with 6 faces, each positioned with rotate + translateZ. translateZ(100px) pushes the face half the cube width outward. Each face is pre-rotated to face its direction before being pushed out. transform-style: preserve-3d on the cube container is essential — without it, faces flatten. The animation rotates the cube on both X and Y axes. This demonstrates the full power of CSS 3D transforms. Adjust translateZ to match half your cube size for proper geometry.

css
.scene {
  perspective: 800px;
  width: 200px; height: 200px;
}
.cube {
  position: relative;
  width: 100%; height: 100%;
  transform-style: preserve-3d;
  animation: rotate 8s infinite linear;
}
@keyframes rotate {
  from { transform: rotateX(0) rotateY(0); }
  to   { transform: rotateX(360deg) rotateY(360deg); }
}
.face {
  position: absolute;
  width: 200px; height: 200px;
  opacity: 0.85;
}
.front  { transform: translateZ(100px); background: red; }
.back   { transform: rotateY(180deg) translateZ(100px); background: blue; }
.right  { transform: rotateY(90deg) translateZ(100px); background: green; }
.left   { transform: rotateY(-90deg) translateZ(100px); background: yellow; }
.top    { transform: rotateX(90deg) translateZ(100px); background: purple; }
.bottom { transform: rotateX(-90deg) translateZ(100px); background: orange; }

3D Card Tilt Effect

The card tilt effect tracks mouse position and rotates the card in 3D based on cursor offset from center. The math divides the offset by a factor (10) to limit rotation angle. On mouseleave, the card resets to flat. The transition creates smooth movement. Adding a glare/shine overlay with a radial gradient that follows the mouse enhances the effect. This is a popular interactive UI pattern for product cards and hero sections. The perspective in the transform string ensures 3D rendering even without a parent perspective property.

css
.tilt-card {
  transition: transform 0.1s ease-out;
  transform-style: preserve-3d;
}

/* JavaScript-driven tilt */
<script>
const card = document.querySelector(".tilt-card");
card.addEventListener("mousemove", (e) => {
  const rect = card.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  const centerX = rect.width / 2;
  const centerY = rect.height / 2;
  const rotateX = (y - centerY) / 10;
  const rotateY = (centerX - x) / 10;
  card.style.transform =
    `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
});
card.addEventListener("mouseleave", () => {
  card.style.transform = "perspective(1000px) rotateX(0) rotateY(0)";
});
</script>

perspective-origin & 3D Spacing

perspective-origin sets the vanishing point position (like moving your head). Default is 50% 50% (center). Moving it creates dynamic viewing angles. For parallax effects, elements at different translateZ values move at different rates when the container scrolls or rotates. Elements further back (negative translateZ) need scale() to compensate for perspective shrinking. This technique creates depth in UI without JavaScript — pure CSS parallax. Combined with scroll-driven animations, it creates immersive scrolling experiences.

css
.stage {
  perspective: 1200px;
  perspective-origin: 50% 30%; /* x y position of vanishing point */
  transform-style: preserve-3d;
}

.layer-1 { transform: translateZ(0); }
.layer-2 { transform: translateZ(50px); }
.layer-3 { transform: translateZ(100px); }

/* Parallax-like depth */
.depth-bg    { transform: translateZ(-200px) scale(1.2); }
.depth-mid   { transform: translateZ(-100px) scale(1.1); }
.depth-fg    { transform: translateZ(0); }
13

CSS Filters & Backdrop Filters

Filter Functions

CSS filters apply visual effects to elements. grayscale, sepia, and invert change colors; blur softens; brightness/contrast/saturate adjust intensity; hue-rotate shifts colors. Multiple filters chain left-to-right. drop-shadow is superior to box-shadow for PNG/SVG images because it follows the alpha shape (transparent areas don't get shadowed). Filters are GPU-accelerated and perform well. Common use: grayscale on hover for image galleries, vintage photo effects, and accessibility (high contrast modes).

css
img.grayscale { filter: grayscale(100%); }
img.blur { filter: blur(5px); }
img.bright { filter: brightness(1.5); }
img.contrast { filter: contrast(200%); }
img.sepia { filter: sepia(80%); }
img.saturate { filter: saturate(2); }
img.hue { filter: hue-rotate(90deg); }
img.invert { filter: invert(100%); }

/* Combine multiple filters */
img.vintage {
  filter: sepia(50%) contrast(110%) brightness(90%) saturate(120%);
}

/* Drop shadow (follows alpha shape) */
img.png-shadow {
  filter: drop-shadow(2px 4px 6px rgba(0,0,0,0.4));
}

backdrop-filter (Glassmorphism)

backdrop-filter applies filters to the area behind an element (not the element itself), creating the glassmorphism effect. The element needs a semi-transparent background for the effect to be visible. blur is the most common backdrop filter for frosted glass. Always include -webkit- prefix for Safari support. Combine with semi-transparent borders and subtle box-shadows for a polished glass look. Performance can be an issue with large blurred areas — use sparingly. This is a defining trend in modern UI design (iOS, macOS, Windows Acrylic).

css
.glass {
  background: rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(10px) saturate(180%);
  -webkit-backdrop-filter: blur(10px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 16px;
  padding: 24px;
}

.glass-dark {
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.1);
}

SVG Filters (url reference)

CSS filter: url(#id) references SVG filters for effects beyond built-in functions. The 'gooey' filter creates blob-like merging of elements (popular for loading animations and organic UI). feGaussianBlur + feColorMatrix with a high alpha multiplier creates the gooey effect by sharpening blurred edges. feTurbulence + feDisplacementMap creates wavy/distorted effects. SVG filters are powerful but can be performance-intensive. They enable effects impossible with CSS alone: liquid morphing, displacement, lighting, and custom compositing.

css
<!-- Define SVG filter -->
<svg style="display:none">
  <filter id="gooey">
    <feGaussianBlur in="SourceGraphic" stdDeviation="10" />
    <feColorMatrix values="
      1 0 0 0 0
      0 1 0 0 0
      0 0 1 0 0
      0 0 0 20 -10" />
  </filter>
</svg>

<!-- Apply via CSS -->
.gooey-container {
  filter: url(#gooey);
}

<!-- Displacement map for wavy text -->
<filter id="wavy">
  <feTurbulence baseFrequency="0.02" numOctaves="3" />
  <feDisplacementMap in="SourceGraphic" scale="10" />
</filter>

Filter Animations

Filters can be animated and transitioned smoothly. The pulse-glow effect combines brightness and drop-shadow for a neon glow. Grayscale-to-color on hover is a classic gallery interaction. Filter transitions are GPU-accelerated and perform better than animating box-shadow or background-color. However, animating blur or complex filters can be expensive — test on mobile. The scroll-driven hue-rotate is a fun effect but can cause performance issues on long pages; use will-change: filter to hint the browser for optimization.

css
@keyframes pulse-glow {
  0%, 100% {
    filter: brightness(1) drop-shadow(0 0 5px #3498db);
  }
  50% {
    filter: brightness(1.3) drop-shadow(0 0 20px #3498db);
  }
}

.glow-button {
  animation: pulse-glow 2s ease-in-out infinite;
}

/* Hover filter transition */
.gallery img {
  filter: grayscale(100%);
  transition: filter 0.3s ease;
}
.gallery img:hover {
  filter: grayscale(0%);
}

/* Color shift on scroll (with JS) */
window.addEventListener("scroll", () => {
  const hue = window.scrollY * 0.5;
  document.body.style.filter = `hue-rotate(${hue}deg)`;
});

mix-blend-mode & background-blend-mode

mix-blend-mode determines how an element's pixels blend with the content behind it. multiply darkens (good for shadows and watermarks); screen lightens (good for glows); difference creates psychedelic effects; overlay combines multiply and screen for contrast. background-blend-mode blends multiple background layers (images and gradients) on the same element. Blend modes are essential for creative compositing, duotone effects, and text-over-image overlays. isolation: isolate creates a new blending context to contain blend effects.

css
/* mix-blend-mode: how element blends with backdrop */
.overlay {
  mix-blend-mode: multiply; /* darken */
}
.overlay-screen {
  mix-blend-mode: screen; /* lighten */
}
.overlay-difference {
  mix-blend-mode: difference; /* invert */
}

/* Common blend modes: multiply, screen, overlay,
   darken, lighten, color-dodge, color-burn,
   hard-light, soft-light, difference, exclusion,
   hue, saturation, color, luminosity */

/* background-blend-mode: blend background layers */
.gradient-texture {
  background-image: url(texture.png),
                    linear-gradient(45deg, red, blue);
  background-blend-mode: multiply;
}
14

Container Queries

Container Query Basics

Container queries let components respond to their container's size rather than the viewport. container-type: inline-size makes the element a query container based on its inline (width) dimension. The @container rule applies styles when the container matches the condition. This is revolutionary for component-based design — a card component adapts whether it's in a sidebar or full-width main area, regardless of screen size. This solves the fundamental limitation of media queries, which only respond to viewport size.

css
/* Define a container */
.card-container {
  container-type: inline-size;
  /* or: container-type: size; (both dimensions) */
}

/* Query the container's size */
@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 2fr;
  }
}

@container (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

Named Containers

Named containers let you target specific containers when multiple are nested. container-name gives the container a label; @container name (condition) queries only that container. This prevents conflicts when components are nested with different container contexts. The container shorthand combines name and type: container: panel / inline-size. Named containers are essential for complex layouts where multiple independent container queries coexist. Without names, @container queries the nearest ancestor container.

css
/* Name a container for specific queries */
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

.main-content {
  container-type: inline-size;
  container-name: main;
}

/* Query specific container by name */
@container sidebar (min-width: 300px) {
  .widget { display: grid; grid-template-columns: 1fr 1fr; }
}

@container main (min-width: 600px) {
  .article { columns: 2; }
}

/* Shorthand: container: name / type */
.panel {
  container: panel / inline-size;
}

Container Query Units

Container query units (cqw, cqh, cqmin, cqmax) are like viewport units (vw, vh) but relative to the query container instead of the viewport. 1cqw = 1% of container width. This enables truly component-responsive typography and spacing — text scales with the component's width, not the screen. Combined with clamp(), you get fluid typography that adapts to container size with min/max bounds. This is ideal for design systems where components must work in various layout contexts. Note: cqh/cqmin/cqmax require container-type: size (both dimensions).

css
.card {
  container-type: inline-size;
}

.card-title {
  /* cqw: 1% of container width */
  font-size: 5cqw;

  /* cqh: 1% of container height (needs size type) */
  padding: 2cqh;

  /* cqmin: 1% of smaller container dimension */
  margin: 2cqmin;

  /* cqmax: 1% of larger container dimension */
  border-radius: 1cqmax;
}

/* Fluid typography that responds to container */
.headline {
  font-size: clamp(1rem, 8cqw, 3rem);
}

Container Style Queries

Container style queries (experimental, Chromium-only as of 2025) let you query custom property values on a container, not just dimensions. This enables style-based conditional rendering: a component can change appearance based on a --theme variable set on its container. container-type: style (or no container-type needed for style queries) enables this. This is powerful for theming — set a variable on a parent and all children adapt. Full browser support is still pending; use with progressive enhancement or @supports checks.

css
/* Query custom property values on container */
.card-wrapper {
  container-type: style;
  container-name: card;
}

@container card style(--theme: dark) {
  .card {
    background: #1a1a1a;
    color: #fff;
  }
}

@container card style(--theme: light) {
  .card {
    background: #fff;
    color: #333;
  }
}

/* Set the theme variable */
<div class="card-wrapper" style="--theme: dark">
  <div class="card">...</div>
</div>

Responsive Component Pattern

This pattern creates a truly reusable component that adapts to its placement. The product card shows a vertical layout in narrow containers, horizontal in medium, and a 3-column grid in wide containers. The same component works in a sidebar (narrow), grid (medium), or hero (wide) without any prop-based conditional logic. This is the killer use case for container queries — components that are genuinely context-independent. Combined with container query units for typography, you get fully self-adapting components.

css
/* Component that adapts to any container */
.product-card {
  container-type: inline-size;
  container-name: product;
}

.product-card .layout {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Medium container: horizontal layout */
@container product (min-width: 350px) {
  .product-card .layout {
    flex-direction: row;
  }
  .product-card .image {
    width: 40%;
  }
}

/* Large container: full feature layout */
@container product (min-width: 600px) {
  .product-card .layout {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
  }
  .product-card .actions {
    display: flex;
    flex-direction: column;
  }
}
15

Scroll Snap & Scroll-Driven Animations

Scroll Snap Basics

Scroll snap creates magnetic scrolling that aligns elements to snap points. scroll-snap-type: x/y sets the axis; mandatory forces snapping (always lands on a point); proximity snaps only when close. scroll-snap-align: start/center/end defines where the child snaps within the container. Full-page scrolling sections (like presentation slides) use y mandatory with 100vh sections. Horizontal carousels use x mandatory. Always test on touch devices — mandatory snapping can feel restrictive if content is taller than viewport. Use proximity for more forgiving behavior.

css
/* Container with scroll snapping */
.gallery {
  scroll-snap-type: x mandatory;
  overflow-x: auto;
  display: flex;
  gap: 1rem;
}

/* Children snap to positions */
.gallery section {
  scroll-snap-align: start;
  flex: 0 0 100%;
  height: 300px;
}

/* Vertical snapping */
.full-page {
  scroll-snap-type: y mandatory;
  height: 100vh;
  overflow-y: scroll;
}
.full-page section {
  scroll-snap-align: start;
  height: 100vh;
}

Scroll Snap with Padding

scroll-padding offsets snap points from the container edge — useful when you have fixed headers or want margin around snapped items. scroll-snap-stop: always prevents fast scrolling from skipping multiple items (forces one-at-a-time snapping). scroll-snap-align: center snaps items to the center of the container, creating a cover-flow effect. These properties give fine control over the snapping behavior. For image carousels, combine with scroll-behavior: smooth for animated transitions between snaps.

css
.carousel {
  scroll-snap-type: x mandatory;
  scroll-padding: 0 20px; /* offset snap points */
  overflow-x: auto;
  display: flex;
  gap: 1rem;
  padding: 0 20px;
}

.carousel .item {
  scroll-snap-align: start;
  scroll-snap-stop: always; /* don't skip items */
  flex: 0 0 80%;
}

/* Center-aligned snapping */
.carousel-center .item {
  scroll-snap-align: center;
  flex: 0 0 60%;
}

Scroll-Driven Animations (animation-timeline)

Scroll-driven animations (CSS only, no JS!) link animations to scroll position. animation-timeline: scroll() ties the animation to the page scroll progress (0% at top, 100% at bottom). animation-timeline: view() ties it to the element entering/leaving the viewport. animation-range defines when the animation starts/ends relative to scroll (entry, exit, cover, contain). This enables scroll progress bars, reveal-on-scroll, and parallax effects without JavaScript or scroll event listeners. Supported in Chrome 115+, progressive enhancement recommended for other browsers.

css
/* Animate as user scrolls */
@keyframes fade-in {
  from { opacity: 0; transform: translateY(50px); }
  to   { opacity: 1; transform: translateY(0); }
}

.reveal {
  animation: fade-in linear;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
}

/* Progress bar based on scroll position */
.progress-bar {
  position: fixed;
  top: 0; left: 0;
  height: 4px;
  background: #3498db;
  width: 100%;
  transform-origin: left;
  animation: grow linear;
  animation-timeline: scroll();
}

@keyframes grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

View Timeline Ranges

View timeline ranges control exactly when the animation occurs. 'entry' is when the element enters the viewport; 'exit' when it leaves; 'cover' spans from entry start to exit end; 'contain' is when the element is fully visible. Percentages fine-tune within each range. animation-range: entry 10% entry 90% means the animation runs from 10% into entry to 90% into entry. For sticky headers, scroll() with a pixel range (0 200px) animates based on absolute scroll distance. This replaces complex JavaScript scroll handlers with declarative CSS.

css
/* Element animates as it enters viewport */
@keyframes slide-up {
  from { opacity: 0; transform: translateY(100px); }
  to   { opacity: 1; transform: translateY(0); }
}

.section {
  animation: slide-up linear both;
  animation-timeline: view();
  /* Ranges: entry, exit, cover, contain */
  animation-range: entry 10% entry 90%;
}

/* Sticky header that shrinks on scroll */
.header {
  position: sticky;
  top: 0;
  animation: shrink linear both;
  animation-timeline: scroll();
  animation-range: 0 200px;
}
@keyframes shrink {
  to { padding: 0.5rem 1rem; font-size: 0.9em; }
}

scroll-behavior & smooth scroll

scroll-behavior: smooth on html (or any scroll container) makes anchor link navigation and scrollIntoView() animate smoothly instead of jumping. This is a one-line replacement for JavaScript smooth-scroll libraries. Always respect prefers-reduced-motion — some users experience motion sickness, so disable smooth scrolling for them. The scroll-snap-type: proximity (vs mandatory) pairs well with smooth scrolling for a natural feel. scrollIntoView with block: 'start'/'center'/'end' controls vertical alignment of the target.

css
/* Smooth scrolling for anchor links */
html {
  scroll-behavior: smooth;
}

/* With scroll snap */
html {
  scroll-behavior: smooth;
  scroll-snap-type: y proximity;
}

/* Target specific elements */
.container {
  scroll-behavior: smooth;
  overflow-y: auto;
}

/* JavaScript: scroll to element */
<script>
document.querySelector(".section-3")
  .scrollIntoView({ behavior: "smooth", block: "start" });

/* Scroll to top */
window.scrollTo({ top: 0, behavior: "smooth" });
</script>

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}
16

CSS Counters

Basic Counter

CSS counters auto-number elements without JavaScript. counter-reset initializes a counter (on the parent or any ancestor). counter-increment increases it (usually on the element being numbered). counter(name) displays the current value in content. The counter automatically handles numbering across the document. This is ideal for numbering headings, list items, figures, or any sequential content. Counters are scoped to the element where they're reset and its descendants.

css
/* Initialize counter on parent */
body {
  counter-reset: section;
}

/* Increment and display */
h2::before {
  counter-increment: section;
  content: "Section " counter(section) ": ";
  color: #3498db;
  font-weight: bold;
}

/* Result: Section 1: Introduction, Section 2: Methods... */

Nested Counters

Nested counters create hierarchical numbering (1.1, 1.2, 2.1, etc.). Reset the sub-counter on the parent heading (counter-reset: section on h2). Each h2 starts a new chapter and resets the section counter. Each h3 increments the section counter within the current chapter. The content property combines multiple counter() calls with separators. This mirrors how books and technical documents are numbered. The counters cascade naturally based on DOM structure and reset points.

css
body {
  counter-reset: chapter;
}

h2 {
  counter-reset: section;
  counter-increment: chapter;
}
h2::before {
  content: "Chapter " counter(chapter) ". ";
}

h3 {
  counter-increment: section;
}
h3::before {
  content: counter(chapter) "." counter(section) " ";
}

/* Result: Chapter 1.  / 1.1 / 1.2 / Chapter 2. / 2.1 ... */

Counter Styles

counter() accepts a style argument: decimal (default), decimal-leading-zero (01, 02), lower/upper-alpha (a/b or A/B), lower/upper-roman (i/ii or I/II). @counter-style defines custom counter styles with cyclic symbols, additive systems, or symbolic notation. The system: cyclic repeats symbols; system: additive creates Roman-numeral-like systems. Custom counter styles are powerful for internationalized lists, custom bullet points, or decorative numbering. Browser support for @counter-style is good in modern browsers.

css
/* Different number styles */
ol {
  counter-reset: item;
  list-style: none;
}
li::before {
  counter-increment: item;
  /* decimal, decimal-leading-zero, lower-alpha,
     upper-alpha, lower-roman, upper-roman */
  content: counter(item, upper-roman) ". ";
}

/* Leading zeros: 01, 02, 03... */
li::before {
  content: counter(item, decimal-leading-zero) ". ";
}

/* Custom @counter-style */
@counter-style thumbs {
  system: cyclic;
  symbols: "👍" "👎";
  suffix: " ";
}
.thumbs-list { list-style-type: thumbs; }

counters() Function (Nested)

The counters() function (plural) returns the full counter path as a string, with levels separated by the specified delimiter. This automatically handles nested counter scopes — each nested ol creates a new counter scope. counters(nested, '.') produces 1, 1.1, 1.1.1 for deeply nested lists. This is different from counter() which only shows the current level. Use counters() for outline-style numbering of nested lists, table of contents, or hierarchical menus. The separator string can be any character (. , - > etc.).

css
/* counters() creates full path string */
ol {
  counter-reset: nested;
  list-style: none;
}
li::before {
  counter-increment: nested;
  /* counters() returns all levels separated by a string */
  content: counters(nested, ".") " - ";
  font-weight: bold;
}

/* For nested <ol> elements:
   1 - Top level
   1.1 - Nested
   1.1.1 - Deeper
   2 - Top level again
*/

Counter for Figures & Tables

Counters are perfect for auto-numbering figures, tables, and equations in documentation. Reset per chapter (counter-reset on h2) so numbering restarts each chapter. The ::before pseudo-element on figcaption/caption prepends the number. This ensures consistent, automatic numbering that updates when content is reordered. For cross-references ('see Figure 3'), CSS counters can't help directly — you'd need JS or HTML anchors. But for display numbering, this is a clean, maintenance-free solution.

css
body {
  counter-reset: figure table;
}

figure figcaption::before {
  counter-increment: figure;
  content: "Figure " counter(figure) ": ";
  font-weight: bold;
  color: #2c3e50;
}

table caption::before {
  counter-increment: table;
  content: "Table " counter(table) ": ";
  font-weight: bold;
  color: #2c3e50;
}

/* Reset figure counter per chapter */
h2 {
  counter-reset: figure;
}
18

Dark Mode & Color Schemes

prefers-color-scheme

prefers-color-scheme: dark detects the user's OS/browser dark mode preference. Define theme variables in :root for light mode, then override them in the dark media query. Using CSS variables means you only change the variable values — all components automatically update. This is the standard approach for dark mode. The media query also supports 'light' and 'no-preference'. Test by toggling your OS dark mode or using Chrome DevTools' Rendering tab (Emulate CSS prefers-color-scheme).

css
/* Light mode (default) */
:root {
  --bg: #ffffff;
  --text: #1a1a1a;
  --surface: #f5f5f5;
  --border: #e0e0e0;
  --accent: #3498db;
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a1a;
    --text: #e0e0e0;
    --surface: #2a2a2a;
    --border: #404040;
    --accent: #5dade2;
  }
}

body {
  background: var(--bg);
  color: var(--text);
}

Manual Theme Toggle

For manual theme toggle, use a data-theme attribute on <html> that overrides the system preference. Store the user's choice in localStorage so it persists. On page load, check localStorage before rendering to avoid flash of wrong theme (FOUC). color-scheme: light dark tells the browser to render native UI elements (scrollbars, form controls) in the appropriate scheme. For best UX, default to system preference when no saved choice exists: check matchMedia('(prefers-color-scheme: dark)') as the fallback.

css
/* Default to system preference, allow override */
:root {
  color-scheme: light dark;
}

/* Manual dark mode via data attribute */
[data-theme="dark"] {
  --bg: #1a1a1a;
  --text: #e0e0e0;
}

[data-theme="light"] {
  --bg: #ffffff;
  --text: #1a1a1a;
}

/* Toggle button */
<button onclick="toggleTheme()">Toggle Theme</button>
<script>
function toggleTheme() {
  const current = document.documentElement.dataset.theme;
  const next = current === "dark" ? "light" : "dark";
  document.documentElement.dataset.theme = next;
  localStorage.setItem("theme", next);
}
// Load saved preference
const saved = localStorage.getItem("theme");
if (saved) document.documentElement.dataset.theme = saved;
</script>

color-scheme Property

The color-scheme property tells the browser which color schemes the page supports, affecting native UI elements: scrollbars, form controls (inputs, buttons, dropdowns), default background/canvas color, and the ::placeholder color. Without it, form controls may appear light even in dark mode (jarring mismatch). Set color-scheme: light dark on :root to let native elements adapt automatically. This is a one-line fix that dramatically improves dark mode polish. It's separate from your CSS variable theming — it only affects browser-native rendering.

css
:root {
  /* Tell browser this page supports both schemes */
  color-scheme: light dark;
}

/* Force light only */
.light-only {
  color-scheme: light;
}

/* Force dark only */
.dark-only {
  color-scheme: dark;
}

/* This affects native UI: scrollbars, form controls,
   default colors (canvas, text), and form elements */
input, textarea, select {
  /* Will use dark form controls in dark mode */
  color-scheme: dark;
}

Dark Mode Images & Media

Images need special handling in dark mode. White-background diagrams can be inverted with filter: invert(1) hue-rotate(180deg) — the hue-rotate prevents color distortion. Photos of people should never be inverted. Reduce brightness slightly for less eye strain. For logos and icons, use <picture> with media-query sources to serve dark-optimized versions. SVGs with currentColor automatically adapt. For background images, provide dark-mode alternatives via media queries. Always test images in both modes for readability.

css
@media (prefers-color-scheme: dark) {
  /* Invert images that are essentially white-background */
  img[src*="logo"],
  img[src*="diagram"] {
    filter: invert(1) hue-rotate(180deg);
  }

  /* Reduce brightness of photos */
  img.photo {
    filter: brightness(0.85);
  }

  /* Don't invert photos with people */
  img[src*="photo"]:not(.invertable) {
    filter: none;
  }
}

/* Use dark-mode-friendly SVG */
<picture>
  <source srcset="logo-dark.svg"
          media="(prefers-color-scheme: dark)">
  <img src="logo-light.svg" alt="Logo">
</picture>

Smooth Theme Transition

Adding color transitions to all elements creates a smooth theme switch animation. However, this can cause a flash of animated colors on initial page load. The solution: add a .no-transition class to <html> during theme switches and on initial load, then remove it after one frame. Always respect prefers-reduced-motion to disable transitions for users who request reduced motion. Be selective — transitioning all properties can impact performance. Only transition color-related properties (background-color, color, border-color, box-shadow).

css
/* Transition colors when theme changes */
* {
  transition: background-color 0.3s ease,
              color 0.3s ease,
              border-color 0.3s ease;
}

/* But not during initial load (prevent flash) */
.no-transition * {
  transition: none !important;
}

<script>
// Disable transitions during theme switch
function toggleTheme() {
  document.documentElement.classList.add("no-transition");
  document.documentElement.dataset.theme = newTheme;
  // Re-enable after switch
  requestAnimationFrame(() => {
    document.documentElement.classList.remove("no-transition");
  });
}
</script>

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}
19

CSS Functions Deep Dive

clamp(), min(), max()

clamp(min, preferred, max) is the holy grail of fluid typography and spacing — it scales with the preferred value but never exceeds the min/max bounds. min() picks the smaller value (great for max-width that's responsive). max() picks the larger value (great for minimum sizes). These eliminate many media queries by providing continuous fluid scaling. The preferred value in clamp typically uses viewport units (vw, vh) or container units (cqw). This is the modern approach to responsive design without breakpoints.

css
/* clamp(min, preferred, max) */
h1 {
  font-size: clamp(1.5rem, 5vw, 3rem);
  /* Never smaller than 1.5rem, never larger than 3rem,
     preferred is 5vw */
}

/* min(): use the smaller value */
.container {
  width: min(100% - 2rem, 1200px);
  /* Full width minus padding, but max 1200px */
}

/* max(): use the larger value */
.hero {
  font-size: max(2rem, 4vw);
  /* At least 2rem, grows with viewport */
}

/* Combining for responsive padding */
.card {
  padding: clamp(1rem, 3vw, 2rem);
}

calc() Advanced

calc() performs calculations with mixed units (px, %, em, vw, etc.). Addition and subtraction require spaces around the operator (calc(100% - 20px), not calc(100%-20px)). Multiplication and division only work with numbers, not lengths (calc(10px * 2) is valid; calc(10px * 10px) is not). calc() can be nested but parentheses also work. Combined with CSS variables, calc() enables dynamic, computed design systems. It's essential for responsive layouts that combine fixed and fluid dimensions.

css
/* Mixed units */
.sidebar {
  width: calc(100% - 250px); /* full width minus fixed sidebar */
  height: calc(100vh - 60px); /* viewport minus header */
}

/* Nested calc */
.element {
  margin: calc(calc(100% - 50px) / 2);
  /* Simplified: */
  margin: calc((100% - 50px) / 2);
}

/* With CSS variables */
:root { --gap: 20px; }
.grid {
  gap: calc(var(--gap) * 2);
}

/* Math operations: + - * / */
.padding {
  padding: calc(1rem + 2px);
  width: calc(50% - 10px);
}

attr() Function

attr() retrieves HTML attribute values, most commonly used in content for pseudo-elements. It's the basis of CSS-only tooltips (data-tooltip attribute → ::after content). attr() works reliably only in content property. Using attr() for other properties (width, color, etc.) with type casting (attr(data-size px)) is part of CSS Values Level 5 but has minimal browser support. For now, use CSS variables for dynamic values from HTML: set style='--size: 100px' and use var(--size).

css
/* Display attribute values */
a[href]::after {
  content: attr(href);
}

/* data attributes for tooltips */
.tooltip::after {
  content: attr(data-tooltip);
  display: none;
  position: absolute;
}
.tooltip:hover::after {
  display: block;
}

/* HTML: <div class="tooltip" data-tooltip="Help text">?</div> */

/* Note: attr() for non-content properties is experimental */
/* This doesn't work widely yet: */
/* .box { width: attr(data-width px); } */

var() with Fallbacks

var() accepts a fallback as its second argument, used when the variable is not defined. Fallbacks can chain: var(--a, var(--b, var(--c, default))). This enables progressive enhancement and multi-level theming. An empty variable value (--defined: ;) is still 'set', so the fallback won't trigger — this enables conditional CSS patterns. Fallbacks can include any valid CSS value, including calc() and other functions. Use fallbacks for optional theme variables that might not be defined in all contexts.

css
/* Single fallback */
color: var(--text-color, #333);

/* Chained fallbacks */
color: var(--text, var(--default-text, black));

/* Fallback in shorthand */
background: var(--bg, white) url(image.png);

/* Fallback with calc */
width: calc(100% - var(--sidebar, 250px));

/* Check if variable is set */
:root {
  --defined: ; /* empty space = valid value */
}
.conditional {
  --use-default: var(--defined, initial);
  /* If --defined is empty, --use-default = initial */
}

gradient Functions

CSS gradients create smooth transitions between colors without images. linear-gradient (direction + colors), radial-gradient (from center outward), and conic-gradient (rotating around a point) are the three types. Hard stops (same percentage for two colors) create sharp bands. repeating-linear-gradient creates patterns like stripes. Gradients can be layered with multiple backgrounds (comma-separated, first = top layer). Conic gradients enable pie charts and color wheels in pure CSS. Gradients are resolution-independent and perform better than image files.

css
/* Linear gradient */
.bg { background: linear-gradient(45deg, #3498db, #e74c3c); }
.bg2 { background: linear-gradient(to right, red, orange, yellow); }

/* Radial gradient */
.radial { background: radial-gradient(circle, white, blue); }
.radial2 { background: radial-gradient(circle at top left, #fff, #000); }

/* Conic gradient */
.pie { background: conic-gradient(red 0% 30%, blue 30% 70%, green 70% 100%); }

/* Repeating gradients */
.stripes { background: repeating-linear-gradient(45deg, #ccc 0 10px, #fff 10px 20px); }

/* Hard color stops (sharp edges) */
.hard { background: linear-gradient(to right, #000 50%, #fff 50%); }

/* Multiple backgrounds */
.multi {
  background:
    linear-gradient(rgba(0,0,0,0.5), transparent),
    url(photo.jpg);
}
20

CSS Variables

Defining & Using Variables

CSS custom properties (variables) are defined with --name and used with var(). Declare them on :root for global access. Unlike Sass variables, they are live — changing a variable via JavaScript instantly updates every element that uses it.

css
:root {
  --primary: #3498db;
  --spacing: 16px;
  --radius: 8px;
}

.button {
  background: var(--primary);
  padding: var(--spacing);
  border-radius: var(--radius);
}

Fallback Values

var() accepts a second argument as a fallback when the variable is undefined. Fallbacks can nest, letting you chain defaults. For browsers that do not support custom properties, declare a normal value first, then override with var().

css
.box {
  color: var(--brand, #333);
  background: var(--bg, var(--default-bg, white));
}

/* Provide fallback for older browsers */
.element {
  color: #333;
  color: var(--text-color, #333);
}

JavaScript Interaction

JavaScript reads variables via getComputedStyle().getPropertyValue() and sets them with setProperty(). This makes runtime theming trivial — flip a few variables to restyle the entire app.

css
// Read a variable
const color = getComputedStyle(document.documentElement)
  .getPropertyValue('--primary');

// Set a variable on an element
document.documentElement.style.setProperty('--primary', '#e74c3c');

// Toggle a theme
document.documentElement.setAttribute('data-theme', 'dark');

Scoping & Inheritance

Custom properties inherit, so a variable set on :root cascades to every element. Redefining it on .card overrides it for that subtree only. This scoping enables component-level theming without specificity wars.

css
:root { --size: 16px; }

.card {
  --size: 20px; /* Overrides only within .card */
  font-size: var(--size);
}

.card .text { font-size: var(--size); } /* 20px */
.other { font-size: var(--size); }       /* 16px */

Responsive Variables

Redefine variables inside media queries to make the entire layout respond to breakpoints without rewriting rules. Change one variable and every element using it adapts — far cleaner than overriding each property individually.

css
:root {
  --container-width: 1200px;
  --font-size: 18px;
}

@media (max-width: 768px) {
  :root {
    --container-width: 100%;
    --font-size: 16px;
  }
}

.container { max-width: var(--container-width); }
body { font-size: var(--font-size); }
21

Animations & Keyframes

@keyframes & Animation Shorthand

@keyframes defines the start (from/0%) and end (to/100%) states of an animation. The animation shorthand combines name, duration, timing-function, delay, iteration-count, direction, fill-mode, and play-state. forwards keeps the final state.

css
@keyframes slide-in {
  from { transform: translateX(-100%); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}

.element {
  animation: slide-in 0.5s ease-out forwards;
}

/* Shorthand: name duration timing delay count direction fill-mode */
.bounce {
  animation: bounce 1s cubic-bezier(0.68, -0.55, 0.27, 1.55) 0s infinite alternate;
}

Transitions

Transitions smoothly interpolate between property values when they change. Specify which properties transition and their duration/timing. transition: all is convenient but can hurt performance — prefer listing specific properties.

css
.button {
  background: blue;
  transition: background 0.3s ease, transform 0.2s ease-out;
}

.button:hover {
  background: darkblue;
  transform: scale(1.05);
}

/* Transition all changed properties */
.card { transition: all 0.3s ease; }

Timing Functions

Timing functions control the acceleration curve. ease-out starts fast and slows (good for entrances); ease-in starts slow and accelerates (good for exits). cubic-bezier lets you craft custom curves. steps() creates a discrete effect.

css
/* Built-in */
.ease-linear { transition: all 1s linear; }
.ease-out    { transition: all 1s ease-out; }

/* Custom cubic-bezier(x1, y1, x2, y2) */
.custom { transition: all 1s cubic-bezier(0.68, -0.55, 0.27, 1.55); }

/* Steps */
.steps { transition: all 1s steps(5, end); }

Animation Events

JavaScript can listen for animationstart, animationiteration (fires each loop), and animationend. Control playback with the animation-play-state property (paused/running). Combine with animationend to chain animations.

css
const el = document.querySelector('.animate');
el.addEventListener('animationstart', () => console.log('Started'));
el.addEventListener('animationiteration', () => console.log('Looped'));
el.addEventListener('animationend', () => console.log('Finished'));

// Pause and resume
el.style.animationPlayState = 'paused';
el.style.animationPlayState = 'running';

Performance: transform & opacity

Animate only transform and opacity for 60fps performance — they run on the GPU without triggering layout. Animating width, height, top, or margin forces layout recalculation every frame. Use will-change sparingly to hint the browser.

css
/* GOOD: GPU-accelerated, cheap */
.fade { transition: opacity 0.3s; }
.move { transition: transform 0.3s; }

/* BAD: triggers layout/paint, expensive */
.resize { transition: width 0.3s, height 0.3s; }

/* Hint the browser to optimize */
.card { will-change: transform; }
22

Transforms 3D

2D Transforms

2D transforms move, rotate, scale, and skew elements without affecting surrounding layout. Transforms are composited on the GPU, making them performant for animation. Order matters: rotate then translate moves along the rotated axis.

css
.box { transform: translate(50px, 20px); }
.box { transform: rotate(45deg); }
.box { transform: scale(1.5); }
.box { transform: skew(20deg, 10deg); }

/* Combined — order matters */
.combo { transform: translate(100px, 0) rotate(90deg) scale(1.2); }

3D Transforms & Perspective

3D transforms add rotateX, rotateY, rotateZ, translateZ, and scaleZ. perspective on a parent gives all children a shared vanishing point — lower values exaggerate the 3D effect. transform-style: preserve-3d keeps nested elements in 3D.

css
.scene { perspective: 1000px; }

.card {
  transform: rotateY(45deg);
  transform-style: preserve-3d;
}

/* Perspective on the element itself */
.self { transform: perspective(800px) rotateX(30deg); }

/* Transform origin */
.pivot { transform-origin: top left; transform: rotate(45deg); }

Flip Card Effect

The classic flip card uses preserve-3d on a container, with front and back faces absolutely positioned. backface-visibility: hidden hides the back of each face. The back face is pre-rotated 180deg so it shows when the container flips.

css
.card { perspective: 1000px; }
.inner {
  position: relative;
  transform-style: preserve-3d;
  transition: transform 0.6s;
}
.card:hover .inner { transform: rotateY(180deg); }
.front, .back {
  position: absolute;
  backface-visibility: hidden;
}
.back { transform: rotateY(180deg); }

Cube with 6 Faces

A 3D cube is built from six faces, each translated 50px (half the cube width) outward along its axis. preserve-3d keeps the faces positioned in 3D space. Animate the cube transform to spin it.

css
.cube {
  position: relative;
  width: 100px; height: 100px;
  transform-style: preserve-3d;
  transform: rotateX(-20deg) rotateY(30deg);
}
.face { position: absolute; width: 100px; height: 100px; }
.front  { transform: translateZ(50px); }
.back   { transform: rotateY(180deg) translateZ(50px); }
.right  { transform: rotateY(90deg) translateZ(50px); }
.left   { transform: rotateY(-90deg) translateZ(50px); }
.top    { transform: rotateX(90deg) translateZ(50px); }
.bottom { transform: rotateX(-90deg) translateZ(50px); }

Matrix & Performance

matrix() and matrix3d() express any transform as a single matrix — useful when computing transforms in JavaScript. translateZ(0) or will-change: transform force GPU acceleration, improving animation smoothness. Avoid overusing layers.

css
/* matrix(a, b, c, d, e, f) encodes translate, rotate, scale, skew */
.transformed { transform: matrix(1, 0, 0, 1, 100, 50); }

/* 3D matrix */
.cube { transform: matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1); }

/* Hardware acceleration hint */
.gpu { transform: translateZ(0); }
23

CSS Grid Advanced

Grid Template Areas

grid-template-areas names grid cells visually, making layout intent obvious. Each quoted row represents a grid row; identical names span cells. Empty cells use a dot (.). Areas must form rectangles — L-shapes are invalid.

css
.layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  gap: 16px;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main   { grid-area: main; }
.footer { grid-area: footer; }

minmax & auto-fit

repeat(auto-fit, minmax(200px, 1fr)) creates a responsive grid without media queries: columns are at least 200px and stretch to fill the row. auto-fit collapses empty tracks; auto-fill keeps them reserved.

css
.grid {
  display: grid;
  /* Columns auto-fit, each at least 200px, sharing leftover space */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
}

/* Fixed number that wraps */
.wrap {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}

Grid Alignment

Grid alignment works at two levels: the container (justify/align-items and justify/align-content) and the item (justify/align-self). justify-* controls the inline axis, align-* the block axis. place-items: center is shorthand for both.

css
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  justify-items: center;
  align-items: center;
  justify-content: space-between;
  align-content: center;
}

.item {
  justify-self: end;
  align-self: start;
}

Spanning & Line Placement

Place items by grid line number (1 / 3 means from line 1 to line 3) or by span (span 2). Negative lines count from the end (-1 is the last line). grid-column: 1 / -1 makes an item span the full width.

css
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
.header { grid-column: 1 / -1; }       /* full width */
.sidebar { grid-column: 1 / 3; }       /* columns 1-2 */
.main { grid-column: span 2; }          /* span 2 columns */
.tall { grid-row: 1 / span 2; }         /* span 2 rows */

Subgrid

subgrid lets a child grid inherit the parent track definitions, so columns or rows align perfectly across nested grids. Without subgrid, nested grids define their own tracks and can misalign. Supported in all modern browsers since 2023.

css
.card {
  display: grid;
  grid-template-rows: auto 1fr auto;
  /* Inherit column tracks from parent */
  grid-template-columns: subgrid;
  grid-column: span 3;
}

.parent {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
}
24

CSS Functions

calc()

calc() performs math on mixed units (px, %, vw, em, rem). Always surround + and - with spaces (calc(100%-20px) fails; calc(100% - 20px) works). Multiplication and division need one side to be a unitless number.

css
.sidebar {
  width: calc(100% - 250px);
  padding: calc(16px + 2vw);
  font-size: calc(14px + 0.5vw);
}

/* Nested calc */
.full { width: calc(calc(100% - 20px) / 2); }

/* With variables */
.box { margin: calc(var(--gap) * 2); }

clamp()

clamp() bounds a value between a minimum and maximum, with a preferred value in the middle. It is the cleanest way to build fluid typography and spacing — the value scales but never goes below or above the bounds.

css
/* clamp(MIN, PREFERRED, MAX) */
h1 {
  font-size: clamp(1.5rem, 4vw, 3rem);
}

.sidebar {
  width: clamp(200px, 25vw, 400px);
}

.padding {
  padding: clamp(1rem, 2vw + 1rem, 2rem);
}

min() & max()

min() returns the smallest argument; max() returns the largest. Unlike clamp (which takes exactly three values), min/max accept any number of arguments. Use min() to cap a value and max() to floor a value.

css
/* Picks the smallest/largest of comma-separated values */
.box {
  width: min(100% - 32px, 800px);
  font-size: max(16px, 2vw);
}

/* With multiple units */
.responsive {
  padding: min(5vw, 2rem);
  margin: max(1rem, 3vw);
}

var() with Fallbacks

var() second argument is used only when the custom property is undefined. For invalid values, the property falls back to its inherited or initial value. Nest var() calls to chain fallbacks.

css
.button {
  background: var(--btn-bg, #007bff);
  color: var(--text, var(--default-text, black));
  width: var(--width, 100%);
}

color-mix() & color functions

color-mix() blends two colors in a specified color space (srgb, oklch, lab). It is the modern way to derive tints and shades from a brand color. Relative color syntax (from keyword) lets you adjust individual channels.

css
/* Mix two colors */
.muted { color: color-mix(in srgb, red 30%, blue); }

/* Adjust relative lightness */
.lighter { background: color-mix(in oklch, var(--brand) 70%, white); }
.darker  { background: color-mix(in oklch, var(--brand) 70%, black); }

/* Relative color syntax (newest) */
.tint { background: oklch(from var(--brand) calc(l + 0.1) c h); }
25

Scroll-Driven Animations

animation-timeline Basics

animation-timeline: scroll(root) ties an animation to the page scroll position — as the user scrolls, the animation progresses. No JavaScript needed. This is far smoother than scroll event listeners.

css
@keyframes grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

.progress-bar {
  animation: grow linear;
  animation-timeline: scroll(root);
  transform-origin: left;
}

view() Timeline

view() creates a timeline tied to an element entering and leaving the viewport. animation-range controls which portion of the entry triggers the animation. Perfect for scroll-reveal effects without IntersectionObserver.

css
@keyframes reveal {
  from { opacity: 0; transform: translateY(40px); }
  to   { opacity: 1; transform: translateY(0); }
}

.card {
  animation: reveal linear;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

Scroll-Linked Progress Bar

A fixed progress bar that fills as you scroll the page. animation-timeline: scroll(root) maps the page scroll to the animation 0-100% range. transform-origin: 0 50% makes the bar grow from the left.

css
body { min-height: 200vh; }
.progress {
  position: fixed;
  top: 0; left: 0;
  height: 4px;
  width: 100%;
  background: linear-gradient(to right, #6366f1, #ec4899);
  transform-origin: 0 50%;
  animation: scale-x linear;
  animation-timeline: scroll(root);
}
@keyframes scale-x {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

Named Scroll Timelines

When you need to tie an animation to a specific scrollable element (not the root), define scroll-timeline-name on the container and reference it in animation-timeline. scroll-timeline-axis picks which axis to track (block/inline/x/y).

css
.scroll-container {
  scroll-timeline-name: --cards;
  scroll-timeline-axis: block;
  overflow-y: scroll;
  height: 400px;
}

.card {
  animation: fade-in linear;
  animation-timeline: --cards;
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

View Range Adjustments

animation-range fine-tunes when the animation runs relative to the element visibility. cover 0% to cover 100% spans from when the element enters until it fully leaves. contain 0% to 100% spans only when it is fully inside.

css
.image {
  animation: parallax linear;
  animation-timeline: view();
  animation-range: cover 0% cover 100%;
}

@keyframes parallax {
  from { transform: translateY(-20%); }
  to   { transform: translateY(20%); }
}

/* Only animate while in view */
.pulse {
  animation: pulse 1s infinite;
  animation-timeline: view();
  animation-range: contain 0% contain 100%;
}
26

CSS Layers

Defining @layer

@layer groups styles into named cascade layers. The order in the declaration line sets priority: later layers win over earlier ones, regardless of selector specificity. This brings predictable ordering to large stylesheets.

css
@layer reset, base, components, utilities;

@layer reset {
  * { margin: 0; padding: 0; box-sizing: border-box; }
}

@layer base {
  body { font-family: sans-serif; line-height: 1.6; }
}

@layer utilities {
  .hidden { display: none !important; }
}

Layer Priority

Within layers, normal specificity rules apply. But across layers, later layers always win — a single class in components beats an ID in base. Unlayered styles take priority over all layered styles.

css
@layer base, components;

@layer base {
  /* Even though this is more specific, base loses */
  div.button { color: black; }
}

@layer components {
  .button { color: blue; } /* This wins */
}

/* Unlayered styles always beat layered styles */
.button { color: red; }

Importing into Layers

@import ... layer(name) places an entire stylesheet into a named layer. This is essential for taming third-party CSS: put Bootstrap in a vendor layer, and your custom styles (in a later layer or unlayered) will always win.

css
@import url("reset.css") layer(reset);
@import url("bootstrap.css") layer(vendor);

@layer reset, vendor, custom;

@layer custom {
  .btn { background: hotpink; }
}

Nested Layers

Layers can nest, creating a hierarchy. framework.theme refers to the theme sub-layer inside framework. Nested layers follow the same priority rules: within framework, theme beats base.

css
@layer framework {
  @layer base, theme;

  @layer base {
    .btn { padding: 8px; }
  }
  @layer theme {
    .btn { color: navy; }
  }
}

/* Reference nested layer */
@layer framework.theme {
  .btn { color: hotpink; }
}

Layer Conditional Logic

@layer blocks can nest inside @media and @supports, so layer membership adapts to conditions. This keeps feature-specific styles organized within their layer rather than leaking into unlayered space.

css
@layer base, components;

@media (max-width: 600px) {
  @layer components {
    .nav { flex-direction: column; }
  }
}

@supports (display: grid) {
  @layer components {
    .layout { display: grid; }
  }
}
27

Backdrop Filters

backdrop-filter Basics

backdrop-filter applies a filter to the content behind an element, creating frosted-glass effects. Always include the -webkit- prefix for Safari. Combine with a semi-transparent background so the blur is visible.

css
.glass {
  background: rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 12px;
}

Multiple Filters

Chain multiple filters space-separated: blur, brightness, contrast, grayscale, hue-rotate, invert, opacity, saturate, sepia. Each applies to the backdrop. Pair with a tinted background to steer the mood.

css
.vibrant {
  backdrop-filter: blur(8px) saturate(180%) brightness(1.1);
}

.dark-glass {
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(12px) grayscale(50%);
}

Blend Modes

mix-blend-mode controls how an element blends with the content beneath it (separate from backdrop-filter). multiply darkens, screen lightens, difference inverts. Be cautious with text legibility.

css
.overlay {
  background: rgba(255, 0, 100, 0.3);
  backdrop-filter: blur(4px);
  mix-blend-mode: multiply;
}

.difference {
  background: white;
  mix-blend-mode: difference;
  backdrop-filter: invert(1);
}

Performance & Fallbacks

backdrop-filter is GPU-intensive, especially with large blur radii. Provide a solid background fallback for browsers without support, then enhance with @supports. Avoid animating backdrop-filter — it can cause severe jank.

css
/* Fallback for unsupported browsers */
.glass {
  background: rgba(255, 255, 255, 0.9);
}

/* Progressive enhancement */
@supports (backdrop-filter: blur(10px)) {
  .glass {
    background: rgba(255, 255, 255, 0.2);
    backdrop-filter: blur(10px);
  }
}

Glassmorphism Card

A classic glassmorphism card combines a translucent background, backdrop blur, a subtle light border (simulating edge reflection), and a soft shadow. The saturate filter boosts colors seen through the glass.

css
.card {
  background: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(16px) saturate(180%);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 16px;
  padding: 24px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.