A CSS transition interpolates between two states when something changes — a hover, a focus, a checked box — and it reverses cleanly, which is what separates it from a keyframe animation. These 22 hand-coded patterns cover timing functions and linear() springs, delay choreography, transitioning to display: none, @starting-style entry, height-auto, animating custom properties, :has() state machinery, and a reduced-motion strategy. Scoped under .trn-NN, gated behind @supports; drops into React, Vue, Svelte, or Astro.

19 pure CSS3 light JSPublished

Related20 CSS Rotate Animations30 CSS Keyframe Animations30 CSS Hover Effects11 CSS Page Transitions

CSS Transition Property Reference and Timing — preview
01 / 22Pure CSS

CSS Transition Property Reference and Timing

The reference build the rest of the collection borrows from: three buttons that spell out the shorthand order, an annotated panel naming every longhand it expands to, and three identical markers running the same distance under ease, ease-in-out and a custom cubic-bezier() so the curves are visually distinguishable. Includes the beginner trap where two bare time values are silently read as duration then delay.

PublishedUpdated

Transition Delay Choreography with Reverse Exit — preview
02 / 22Pure CSS

Transition Delay Choreography with Reverse Exit

A menu panel whose items transition in with an increasing transition-delay and — the part almost every implementation gets wrong — transition out in reverse order, because the delay on the resting state is calculated from the other end of the list. Both delay sets are printed beside the panels so the entry ladder and the exit ladder can be read directly.

PublishedUpdated

Transform Transitions on the Compositor Thread — preview
03 / 22Pure CSS

Transform Transitions on the Compositor Thread

Two identical cards, side by side and both labelled: the left one transitions top, left and width, the right one transitions transform. The rules and their per-frame cost are printed on each card, so the difference between a transition that re-runs layout sixty times a second and one that hands a matrix to the compositor is readable rather than theoretical.

PublishedUpdated

Custom Easing with the linear Timing Function — preview
04 / 22Pure CSS

Custom Easing with the linear Timing Function

cubic-bezier() can only describe a single smooth curve, so a bounce that settles twice is impossible to express. The linear() timing function takes a list of stops instead and approximates springs, bounces and stepped eases directly in a transition. Three markers run the same distance under three linear() strings, each printed beside its track, behind an @supports gate with a bezier fallback declared first.

PublishedUpdated

Transitioning Transform Scale Without Layout Shift — preview
05 / 22Pure CSS

Transitioning Transform Scale Without Layout Shift

An editorial image card that scales inside an overflow: hidden frame, so the zoom never touches the surrounding layout. Covers the two traps that make hand-rolled zooms look broken: scaling the media instead of a wrapper leaves the frame's rounded corners cutting the wrong shape, and scaling text rasterises it at the old size so captions go soft mid-transition.

PublishedUpdated

Color and Gradient Transitions with Registered Properties — preview
06 / 22Pure CSS

Color and Gradient Transitions with Registered Properties

background-image is a discrete property, so a gradient cannot be interpolated — swapping one for another flips at the midpoint no matter what duration you write. The fix is to register the gradient's angle and its stop colours as typed @property custom properties and transition those instead. The naive attempt and the registered version sit side by side, both labelled.

PublishedUpdated

Border and Outline Transitions Without Reflow — preview
07 / 22Pure CSS

Border and Outline Transitions Without Reflow

Transitioning border-width changes the element's box, so every hover reflows the row and nudges its neighbours. Three alternatives that produce the same visual ring with no layout cost are shown beside the naive version and labelled: an inset box-shadow, an outline with outline-offset, and a pseudo-element border that scales.

PublishedUpdated

Transition to Display None with allow-discrete — preview
08 / 22Pure CSS

Transition to Display None with allow-discrete

For twenty years an element set to display: none vanished on the spot and any fade-out was lost, which is why hiding used to mean visibility plus a delay. transition-behavior: allow-discrete flips the discrete property at the end of the transition instead of the start. Two toasts, both labelled: one fades out correctly, one snaps.

PublishedUpdated

Focus Within Parent State Transitions — preview
09 / 22Pure CSS

Focus Within Parent State Transitions

A workspace search panel that expands its filter tray when any descendant receives focus, using :focus-within on the parent so keyboard and touch users get the affordance that mouse users get from :hover. A hover-only reveal is not a styling shortcut — it is a feature that does not exist for anyone who cannot hover.

PublishedUpdated

Transitioning Height Auto with interpolate-size — preview
10 / 22Pure CSS

Transitioning Height Auto with interpolate-size

The oldest unsolved transition problem: height: auto had no numeric value to interpolate toward, so every accordion guessed with max-height — clipping tall content or easing against a value the content never reaches. interpolate-size: allow-keywords makes the keyword interpolable. All three techniques are shown open and closed, side by side and labelled.

