CSS3

Selectors, layout, typography, color, flexbox and grid, animation, custom properties and responsive design — with live playgrounds for the parts that are easier to feel than to read.

Every CSS3 property at a glance

Every entry, one line each. Highlighted ones have a detailed example below — click to jump there.

:hover
Matches while the pointer is over the element
:focus
Matches while the element has keyboard focus
:focus-visible
Matches focus only when it should be visibly indicated
:active
Matches while the element is being activated
:disabled
Matches a disabled form element
:checked
Matches a checked checkbox, radio, or option
:user-valid / :user-invalid
Like :valid/:invalid, but only after the user has interacted
:first-child
Matches an element that is the first child of its parent
:last-child
Matches an element that is the last child of its parent
:nth-child(n)
Matches elements by their position among siblings
:empty
Matches an element with no children
:not(sel)
Matches anything not matching the given selector
:is() / :where()
Groups selectors; :where() always contributes zero specificity
:has()
The relational pseudo-class — matches an element whose descendants (or later siblings) match, not just "parent"
::before / ::after
Generated content inserted before/after the content box
::placeholder
Styles the placeholder text of a form field
::selection
Styles the portion of text selected by the user
::marker
Styles the bullet/number of a list item
!important
Overrides normal cascade order — not a specificity level, a separate importance flag
inline style
An element's own style attribute beats any stylesheet selector except !important
@layer
Names a cascade layer — layers are ordered before specificity is even consulted
@scope
Limits a block of rules to a subtree, with an optional lower boundary
display
How an element participates in layout: block, inline, flex, grid, none
box-sizing
Whether width/height include padding and border
width / height
The content box size, unless box-sizing says otherwise
min-width / max-width
Clamps the computed width — max-width wins over width
min-height / max-height
Clamps the computed height in the block direction
margin
Space outside the border; vertical margins collapse in flow layout
padding
Space inside the border, around the content
border
Shorthand for border-width, border-style and border-color
border-radius
Rounds corners; a percentage or a huge px value gives a pill
outline
A ring drawn outside the border that takes up no layout space
overflow
What happens to content that exceeds the box: visible, hidden, auto, clip
position
static, relative, absolute, fixed or sticky positioning scheme
inset
Shorthand for top, right, bottom and left on a positioned element
z-index
Stacking order among positioned siblings in the same stacking context
aspect-ratio
Locks the box to a width:height ratio, reserving space before load
object-fit
How a replaced element (img, video) fills its box: cover, contain, fill
object-position
Which part of the image survives an object-fit: cover crop
float
Pulls an element to one side and lets inline content wrap around it
visibility
Hides an element while keeping its space in the layout
margin-inline / padding-block
Logical properties — map to physical sides based on writing direction
flex-direction
Which way the main axis runs: row, row-reverse, column, column-reverse
flex-wrap
Whether items overflow onto additional lines instead of shrinking
justify-content
Distributes items along the main axis
align-items
Aligns items along the cross axis
align-content
Distributes wrapped lines along the cross axis — needs flex-wrap
align-self
Overrides align-items for one individual item
flex
Shorthand for flex-grow, flex-shrink and flex-basis
flex-grow
Share of leftover free space this item absorbs
flex-shrink
How readily the item gives up space when the line overflows
flex-basis
The item's starting size before grow/shrink is applied
order
Reorders items visually without touching the DOM — skips screen readers
gap
Space between flex or grid tracks, with no margin on the outer edges
grid-template-columns
Defines the column tracks — px, fr, minmax(), repeat(), auto-fit
grid-template-rows
Defines the explicit row tracks
grid-template-areas
Names regions in ASCII art so items can be placed by name
grid-auto-flow
How auto-placed items fill the grid: row, column, or dense backfill
grid-auto-rows
Size of implicitly created rows beyond the explicit template
grid-column
Start/end column lines for an item — "1 / -1" spans every column
grid-row
Start/end row lines for an item, or a span count
grid-area
Places an item by named area, or by all four grid lines at once
justify-items
Inline-axis alignment of every item inside its own cell
place-items
Shorthand for align-items and justify-items — "center" centers anything
place-content
Shorthand for align-content and justify-content
font-family
Prioritized list of typefaces, ending in a generic family
font-size
Text size; rem keeps it tied to the user's browser setting
font-weight
100–900; variable fonts accept any value between the named steps
font-style
normal, italic, or oblique with an optional angle
line-height
Leading between lines; a unitless number scales with font-size
letter-spacing
Tracking between characters — usually tightened on large headings
text-align
Horizontal alignment of inline content: left, center, right, justify
text-transform
Renders text as uppercase, lowercase or capitalize without changing the DOM
text-decoration
Underline, overline or line-through, with color, style and thickness
text-underline-offset
Pushes the underline away from the baseline for legibility
text-wrap
balance evens out short headings; pretty avoids an orphaned last word
text-overflow
How clipped inline text is signaled — "ellipsis" needs overflow: hidden
white-space
Whether whitespace collapses and text wraps: normal, nowrap, pre, pre-wrap
text-shadow
Offset, blur and color drawn behind the glyphs
@font-face
Defines a custom font to load and use by name
color
Foreground text color, and the default for currentColor and borders
opacity
Transparency of the whole element and its children, 0 to 1
background-color
Solid fill painted behind the content, under any background image
background-image
One or more images or gradients, first layer on top
background-size
cover, contain, or explicit dimensions for each background layer
background-position
Where the background image is anchored within the box
background-repeat
Whether the image tiles, and along which axis
background-clip
Which box the background stops at — "text" clips it to the glyphs
box-shadow
Offset, blur, spread and color — inset draws it inside the border
filter
Graphical effects on the element: blur, brightness, saturate, drop-shadow
backdrop-filter
Applies a filter to whatever sits behind the element — frosted glass
mix-blend-mode
How the element composites with the backdrop: multiply, screen, overlay
accent-color
Tints native checkboxes, radios, range sliders and progress bars
color-scheme
Tells the browser which themes you support, so native UI follows
transition
Shorthand: property, duration, timing function, delay
transition-property
Which properties animate — naming them beats "all" for performance
transition-duration
How long the transition takes; 0s means no transition
transition-timing-function
The easing curve: ease, linear, steps(), cubic-bezier()
transition-delay
Wait time before the transition starts
animation
Shorthand for name, duration, easing, delay, count, direction, fill
animation-duration
Length of one cycle of the keyframe animation
animation-iteration-count
How many times to run — a number, or infinite
animation-direction
normal, reverse, alternate — whether cycles play backwards
animation-fill-mode
Whether the first/last keyframe sticks before and after the run
animation-timeline
Drives an animation by scroll() or view() instead of by time
@keyframes
Defines the steps of a CSS animation
@starting-style
Defines a from-state so a transition can fire on first appearance (e.g. display: none → shown)
transform
Visually translate, rotate, scale or skew without affecting layout
transform-origin
The pivot point that rotation and scaling happen around
translate / rotate / scale
Individual transform properties — animate independently of each other
perspective
Distance to the viewer, giving child 3D transforms real depth
will-change
Hints an upcoming change so the browser can promote a layer — use sparingly
--*
Declares a custom property; inherits and is live in the DOM
var()
Reads a custom property, with an optional fallback second argument
@property
Registers a custom property with a type, so it can animate
@media
Applies styles conditionally based on device/viewport features
@supports
Applies styles only if the browser supports a given feature
container-type
Makes an element a query container: inline-size, size, or normal
container-name
Labels a container so @container can target it specifically
@container
Styles a descendant based on its container's size, not the viewport's
clamp()
clamp(min, preferred, max) — fluid sizing with no media query
min() / max()
Picks the smaller or larger of comparable values at computed time
calc()
Mixes units in one expression: calc(100% - 2rem)
dvh / svh / lvh
Dynamic, small and large viewport height — account for mobile browser chrome
cursor
Which pointer shape appears over the element
pointer-events
"none" makes an element transparent to clicks and hovers
user-select
Whether the user can select the text inside
list-style
Marker type, position and image for list items
content
Sets the generated content of ::before and ::after — required for them to render
appearance
"none" strips the browser's native styling from a form control
scroll-behavior
"smooth" animates jumps from anchor links and scrollTo()
scroll-snap-type
Turns a scroll container into a snapping carousel: x, y, mandatory, proximity
scroll-snap-align
Which edge of a child snaps to the container: start, center, end
scroll-margin-top
Offsets an anchor target so a sticky header does not cover it
overscroll-behavior
"contain" stops scroll chaining from a modal to the page behind it
clip-path
Clips the element to a shape: inset(), circle(), polygon(), path()
writing-mode
Runs text vertically or horizontally, remapping block and inline axes
content-visibility
"auto" skips rendering off-screen subtrees for faster paints

