Shots in this scene
What Can and Can't Transition
In one line
The browser can blend numbers, lengths, colours and shapes, so those properties transition smoothly. Keyword properties such as display have no in-between, so they flip, and values like height: auto cannot be blended with a length at all.
Think of it like this
A painter can mix any amount of orange into blue and get every shade between. But a door is open or closed. Half-way between open and closed is not a place a door can be. CSS properties are like that. Numbers and colours are paint: the browser can mix them. Keywords such as display: none or position: absolute are doors: there is no in-between to show.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- interpolate
- To work out the values between a start value and an end value, for example 40% of the way from 0 to 200 is 80.
- discrete property
- A property whose values are keywords with nothing between them, like display or text-align. It switches instead of blending.
- allow-discrete
- The transition-behavior value that lets a discrete property take part in a transition, flipping at 50%. display and visibility have special rules.
- @starting-style
- A rule that gives an element the style to start from on its first frame, when it has no earlier style to change from.
Step by step
01Read the table
Run "Which properties transition?". Each line says whether the browser created a transition for one property with transition: all 1000ms linear. Nine make one. background-image, height (to auto), display, text-align, border-style and position do not.
02Slow down the flip
Run "Four ways to flip", press 0.1×, then Switch all four, and scrub to the middle. The text-align box moves its label to the right between 480 and 520 ms. The visibility box stays until the very end.
03Watch display leave
In the same example, the third box fades and then disappears at 1000 ms (display with allow-discrete). The fourth box, which has no allow-discrete, is gone in the first frame, so its fade is never seen.
04Grow to auto
In "Auto, gradients and colours" press Switch. The first box jumps to its full height. The second grows over 1000 ms, because of interpolate-size: allow-keywords.
Try it yourself
- 1
Find the half-way flip
In "Four ways to flip", press 0.1×, press Switch all four, then pause and step one frame at a time near 500 ms. The
text-aligntrack isCSSTransition · text-alignwith a layout mark. The label is on the left at 480 ms and on the right at 520 ms, with no movement between: a flip, not a slide. - 2
Take allow-discrete away
In the
#drule, deletedisplay 1000ms linear allow-discrete,so only theopacityitem is left. Press Switch all four: the third box now vanishes in the first frame, exactly like the fourth, and its opacity fade is never seen because it is already gone.
Code & diagrams
The script changes each property with `transition: all 1000ms linear` set, and asks the browser how many transitions it created. A tick means it made one.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
opacity: transition
width: transition
background-color: transition
font-weight: transition
z-index: transition
box-shadow: transition
transform: transition
filter: transition
visibility: transition
background-image: no transition
height: no transition
display: no transition
text-align: no transition
border-style: no transition
position: no transitionAll four take 1000 ms. Slow the page to 0.1x, press Switch all four and step: text-align changes at the half-way point, visibility at the end, display fades then leaves, and the fourth box leaves at once.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Press Switch. Box 1 (height: auto) jumps, box 2 (interpolate-size) grows, gradient 1 swaps, gradient 2 animates a registered --stop, and the two colour bars blend in sRGB and in Oklab.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
interpolate-size supported: trueBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Transition a height to auto
Give a box height: 0; overflow: hidden; transition: height 300ms; and a class that sets height: auto. Add the class.
Break #2
Fade out with display: none but no allow-discrete
In "Four ways to flip", look at the fourth box: #n { transition: opacity 1000ms linear; } and #n.on { display: none; opacity: 0; }.
Myth vs fact
Myth
transition: all animates every property.
Fact
It animates every property the browser can interpolate. Discrete ones (text-align, display, border-style) are left out unless you add allow-discrete, and height: auto and gradients cannot be blended at all.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
While an element fades out with
visibilityordisplayheld on, it is stillvisible: it receives clicks and keyboard focus until the end. For a closing menu, setpointer-events: noneand theinertattribute at the start of the exit, so it cannot be used while it fades. - ▸
transition-behavioris not inherited and is reset by the shorthand:transition: opacity 1s(no keyword) setstransition-behavior: normal, which switchesallow-discreteoff. Put the keyword inside each item of the list, or settransition-behaviorafter the shorthand.
Remember this
- 1
The problem this solves:
transition: all 300mslooks as if it animates everything. It does not. A menu that should fade out vanishes at once, a panel that should grow to its natural height jumps, a gradient swaps in a single frame, and nothing says why. Knowing the rule in advance saves hours of guessing. - 2
Interpolation. To make the frames between two values the browser must interpolate: work out a value at, say, 37% of the way. It can do that for numbers (
opacity,z-index, which rounds to a whole number), lengths and percentages (width,padding), colours, transforms, shadows, filters and shapes such asclip-pathwith matching functions. In the table below, nine of the fifteen properties made a transition. Each property's specification says how it can be animated (MDN lists them under animatable properties). - 3
Discrete properties flip at 50%. A property whose values are keywords (
text-align,border-style,position,display) has no in-between. By default the browser starts no transition for it, andtransition: allleaves it out (measured:text-align: no transition). Addtransition-behavior: allow-discrete(or the keyword in the shorthand,transition: text-align 1s allow-discrete) and it holds the old value for the first half and shows the new one for the second. Frozen at 480 ms of 1000 ms,text-alignwasstart. At 520 ms it wasright. - 4
**
visibilityanddisplaybreak the 50% rule on purpose.** Forvisibility, the element counts asvisiblefor the whole transition if either end isvisible. Fading out tohiddenkeeps it visible to the very end (measured:visibleat 999 ms,hiddenat 1010 ms). Withallow-discrete,displayworks the same way: going tononeit staysblockuntil the end (measured:blockat 999 ms,noneat 1010 ms), and coming fromnoneit turns on at the start. That lets an element fade out and then leave the layout. - 5
**Entering from
display: noneneeds@starting-style.** A transition needs a before-change style, and an element that wasdisplay: nonehas none.@starting-style { .panel.open { opacity: 0; } }supplies the values to start from on the first frame. The exercisefade-out-and-hideneeds bothallow-discreteand@starting-style: removing either breaks the fade (measured). Support (MDN, read 24 Oct 2026):@starting-styleis Baseline 2024 (in all major browsers since August 2024);transition-behaviorandinterpolate-sizeare marked limited availability. Chrome shipped the first two in 117 andinterpolate-sizein 129. For older browsers, write the transition twice, once withoutallow-discreteand once with it: a browser that does not know the keyword drops the second line and keeps the first. The panel then still opens and closes, just without the fade. - 6
**
height: autocannot transition.** The browser cannot blend0pxwith the keywordauto(measured: no transition, the box jumps). The old workaround animatesmax-heightto a big number, which gets the timing wrong, because the duration covers the whole range. Better:interpolate-size: allow-keywordsmakesautoanimate (Chromium; elsewhere the box jumps, which is an acceptable fallback), or animategrid-template-rowsfrom0frto1fron a grid wrapper (measured: it animates, but not in a straight line: 29.5 px of 118 at the half-way time). Phase 10 shows the general fix, FLIP. - 7
Gradients and colour spaces. A gradient is an image, and images flip: a gradient to another gradient changes in one frame (measured). To animate one, animate the numbers inside it: register
@property --stop { syntax: "<percentage>"; ... }and transition--stop(measured:50%at 500 ms), or fade theopacityof a second layer, which is cheaper. Plain colours do interpolate. Betweenrgb(255 0 0)andrgb(0 0 255)the middle isrgb(128, 0, 128)(sRGB). With a modern function such asoklch(), Chromium blends in Oklab instead, and the in-between colours look cleaner.
Explain it without notes
Why does transition: all 300ms not fade out an element that you then set to display: none?
What is the difference between how visibility and text-align behave under allow-discrete?
How would you animate a panel to its natural height, and what do you do for a browser that does not support your method?
Practice
A tooltip is display: none and gets .show. Write the CSS so it fades in over 150 ms and fades out over 150 ms and leaves the layout at the end.
A button's gradient background goes from orange-to-blue to blue-to-orange on hover. It snaps. Make it animate with @property.
Done when you can
You can tell from the property's type whether it will transition, flip or do nothing
You can fade an element out and take it out of the layout with allow-discrete
You know why entering from display: none needs @starting-style
You have a plan for height: auto and for gradients, and a fallback when it is not supported