PublishedUpdated

Entry Transitions with starting-style — preview
11 / 22Light JS

Entry Transitions with starting-style

An element entering the DOM or the top layer has no previous computed value, so there is nothing for a transition to start from — which is why entry animations used to need a keyframe or a forced reflow. @starting-style declares that before-state. Shown on a deliberately plain <dialog>: the chrome is minimal because the lesson is the entry transition, not the modal.

PublishedUpdated

View Transition Names and Pseudo Elements — preview
12 / 22Light JS

View Transition Names and Pseudo Elements

The CSS surface of the View Transition API: view-transition-name on the elements that should be treated as the same thing across a DOM change, and ::view-transition-old() / ::view-transition-new() to style the crossfade the browser generates. A card expands into a detail panel in the same document. Covers the unique-name trap, where two elements sharing a name kill the transition silently.

PublishedUpdated

Data Attribute State Machine Transitions — preview
13 / 22Light JS

Data Attribute State Machine Transitions

One element moving through idle, loading, success and error, driven entirely by a single data-state attribute, with every visual difference expressed as a transition between attribute selectors. Argues against class juggling: one attribute is one source of truth and cannot land in an impossible combined state the way four independent booleans can.

PublishedUpdated

Underline Transitions with Transform Origin — preview
14 / 22Pure CSS

Underline Transitions with Transform Origin

The scaleX() underline, taught as a transform-origin lesson rather than a link effect. Four rows show what the property actually controls: growing from the left, retreating to the right, opening from the centre, and the one people ask about — switching the origin between the hover and resting state so the rule enters from the left and exits to the right.

PublishedUpdated

Transitioning Custom Properties with at-property — preview
15 / 22Pure CSS

Transitioning Custom Properties with at-property

An unregistered custom property is an unparsed string to the engine, so transition: --angle 400ms does exactly nothing — the most common "why won't my CSS variable animate" question there is. Registering it with @property and a typed syntax makes it interpolable. The silent no-op sits beside the working version, both driven from the same hover.

PublishedUpdated

Has Selector Driven Sibling Transitions — preview
16 / 22Pure CSS

Has Selector Driven Sibling Transitions

:has() lets a state on one element drive a transition on a sibling or an ancestor, which used to require JavaScript for no reason other than selector direction. Here a field's validity transitions the styling of the summary row beside it. Includes the specificity surprise: :has() takes the specificity of its most specific argument, so it can quietly outrank the rule you expected to win.

PublishedUpdated

Backdrop Filter Transitions and Their Cost — preview
17 / 22Pure CSS

Backdrop Filter Transitions and Their Cost

Transitioning backdrop-filter: blur() forces everything behind the element to be re-composited on every frame, which makes it one of the most expensive transitions available. Both approaches are shown and labelled: the correct blur transition, and the cheaper alternative that cross-fades a pre-blurred layer instead. Ships -webkit-backdrop-filter alongside, and shows the transformed-ancestor trap.

PublishedUpdated

Staggered Transition Delay with Custom Property Index — preview
18 / 22Pure CSS

Staggered Transition Delay with Custom Property Index

Per-item transition-delay computed from an index custom property set inline — style="--i: 3" plus calc(var(--i) * 60ms) — so a list of any length staggers from a single rule. Compared directly against the :nth-child() approach, which needs one selector per item and silently stops staggering the moment the list grows past the last rule you wrote.

PublishedUpdated

Transition All Versus Explicit Property Lists — preview
19 / 22Pure CSS

Transition All Versus Explicit Property Lists

transition: all looks like convenience and causes three concrete problems: it animates properties you never intended — including ones a parent changed — it makes per-property durations impossible, and it asks the engine to watch every animatable property on the element. Both cards are labelled and identical except for that one declaration; only one of them glitches.

PublishedUpdated

Interruptible Transitions and Reverse Playback — preview
20 / 22Pure CSS

Interruptible Transitions and Reverse Playback

What happens when a transition is interrupted mid-flight: it reverses from wherever it currently is, because the running value is the new start value. A keyframe timeline restarts from frame zero instead. Sweep the pointer on and off the pad below to feel the difference — this is the strongest argument for transitions over animations in interactive UI.

PublishedUpdated

Discrete Property Transition Behaviour — preview
21 / 22Pure CSS

Discrete Property Transition Behaviour

Not every property interpolates. Discrete properties — display, visibility, content-visibility, overlay, flex-direction and others — have no midpoint, so by default they flip at 50% of the duration. This demo marks that midpoint on a timeline, shows the same property flipping at the end under transition-behavior: allow-discrete, and lists which properties behave this way.

PublishedUpdated

Reduced Motion Transition Strategy — preview
22 / 22Pure CSS

Reduced Motion Transition Strategy