Worked examples with live previews, for the parts worth seeing in action.

Selectors & Specificity

Combinators & Pseudo-classes

article p {
  /* descendant, any depth */
}
article > p {
  /* direct child only */
}
h2 ~ p {
  /* any following sibling */
}
h2 + p {
  /* immediately following sibling */
}

p:not(.lede) {
}
:is(h1, h2, h3) {
  /* matches any listed selector */
}
:where(h1, h2, h3) {
  /* same as :is, but contributes 0 specificity */
}
form:has(:invalid) {
  /* the relational pseudo-class — matches an ancestor whose descendant matches */
}
h2:has(+ p) {
  /* also matches by what comes AFTER — not just descendants */
}

Target elements by relationship instead of adding classes everywhere.

  • selector
  • combinator
  • :not
  • :is
  • :where
  • :has

Attribute & Structural Selectors

[data-state='open'] {
}
[href^='https://'] {
} /* starts with */
[href$='.pdf'] {
} /* ends with */
[class*='card-'] {
} /* contains */

li:nth-child(odd) {
}
li:nth-child(3n + 1) {
}
p:first-of-type {
}
p:last-child {
}

Specificity, low to high: type selectors (p) → class/attribute/pseudo-class (.card, [href], :hover) → ID (#nav). An element’s own inline style attribute beats all three. !important isn’t a specificity level at all — it’s a separate importance flag that overrides normal cascade order before specificity is even consulted.

Select by attribute value or position among siblings.

  • attribute selector
  • nth-child
  • nth-of-type
  • specificity

Box Model & Layout

The Box Model

*,
*::before,
*::after {
  box-sizing: border-box; /* width/height include padding + border */
}

.card {
  width: 320px;
  padding: 1.5rem;
  border: 1px solid var(--border);
  margin-block: 1rem; /* logical property: top+bottom, regardless of writing direction */
}

With border-box, .card’s rendered width stays 320px regardless of padding/border — the default content-box would add them on top. ::before/::after need including explicitly — they don’t inherit box-sizing from the wildcard on their own.

content, padding, border, margin — and the box-sizing switch that changes how width is calculated.

  • box-sizing
  • padding
  • margin
  • border
  • box model

Positioning

.tooltip-anchor {
  position: relative; /* establishes containing block for children */
}
.tooltip {
  position: absolute; /* positioned relative to nearest positioned ancestor */
  top: 100%;
  left: 0;
}
.site-header {
  position: sticky; /* scrolls normally, then sticks */
  top: 0;
  z-index: 10;
}
.modal-overlay {
  position: fixed; /* positioned relative to the viewport */
  inset: 0;
}

fixed’s containing block is normally the viewport — but any ancestor with transform, filter, backdrop-filter, perspective, will-change or contain becomes the containing block instead, which is a common source of “why won’t my fixed element stay put” bugs.

relative, absolute, fixed and sticky — and how they interact with the containing block.

relative
Offsets from its normal position; still establishes a containing block
absolute
Removed from flow; positioned against the nearest positioned ancestor
fixed
Removed from flow; positioned against the viewport — unless an ancestor has transform, filter, or will-change
sticky
Flows normally until a scroll threshold, then sticks
  • position
  • relative
  • absolute
  • fixed
  • sticky
  • z-index

Typography

Font Stack & Sizing

body {
  font-family: 'Inter Variable', Inter, system-ui, -apple-system, sans-serif;
  font-size: 1rem;
  line-height: 1.6;
}

h1 {
  font-family: 'Space Grotesk Variable', 'Space Grotesk', sans-serif;
  font-weight: 700;
  font-size: clamp(1.75rem, 1.4rem + 2vw, 3rem);
}

code {
  font-family: 'JetBrains Mono Variable', ui-monospace, monospace;
}

The generic family at the end of font-family is the fallback if none of the named fonts are available — never omit it.

A resilient font stack, and sizing that scales with the reader's own settings.

font-family
A prioritized list ending in a generic family (sans-serif, serif, monospace)
font-size
rem ties size to the root font size, which respects the user's browser zoom
line-height
A unitless number scales with font-size; a px value does not
font-weight
100–900; variable fonts accept any value in between, not just the named steps
  • font-family
  • font-size
  • line-height
  • font-weight

Text Overflow & Wrapping

h1 {
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

.single-line-truncate {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.multi-line-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

Truncating, balancing, and clamping text without cutting off mid-word.

text-wrap: balance
Evens out line lengths — best on short headings, not body copy
text-wrap: pretty
Avoids stranding a single word alone on the last line
text-overflow: ellipsis
Needs overflow: hidden and white-space: nowrap to have any effect
-webkit-line-clamp
Truncates after N lines — still needs the -webkit- prefix everywhere
  • text-wrap
  • text-overflow
  • line-clamp
  • white-space

Colors & Backgrounds

Color Functions

.card {
  color: rgb(23 23 31);
  background: hsl(258 60% 96%);
  border-color: rgb(0 0 0 / 0.1);
}

.accent {
  background: oklch(65% 0.18 290);
}

.hover-tint {
  background: color-mix(in srgb, var(--accent) 15%, transparent);
}

.icon {
  fill: currentColor;
}

rgb(), hsl(), oklch() and mixing colors without a preprocessor.

rgb() / hsl()
Both accept a slash-separated alpha: rgb(0 0 0 / 0.5)
oklch()
Perceptually uniform — adjusting lightness never shifts the hue
color-mix()
Blends two colors in a given color space, in any ratio
currentColor
Resolves to the element's own color — handy for borders and SVG fills
  • color
  • rgb
  • hsl
  • oklch
  • color-mix
  • opacity

Gradients & Backgrounds

.hero {
  background: linear-gradient(135deg, #a78bfa 0%, #7c9cf5 45%, #38e0e0 100%);
}

.spotlight {
  background: radial-gradient(circle at top, rgb(167 139 250 / 0.2), transparent 70%);
}

.photo {
  background-image: url('photo.jpg');
  background-size: cover;
  background-position: center;
}

.gradient-text {
  background: linear-gradient(90deg, #a78bfa, #38e0e0);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

Linear and radial gradients, sizing, and clipping a gradient to text.

linear-gradient()
An angle or side keyword, then color stops — optionally with positions
background-size: cover
Fills the box, cropping the overflow; "contain" fits without cropping
background-clip: text
Clips the background to the glyph shapes — pair with transparent text color
  • gradient
  • linear-gradient
  • background-size
  • background-clip

Flexbox

Flex Container & Item Properties

/* container properties */
.row {
  display: flex;
  flex-direction: row; /* row | row-reverse | column | column-reverse */
  justify-content: space-between; /* main-axis alignment */
  align-items: center; /* cross-axis alignment */
  flex-wrap: wrap;
  gap: 1rem;
}

/* item properties — set on a child, not the flex container */
.grow-item {
  flex: 1 1 auto; /* grow shrink basis */
  align-self: flex-start; /* overrides align-items for this one item */
  order: -1; /* moves it earlier visually — tab order still follows the DOM */
}

Try the values live in the playground below.

Container properties control the axes; item properties override one child at a time.

  • flexbox
  • display flex
  • justify-content
  • align-items
  • gap
  • align-self
  • order

Flexbox Playground

Toggle justify-content, align-items and direction and watch the layout respond live.

1
2
3
4

  • flexbox
  • playground
  • interactive
  • justify-content
  • align-items

Grid

Grid Container Properties

.layout {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto 1fr auto;
  gap: 1.5rem;
}

.responsive-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
}

.hero {
  grid-column: 1 / -1; /* span all columns */
}

Try the values live in the playground below.

Define columns, rows and gaps for two-dimensional layout.

  • grid
  • display grid
  • grid-template-columns
  • gap
  • grid-area

Grid Playground

Adjust column count, gap and item span and watch the grid respond live.

1
2
3
4
5
6

  • grid
  • playground
  • interactive
  • grid-template-columns

Transitions & Animations

Transitions

.button {
  transition-property: transform, box-shadow, background-color;
  transition-duration: 200ms;
  transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  /* shorthand: transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1); */
}

.button:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px -6px rgb(0 0 0 / 0.3);
}

Transitions fire on a property change caused by a class toggle, :hover, :focus, etc. — not on page load, unless you also declare @starting-style, which gives the browser an explicit “before” state to transition from on first render (e.g. display: none → shown).

Preview

Hover the button

Animate a property change between two states.

  • transition
  • transition-property
  • easing
  • duration

Keyframe Animations

@keyframes pulse {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0.4;
  }
  100% {
    opacity: 1;
  }
}

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

/* scroll-driven, no JavaScript/IntersectionObserver needed — not supported in
   every browser yet, so treat it as progressive enhancement */
.reveal-on-scroll {
  animation: pulse linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 30%;
}

@media (prefers-reduced-motion: reduce) {
  .badge,
  .reveal-on-scroll {
    animation: none;
  }
}

Always pair a looping animation like this with a prefers-reduced-motion override — the live preview below does.

Preview
pulse 2s ease-in-out infinite

Multi-step animations that run automatically, independent of state changes.

  • @keyframes
  • animation
  • animation-timeline

Custom Properties

CSS Variables

:root {
  --accent: #a78bfa;
  --radius: 12px;
}

.card {
  border-radius: var(--radius);
  border-color: var(--accent);
}

/* override for just this subtree */
.card--danger {
  --accent: #f87171;
}

Unlike Sass variables, custom properties are live in the DOM — inspect and edit them in devtools, or update them from JavaScript with el.style.setProperty('--accent', '#38e0e0').

Define once, reuse everywhere, override per-scope — no preprocessor required.

  • custom properties
  • css variables
  • var
  • --property
  • :root

Scoped Custom Properties & Fallbacks

:root {
  --accent: #a78bfa;
}

/* redeclaring --accent here only affects .danger and its descendants —
   everywhere else still sees the :root value */
.danger {
  --accent: #f87171;
}

.badge {
  color: var(--accent);
  /* falls back to 0.75rem if --badge-size is never defined anywhere */
  font-size: var(--badge-size, 0.75rem);
}

@property --progress {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

.bar {
  /* @property lets the browser animate a custom property smoothly */
  width: var(--progress);
  transition: --progress 300ms ease-out;
}

A custom property’s value cascades and inherits like any other property — the nearest ancestor’s declaration wins, which is what makes .danger above able to locally override --accent without a global rewrite.

How custom property values cascade through nested scopes, and how to guard against an unset variable.

  • var fallback
  • cascade
  • custom properties
  • @property

Responsive Design

Media Queries

/* mobile-first: base styles apply everywhere, then override upward */
.grid {
  display: grid;
  grid-template-columns: 1fr;
}

@media (min-width: 48rem) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 64rem) {
  .grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0a0a12;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

Breakpoints, and matching what the user actually asked for.

mobile-first
Write base styles for small screens, then min-width overrides for larger ones
prefers-color-scheme
Matches the OS-level dark/light preference
prefers-reduced-motion
Matches a user request to minimize animation — wrap non-essential motion in it
  • media query
  • @media
  • prefers-color-scheme
  • prefers-reduced-motion

Fluid Sizing & Container Queries

h1 {
  font-size: clamp(1.5rem, 1rem + 3vw, 3rem);
}

.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

@container sidebar (min-width: 20rem) {
  .card {
    grid-template-columns: auto 1fr;
  }
}

.full-height {
  height: 100dvh; /* accounts for mobile browser chrome showing/hiding */
}

clamp() for sizes that scale without breakpoints, and @container for components that respond to their own box, not the viewport.

clamp(min, preferred, max)
Fluid between min and max, driven by the preferred value (often a vw unit)
container-type
Opts an element in as a query container: inline-size, size, or normal
@container
Styles a descendant based on its container's size, not the viewport's
dvh / svh
Dynamic and small viewport height — account for mobile browser chrome
  • clamp
  • container query
  • @container
  • viewport units
  • dvh
webcheet