CSS Transition Properties
What You Learn From This Page
- How transitions interpolate supported CSS values after a state change.
- How the four longhand transition properties and the shorthand relate.
- How durations, timing functions, delays, and comma-separated lists are matched.
- Which properties transition well and which changes cause layout or painting costs.
- Why automatic dimensions, display changes, and invisible interactive elements need special handling.
- How to build button, card, menu, modal, tooltip, accordion, and focus transitions.
- How reduced-motion preferences and multiple input methods affect interaction design.
- How to debug missing, interrupted, unexpected, or slow transitions.
Introduction
CSS transitions let the browser interpolate between an element's old computed value and a new computed value over time. Without a transition, a button background changes immediately when a hover rule begins to match. With a transition, the browser generates the intermediate colors between the start and end states, producing controlled visual feedback.
A transition does not create the state change. Hover, focus, an active class, an attribute update, a media query, or script causes a property to receive a different value. Transition declarations describe how that change should unfold: which property participates, how long interpolation takes, how progress accelerates, and whether the start is delayed.
Useful transitions clarify cause and effect without making the interface feel slow. They are explicit, subtle, performant enough for their context, and accessible to users who reduce motion. Understanding the longhands, interpolation rules, and state lifecycle prevents common problems such as animating every changed property, leaving invisible elements interactive, or removing an element before its exit transition can finish.
A Basic Transition
.button {
background-color: steelblue;
transition: background-color 200ms ease;
}
.button:hover {
background-color: navy;
}
The base rule supplies both the initial color and the transition behavior. When hover begins, the color moves toward navy over 200 milliseconds. When hover ends, it moves back under the same transition because the declaration remains present on the base element.
The Four Transition Longhands
CSS separates transition behavior into four properties: transition-property, transition-duration, transition-timing-function, and transition-delay. The shorthand combines them.
.button {
transition-property: background-color;
transition-duration: 200ms;
transition-timing-function: ease;
transition-delay: 0ms;
}
Longhands are useful when values are generated independently or when debugging list matching. The shorthand is usually easier to read in component CSS.
transition-property
transition-property identifies which changed properties should interpolate. Values not listed still change, but they change immediately.
.card {
transition-property: transform, box-shadow, opacity;
}
Explicit lists communicate intent and prevent unrelated future declarations from acquiring motion. A transition property alone has no visible effect until a duration greater than zero is also present and one of those values changes.
transition-duration
Duration controls the time from start value to end value. It accepts seconds or milliseconds.
.button {
transition-duration: 150ms;
}
.panel {
transition-duration: 0.3s;
}
A zero-second duration is the initial value, so no animated interpolation is visible unless a duration is defined. Short feedback often falls around 100 to 200 milliseconds, ordinary interface state changes around 150 to 300 milliseconds, and larger movements may need somewhat longer. Content, distance, and context matter more than a universal number.
transition-timing-function
The timing function maps elapsed time to transition progress. It changes perceived acceleration without changing the declared duration.
.button {
transition-timing-function: ease-out;
}
Common keywords include ease, linear, ease-in, ease-out, and ease-in-out. A custom cubic-bezier() curve offers precise control, while steps() produces discrete jumps rather than smooth interpolation.
Common Timing Functions
ease is the general-purpose default. linear advances at a constant rate and often suits progress indicators or mechanically uniform effects. ease-in begins slowly and can suit an element leaving. ease-out begins quickly and settles near the end, which often feels responsive for entering controls. ease-in-out eases both ends for balanced movement.
Easing should support the interaction. A short button color change rarely needs a dramatic custom curve, while a larger panel movement may benefit from stronger deceleration.
transition-delay
Delay specifies how long the browser waits before beginning interpolation.
.tooltip {
transition: opacity 150ms ease 300ms;
}
A small tooltip delay can prevent flashing when a pointer merely crosses a trigger. Delaying direct feedback such as button activation often makes the interface feel unresponsive. Negative delays are valid and begin as though the transition has already progressed, but they are uncommon and should be used only with a clear reason.
The transition Shorthand
A single transition item usually follows property, duration, timing function, and delay.
.button {
transition: background-color 200ms ease 0ms;
}
The property and timing values are optional, but a time value is needed to create a visible duration. If two times appear, the first is duration and the second is delay. Consistent ordering makes declarations easier to scan.
Multiple Transitions
Separate transition items with commas when properties need independent behavior.
.card {
transition:
transform 150ms ease-out,
box-shadow 200ms ease,
opacity 300ms ease;
}
Each item owns its property, duration, easing, and optional delay. Explicit items are generally clearer than parallel longhand lists.
How Longhand Lists Align
Comma-separated longhand values are matched by position.
.card {
transition-property: opacity, transform;
transition-duration: 150ms, 250ms;
transition-timing-function: ease, ease-out;
}
Opacity receives the first duration and easing; transform receives the second. When list lengths differ, CSS repeats shorter lists according to its list-matching rules. Matching lengths or using shorthand items reduces ambiguity.
Put Transitions on the Base State
Defining a transition only inside :hover means the declaration disappears as soon as hover ends. The return path may therefore be immediate or behave differently.
.card {
transition: transform 200ms ease-out;
}
.card:hover {
transform: translateY(-0.25rem);
}
Place the common transition on the normal rule. State-specific transition declarations are valid when entering and leaving intentionally require different timing, but that difference should be explicit.
Transitions Need a Value Change
A transition declaration alone does nothing. A pseudo-class, class, attribute, query, or inline-style update must change the computed value of the listed property.
.panel {
opacity: 0;
transition: opacity 250ms ease;
}
.panel.is-open {
opacity: 1;
}
JavaScript can toggle is-open; CSS owns interpolation. This separation keeps state logic and presentation responsibilities clear.
What Can Trigger a Transition?
Transitions can respond to :hover, :focus-visible, :active, checked states, classes, data attributes, media queries, container queries, and style changes made by script. The browser compares the old and new computed values whenever the cascade changes.
Transitions caused by viewport resizing can look distracting if structural properties animate continuously. Do not add transition declarations to responsive layout dimensions merely because interpolation is possible.
Animatable and Interpolable Values
Many colors, lengths, numbers, shadows, filters, transforms, and opacity values can interpolate. Some properties use discrete behavior or have values that cannot produce meaningful intermediate states. Compatible units and value structures matter.
Consult the property's animation type when behavior is uncertain. A property being animatable does not mean it is a good performance or interaction choice.
Transitioning Color
.link {
color: #26364a;
transition: color 150ms ease;
}
.link:hover,
.link:focus-visible {
color: #075ea8;
}
Color feedback is inexpensive in small regions and can reinforce interactivity. Keep contrast sufficient at both endpoints and throughout the interpolation. Motion or color alone should not be the only communication of critical state.
Transitioning Background and Border Color
.input {
border: 1px solid #8796a8;
transition:
border-color 150ms ease,
box-shadow 150ms ease;
}
.input:focus {
border-color: #075ea8;
box-shadow: 0 0 0 3px rgb(7 94 168 / 0.2);
}
The transition supplements a clear focus state; it must not delay recognition or remove the browser outline without an adequate replacement.
Transitioning transform
Transforms support translation, scale, rotation, and other visual changes without altering normal document flow.
.card {
transform: translateY(0) scale(1);
transition: transform 180ms ease-out;
}
.card:hover {
transform: translateY(-0.25rem) scale(1.01);
}
Keep movement subtle in ordinary interfaces. Large lifts, rotations, and scales can distract, overlap nearby content, and cause discomfort.
Transitioning opacity
.message {
opacity: 0;
transition: opacity 200ms ease;
}
.message.is-visible {
opacity: 1;
}
Opacity changes rendering but does not remove layout space, pointer behavior, keyboard focus, or accessibility-tree presence. Visibility and interaction state must be managed separately when an element is truly unavailable.
Combining Opacity and Transform
.menu {
opacity: 0;
transform: translateY(-0.5rem);
transition:
opacity 160ms ease,
transform 160ms ease-out;
}
.menu.is-open {
opacity: 1;
transform: translateY(0);
}
This combination can make state changes easier to perceive. The menu also needs correct hidden state, focus handling, trigger semantics, and keyboard behavior; CSS motion is only one part of the component.
Transitioning box-shadow and filter
Shadows and filters can interpolate, but they often require repainting. A subtle card shadow or small image effect is usually acceptable; large blurred surfaces or many simultaneous filtered elements can be expensive.
.card {
box-shadow: 0 0.125rem 0.375rem rgb(0 0 0 / 0.12);
transition: box-shadow 200ms ease;
}
.card:hover {
box-shadow: 0 0.5rem 1.25rem rgb(0 0 0 / 0.2);
}
Measure representative devices before replacing a simple effect with premature optimization.
Width, Height, Margin, and Padding
Dimensions and box-model properties can transition between compatible numeric values, but they can trigger layout recalculation and affect surrounding elements.
.search {
width: 10rem;
transition: width 250ms ease;
}
.search:focus {
width: 18rem;
}
This may be appropriate in a controlled toolbar, but it can cause neighboring controls to move. For purely visual movement, transform is often a better fit. Layout transitions remain valid when the relationship itself needs to change and performance is acceptable.
The height: auto Challenge
Traditional transitions require endpoints that the browser can interpolate numerically. The distance from zero to auto depends on content and historically has not transitioned under ordinary behavior.
.panel {
height: 0;
transition: height 300ms ease;
}
.panel.is-open {
height: auto;
}
Support for newer intrinsic-size transition capabilities is evolving, but production code still needs a compatibility strategy appropriate to its browser policy.
max-height Workaround
.panel {
max-height: 0;
overflow: hidden;
transition: max-height 300ms ease;
}
.panel.is-open {
max-height: 30rem;
}
The chosen maximum must exceed content height, and the visible timing can feel uneven because the declared distance may be much larger than the content. It is convenient but not mathematically equivalent to transitioning the actual height.
Grid Expand-and-Collapse Pattern
.expander {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 250ms ease;
}
.expander.is-open {
grid-template-rows: 1fr;
}
.expander > div {
overflow: hidden;
}
This pattern can produce a content-aware reveal without selecting an arbitrary maximum. Test browser support, nested margins, focus visibility, and reduced motion.
display and Discrete State
Classic display: none to display: block changes do not produce ordinary intermediate values. If an element is removed before an exit fade, the fade cannot be seen. Modern CSS includes mechanisms for discrete transitions and entry styles, but support and component lifecycle still require deliberate handling.
For broad compatibility, coordinate rendered presence with opacity, visibility, classes, or script. Never keep an invisible menu keyboard-focusable simply to preserve a fade.
Visibility and Opacity
.tooltip {
opacity: 0;
visibility: hidden;
transition:
opacity 150ms ease,
visibility 0s linear 150ms;
}
.tooltip.is-visible {
opacity: 1;
visibility: visible;
transition-delay: 0s;
}
Opacity fades while visibility changes after the exit. Tooltips still need an appropriate trigger relationship and must not contain inaccessible interactive content under a tooltip pattern.
Pointer Events Are Not Complete State Management
.overlay {
opacity: 0;
pointer-events: none;
transition: opacity 200ms ease;
}
.overlay.is-open {
opacity: 1;
pointer-events: auto;
}
pointer-events: none stops pointer targeting, but it does not by itself remove descendants from keyboard focus or assistive-technology navigation. Manage semantic hidden state, focus, inert background content, and modal behavior with the appropriate HTML and script APIs.
Button Feedback
.button {
background-color: #075ea8;
transform: translateY(0) scale(1);
transition:
background-color 140ms ease,
transform 100ms ease-out;
}
.button:hover {
background-color: #064b86;
}
.button:active {
transform: translateY(0.0625rem) scale(0.98);
}
Fast, small movement can provide tactile feedback. Disabled and busy states should remain clear without relying on animation.
Card Elevation
.card {
transform: translateY(0);
transition:
transform 180ms ease-out,
box-shadow 180ms ease;
}
.card:hover,
.card:focus-within {
transform: translateY(-0.25rem);
box-shadow: 0 0.75rem 1.5rem rgb(0 0 0 / 0.16);
}
:focus-within can provide comparable emphasis when a card contains keyboard-focusable controls. Avoid moving the card so far that it overlaps unrelated content.
Dropdown and Menu Motion
A dropdown may fade and move slightly, but the interaction must work without hover and without motion. The trigger should expose expanded state, the menu should enter and leave the correct interaction model, and focus order should remain logical.
Hover delays can reduce accidental closure, but long delays make intentional action feel slow. Touch and keyboard behavior should not depend on pointer hover timing.
Modal and Overlay Motion
.overlay {
opacity: 0;
transition: opacity 180ms ease;
}
.dialog {
opacity: 0;
transform: scale(0.98);
transition:
opacity 180ms ease,
transform 180ms ease-out;
}
.modal.is-open .overlay,
.modal.is-open .dialog {
opacity: 1;
}
.modal.is-open .dialog {
transform: scale(1);
}
Keep scale changes small. Large zooming can be uncomfortable and is unnecessary for confirming that a dialog opened.
Accordion Indicator Motion
.accordion-icon {
transition: transform 180ms ease;
}
.accordion-button[aria-expanded="true"] .accordion-icon {
transform: rotate(180deg);
}
The icon supports the expanded state but does not replace the accessible attribute or visible label. Content reveal needs its own state and focus behavior.
Underline Motion
Animating width changes geometry. Scaling a pseudo-element can create a similar visual result without changing layout.
.link::after {
content: "";
display: block;
height: 0.125rem;
background: currentColor;
transform: scaleX(0);
transform-origin: left;
transition: transform 180ms ease;
}
.link:hover::after,
.link:focus-visible::after {
transform: scaleX(1);
}
Do not remove the ordinary underline from body links if the animated treatment weakens recognition.
Different Enter and Exit Timing
Some hover menus should open immediately but tolerate a brief pointer departure before closing.
.menu {
opacity: 0;
transition: opacity 150ms ease 200ms;
}
.menu.is-open {
opacity: 1;
transition-delay: 0ms;
}
State-specific delay creates intentional asymmetry. Test whether delayed closure interferes with keyboard or touch interaction and whether hidden controls remain available accidentally.
Avoid transition: all
transition: all 300ms ease is convenient but allows future changes to animate unexpectedly. A later hover rule might change width, padding, border radius, or layout position, producing motion that the original author never intended.
.card {
transition:
transform 180ms ease-out,
box-shadow 180ms ease;
}
Explicit property lists improve maintenance, debugging, and performance reasoning.
Performance and the Rendering Pipeline
Property changes can require style calculation, layout, painting, and compositing. Transform and opacity are often preferred for visual movement because browsers can commonly handle them during compositing without recalculating surrounding layout. That is a guideline, not a universal guarantee.
Width, height, margin, padding, and positional offsets can trigger layout. Shadows, filters, and large background changes may be paint-heavy. Performance depends on element size, effect complexity, device, browser, and how many elements change simultaneously.
Transform Instead of Positional Offsets
/* Purely visual movement */
.card:hover {
transform: translateY(-0.25rem);
}
This is generally preferable to transitioning top for a simple hover lift. If the movement must affect document flow or change the space allocated to the element, transform is not an equivalent layout solution.
Use will-change Carefully
will-change can tell the browser that a property is likely to change, allowing preparation. That preparation consumes memory and resources.
.frequently-moving-panel {
will-change: transform;
}
Do not add it globally or permanently to every interactive element. Apply it only after profiling demonstrates a real benefit, and remove it when preparation is no longer needed where practical.
Respect prefers-reduced-motion
Some users experience discomfort from sliding, zooming, parallax, and repeated spatial motion. Provide a reduced-motion version.
@media (prefers-reduced-motion: reduce) {
.menu,
.dialog,
.card {
transition: none;
}
}
A targeted policy often preserves harmless instant state changes while removing unnecessary movement. A subtle color change may remain acceptable, but the product should define and test its motion policy deliberately.
Motion Must Not Carry Essential Meaning
A transition may reinforce that an item expanded or a save completed, but users should still understand the state when motion is disabled. Use text, attributes, icons, contrast, and position appropriately. Hover motion must not reveal the only copy or action needed to operate a feature.
Support Hover and Focus
.link:hover,
.link:focus-visible {
color: #075ea8;
}
Comparable feedback for pointer and keyboard users improves consistency. Touch interactions need an explicit tap behavior rather than reliance on emulated hover.
Registered Custom Property Transitions
Ordinary custom properties are token streams and generally do not interpolate as typed numeric values. Registration gives the browser the information needed to animate compatible values.
@property --progress {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
.progress {
--progress: 0;
transition: --progress 500ms ease;
}
.progress.is-complete {
--progress: 1;
}
This is an advanced technique. Confirm support and connect the custom value to a rendered property or calculation.
Transitions versus Animations
A transition reacts to a change between states, such as normal to hover or closed to open. A CSS animation can run independently and use multiple keyframes, iteration counts, directions, and fill modes.
Use a transition for simple A-to-B state feedback. Use an animation when the sequence has multiple meaningful stages or runs without a state change. Do not choose animation merely because the effect is visually noticeable.
When Not to Transition
Motion is not automatically an improvement. Critical feedback may need to appear immediately. Constantly updating data should not animate every change. Large layout shifts can distract users, and transitions on hundreds of items can create jank and battery cost.
Ask what information the transition adds. If it does not clarify relationship, state, or continuity, an instant change may be better.
Motion Tokens in a Design System
Repeated durations and easing curves should become shared decisions rather than unrelated values scattered across components. Custom properties can define a small vocabulary for fast feedback, ordinary state changes, larger movement, and standard easing.
:root {
--motion-fast: 120ms;
--motion-normal: 200ms;
--motion-slow: 320ms;
--ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
--ease-enter: cubic-bezier(0, 0, 0.2, 1);
--ease-exit: cubic-bezier(0.4, 0, 1, 1);
}
.button {
transition:
background-color var(--motion-fast) var(--ease-standard),
transform var(--motion-fast) var(--ease-standard);
}
Tokens improve consistency and allow a motion policy to evolve centrally. They should describe interaction roles rather than producing dozens of nearly identical duration variables. A component can still choose a different value when distance, urgency, or platform behavior provides a concrete reason.
Reduced-motion behavior should be part of the same system. A media query can redefine selected duration tokens or disable spatial component transitions. Test that zero or near-zero durations do not break script that incorrectly assumes a fixed animation time.
Component Lifecycle and Transition Events
Interactive components often need to remain mounted during an exit transition and be removed only after it completes. JavaScript can listen for transitionend, but the handler should verify the event target and property name because one element may transition several properties and descendant events can bubble.
Do not make correctness depend only on an expected 200-millisecond timer. Durations can change through CSS, reduced-motion rules can make them effectively zero, and a transition may be canceled by a rapid state reversal. Code should handle transitioncancel, immediate completion, and repeated open-close actions without leaving stale classes or inaccessible focus.
A robust lifecycle separates logical state from visual phase. For example, a dialog can have closed, opening, open, and closing phases while its semantic modal state and focus behavior remain explicit. CSS transitions render the phase change; they do not replace the component state machine.
Debugging a Transition That Does Not Run
Confirm that the transition is applied, duration is greater than zero, and the property's computed value actually changes. Check whether the property is animatable and whether the start and end values are compatible. Inspect whether another rule overrides the transition or final value.
Also check reduced-motion rules, class timing, and element lifecycle. If script adds and removes states in the same rendering frame, the browser may never paint the start state. If the element is removed immediately, an exit transition cannot complete.
Debugging Interrupted and Reversed Transitions
Rapid state changes can reverse a transition mid-flight. CSS normally begins from the current interpolated value, which often feels natural. Problems arise when classes also replace transition declarations, reset transforms, or remove the element.
Use DevTools to inspect computed transition values and active timing. Slow motion playback and animation panels can reveal delays, curves, and interruption points that are difficult to infer from code.
Testing Transition Behavior
Test pointer, keyboard, touch, and programmatic state changes. Enter and leave states repeatedly, reverse them before completion, resize during an open component, and navigate while transitions are active. Verify both normal and reduced-motion modes.
Profile representative low-powered devices when large surfaces or many elements change. Confirm that motion does not hide focus, delay essential feedback, block input, or leave invisible controls interactive.
Transition Property Reference
| Property | Purpose | Initial Value |
|---|---|---|
transition-property | Names properties that should transition | all |
transition-duration | Sets interpolation time | 0s |
transition-timing-function | Maps time to progress | ease |
transition-delay | Waits before transition begins | 0s |
transition | Combines property, duration, easing, and delay | Individual initial values |
Timing Function Reference
| Value | Behavior | Typical Use |
|---|---|---|
ease | General acceleration and deceleration | Ordinary UI feedback |
linear | Constant progress rate | Progress or mechanical movement |
ease-in | Slow beginning | Selected exit motion |
ease-out | Fast beginning and slow finish | Entering or responsive feedback |
ease-in-out | Slow beginning and finish | Balanced movement |
cubic-bezier() | Custom acceleration curve | Design-system motion tokens |
steps() | Discrete progress jumps | Stepped or frame-like effects |
Common Mistakes and Better Approaches
| Mistake | Better Approach |
|---|---|
| Transition declared only on hover | Place common behavior on the base state |
transition: all on every component | List only intentional properties |
| Two-second button feedback | Use a short duration suited to the interaction |
| Large lift, rotation, and scale on hover | Keep ordinary UI motion subtle |
Animating top for a visual lift | Use transform when flow should not change |
| Opacity alone hides an interactive menu | Manage visibility, pointer, focus, and semantic state |
| Element removed before exit completes | Coordinate lifecycle with transition completion |
| Assuming zero to auto always interpolates | Use a supported intrinsic-size strategy or workaround |
will-change applied globally | Use only after profiling a proven need |
| Large panels slide despite reduced motion | Provide a reduced-motion state |
| Hover is the only state trigger | Support focus, tap, and programmatic state |
| Motion communicates the only state cue | Provide persistent semantic and visual information |
| Hundreds of elements transition together | Limit motion to feedback with clear value |
Common Interview Questions
What is a CSS transition? It is a mechanism that interpolates supported CSS property values over time when their computed values change.
What are the four transition longhands? They are transition-property, transition-duration, transition-timing-function, and transition-delay.
What is the common shorthand order? Use property, duration, timing function, and delay, with comma-separated items for multiple properties.
Why place transitions on the base element? The declaration then remains active when entering and leaving a state, allowing both directions to transition.
Why avoid transition: all? Future changes to unrelated animatable properties can gain unexpected motion and performance cost.
Which properties are commonly preferred for visual movement? Transform and opacity often avoid layout recalculation and are commonly composited efficiently.
Why does height: auto create difficulty? Traditional interpolation needs numeric endpoints, while auto is resolved from content rather than being one fixed length.
What is the difference between a transition and an animation? A transition responds to a value change between states; an animation can run independently through multiple keyframes.
How should reduced motion be handled? Remove or simplify unnecessary spatial motion under prefers-reduced-motion: reduce while preserving clear state feedback.
Practical Review Checklist
- Define common transition behavior on the base state.
- List only the properties intended to animate.
- Use durations appropriate to movement distance and interaction urgency.
- Choose easing that supports entering, leaving, or direct feedback.
- Use delays only when they reduce accidental interaction or clarify sequence.
- Prefer transform and opacity for purely visual movement and fades.
- Use layout-property transitions only when the layout itself should interpolate.
- Keep everyday interface motion subtle.
- Manage visibility, pointer access, focus, and semantic state separately from opacity.
- Coordinate element removal with exit-transition completion.
- Avoid assuming automatic dimensions always interpolate.
- Use
will-changeonly after measuring a real need. - Provide reduced-motion behavior for large or unnecessary movement.
- Make state understandable even when transitions are disabled.
- Support hover, focus, tap, and keyboard interaction appropriately.
- Test rapid reversal, interruption, resize, and repeated state changes.
- Profile large surfaces and simultaneous transitions on realistic devices.
Key Takeaway
A CSS transition describes how a supported property moves from one computed value to another after a state change. Explicitly choose the property, duration, timing function, and optional delay. Keep common behavior on the base state, use comma-separated items for independent values, and remember that a transition requires an actual property change.
Good interface motion is restrained and purposeful. Prefer transform and opacity for visual movement where they fit, but manage layout, visibility, focus, and semantics according to the component's real state. Avoid broad all transitions, unnecessary delays, and large motion without reduced-motion support. Test multiple input methods, reversals, lifecycle timing, and performance. A transition succeeds when it clarifies continuity and feedback without slowing, confusing, or excluding the user.