The reflexive @media (prefers-reduced-motion: reduce) { * { transition: none !important } } is the wrong answer: it removes the gentle fades that actually reduce motion sickness, and it can strand elements mid-state when a transition was carrying them somewhere. The same component is shown under all three treatments, each labelled, with the current preference detected and displayed.

PublishedUpdated

FAQ

Frequently asked questions

What is the difference between a CSS transition and a CSS animation?
A transition interpolates between a start state and an end state when something changes — a hover, a focus, a checked box, a flipped attribute. It has no timeline of its own, it is reversible by definition, and it is declared with transition-property, transition-duration, transition-timing-function, transition-delay, and transition-behavior. An animation runs a @keyframes timeline that exists independently of state: it can loop, it can hold, and it can pass through as many intermediate frames as you write. The practical decision is usually made by two questions. Does the motion need to loop or pass through more than two states? If yes, you need a keyframe animation, because a transition can only ever describe two endpoints. Does the motion need to reverse cleanly when the user changes their mind halfway through? If yes, you want a transition, because transitions reverse smoothly from whatever the current computed value is, while an interrupted keyframe animation restarts or jumps unless you do extra work. This collection covers transitions; keyframe timelines are covered separately.
Why does my transition on a custom property do nothing?
Because an unregistered custom property is just a string as far as the engine is concerned, and strings are not interpolable. When you write --angle: 0deg and transition to --angle: 90deg, CSS has no idea that value is an angle rather than arbitrary text, so it applies the change discretely and your transition silently does nothing. Registering the property with @property and a typed syntax descriptor fixes it: once the engine knows --angle is an <angle>, it can compute the values in between. The same mechanism is what makes gradients transitionable. background-image is a discrete property, so a gradient cannot be transitioned directly no matter what duration you set. The workaround is to register the gradient's angle and colour stops as typed custom properties and transition those instead, letting the gradient recompute each frame from the interpolated values. Demo 15 shows the silent no-op beside the working registered version, and demo 06 applies the same idea to gradients.
How do I transition an element to display none without it snapping away?
Historically you could not, and that is why so many fade-out implementations look broken. The moment display: none applies, the element is gone from the box tree, so any opacity transition you declared never gets a chance to run and the element vanishes instantly. The old workaround was to transition visibility alongside opacity with a matching delay, which fades correctly but leaves the element occupying layout and hanging around in the accessibility tree. The modern fix is transition-behavior: allow-discrete, which tells the engine to flip a discrete property at the end of the transition rather than the start. Combined with listing display in your transition property list, the element stays rendered for the duration of the fade and only then leaves the box tree. Pair it with @starting-style if you also want a matching entry transition, since an element being added to the DOM has no previous state to interpolate from. Demo 08 ships the toast that fades correctly next to the one that snaps, and demo 21 covers which properties are discrete and when the flip happens.
Can I finally transition height from zero to auto?
Yes, with a caveat about support. Setting interpolate-size: allow-keywords on the root opts the page into interpolating intrinsic size keywords, after which transition: height works against height: auto exactly as you would expect. It is available in Chrome and Firefox from version 129 and has not shipped in Safari yet, so it needs a fallback rather than being used bare. The fallback that works everywhere is the grid technique: wrap the content in a grid whose grid-template-rows transitions from 0fr to 1fr, with the inner element set to min-height: 0 and overflow: hidden. It measures the content correctly at any height and needs no JavaScript. What you should avoid is the max-height hack, where you transition to some arbitrary large value and hope the content is shorter. It clips whenever the content exceeds your guess, and because the transition runs the full distance to that guessed value rather than to the real height, the easing visibly finishes early and the panel appears to stall. Demo 10 shows all three side by side.
Is transition all actually a problem?
It causes three real problems that are easy to miss in development and obvious in production. First, it transitions properties you never intended, including ones changed by a parent or by a state you did not think about, which is where those mysterious half-second layout slides during page load come from. Second, it forces every property to share one duration and one easing curve, so you cannot have a fast colour change alongside a slower transform. Third, it asks the engine to watch every animatable property on that element rather than the two you care about. The fix is simply to list what you mean: transition: transform 240ms ease, opacity 240ms ease. The one place all is defensible is a throwaway prototype where nothing else is changing. Demo 19 builds a component where all produces a visible glitch and the explicit list does not, so you can see the failure rather than take it on faith.
Which properties are cheap to transition and which are not?
transform and opacity are the cheap ones, because the browser can hand them to the compositor and animate them without recalculating layout or repainting. Anything that changes geometry — width, height, top, left, margin, padding, border-width — triggers layout on every frame, which is why a hover that nudges an element with top can stutter while the same motion via translate stays smooth. Colour properties sit in between: they repaint but do not reflow, which is fine for a handful of elements and expensive across dozens. backdrop-filter is the most expensive commonly-used transition, because it forces a re-composite of everything behind the element on every frame; where you can, transition the opacity of a pre-blurred layer instead. Border width is a special case worth calling out because the fix is so easy — an inset box-shadow ring or an outline with outline-offset both give you a growing border without affecting layout at all. Demos 03, 07 and 17 cover these trade-offs with both sides labelled.
What is the right way to handle prefers-reduced-motion for transitions?
Not with a blanket kill switch. The rule you see everywhere — a universal selector setting transition: none !important under the reduced-motion query — is a blunt instrument that causes two problems. It removes transitions that actively help, like a gentle opacity fade that softens an abrupt content swap, and because it can cut a transition off partway it sometimes strands an element in a half-applied state that no rule then corrects. Reduced motion means reduced vestibular triggers, not the absence of all change. The strategy that holds up is to keep opacity and colour transitions, remove the ones that move things through space — transform, translate, scale — and shorten durations rather than eliminating them, so the interface still acknowledges a state change without sliding across the viewport. Demo 22 puts a component under all three treatments so the difference between the blanket rule and a considered one is visible rather than theoretical.
How is this different from the hover effects and keyframe animation collections?
Three collections sit adjacent and each owns a distinct thing. This one covers the transition property itself — the mechanics of interpolating between two states, the timing functions, the newer primitives like allow-discrete, @starting-style and interpolate-size, and the state machinery that triggers them. css-keyframe-animations owns @keyframes timelines: looping motion, multi-stage choreography, anything that runs independently of state. css-hover-effects owns hover as a design surface — what a button, card or link should look and feel like when pointed at, where the transition is the delivery mechanism rather than the subject. If you are asking which easing curve to use or why your custom property will not interpolate, you are in the right place. If you are asking what a good card hover looks like, the hover collection is the better starting point.
How portable are these across React, Vue, Svelte, and Astro?
Every demo is framework-agnostic HTML and CSS scoped under a unique .trn-NN root, so nothing leaks into a host page, and 19 of the 22 need no JavaScript at all. The three that do use vanilla ES2024 in under twenty lines each: opening a native dialog, calling document.startViewTransition, and flipping a data attribute. Two things need attention when you port them. Modern features are gated behind @supports with a working fallback declared first, so keep the fallback declaration if you lift the rules into a component library — deleting it is how a demo that degraded gracefully starts failing outright on an older engine. And if your framework scopes styles automatically, check that it has not rewritten the view-transition-name values in demo 12 or the @property registrations in demos 06 and 15, since both are global by specification and a scoping transform can quietly break them. In Astro the pure-CSS demos render as static HTML with no island at all. Tailwind users can lift the rules into @layer components on v3, or declare the same tokens in @theme on v4.

Related collections

20 CSS Animated Buttons preview

20 CSS Animated Buttons

20 hand-coded CSS animated buttons — neon glow, ripple, 3D press, liquid fill, jelly bounce, shine sweep, animated border, moving gradient CTA, text flip, submit success state, add-to-cart progress, download icon, hamburger-to-close, toggle switch, loading spinner inside button, next/prev arrow nav, and ghost button background reveal. Half pure CSS, half lightweight JS for production interactions.

11 CSS animation-timeline Demos preview

11 CSS animation-timeline Demos

11 pure CSS animation-timeline demos built on the W3C scroll() and view() spec — reading progress bar, view-timeline fade & reveal, shrinking sticky header, stacking cards, horizontal scroll section, parallax hero, container shadow indicators, reverse-scroll columns, text scrim reveal, cover card to sticky header, and image zoom / clip-path wipe. Zero JS, off the main thread, Core Web Vitals safe. Copy-paste ready.

25 CSS Background Animations preview

25 CSS Background Animations

25 hand-coded CSS background animations for SaaS hero sections, developer tool documentation, agency portfolios, gaming and Web3 landing pages, seasonal e-commerce campaigns, and dark-mode dashboards. Covers full-screen gradient shifts, animated mesh gradients, aurora borealis glow, cursor spotlight follow, interactive water ripples, dot grid and diagonal stripe patterns, SVG grain and noise texture overlays, topographic contour lines, layered SVG waves, plasma and lava lamp fluids, bokeh light blur, particle constellation networks, matrix digital rain, synthwave 3D perspective grids, starfield warp speed, CRT scanline overlays, fog and mist drift, morphing organic blobs, floating glassmorphism orbs, halftone dot patterns, starry night skies, floating geometric shapes, rising bubbles, and falling snow. 20 are pure CSS with zero JavaScript; the 5 that need it use vanilla canvas or pointer tracking with no dependencies. Every background animates compositor-only properties so it never blocks the main thread, is scoped under a .bga-NN prefix for no-collision pasting, guards prefers-reduced-motion, and ports unchanged to React, Vue, Svelte, Astro, Next.js and Tailwind.

Search CodeFronts

Loading…