Basic Transition
A transition animates a CSS property whenever it changes (on hover, focus, class toggle, etc.). Without a transition the change is instant.
Preview index.html style.css
< button class = "btn" >Hover me</ button > .btn {
display : inline-block ;
padding : .75 rem 1.5 rem ;
background : #6366f1 ;
color : white ;
border : none ;
border-radius : 8 px ;
font-size : 1 rem ;
cursor : pointer ;
transition : background .25 s ease , transform .15 s ease , box-shadow .25 s ease ;
}
.btn:hover {
background : #4f46e5 ;
transform : translateY ( -2 px );
box-shadow : 0 6 px 20 px rgb ( 99 102 241 / .4 );
}
.btn:active { transform : translateY ( 0 ); }
/* shorthand: property duration timing-fn delay */
transition: background 0 .3s ease;
transition: all 0 .3s ease;
transition: transform 0 .4s ease, opacity 0 .3s ease; /* multiple */
/* individual longhand */
transition-property : transform;
transition-duration : 0.4s;
transition-timing-function : ease-out ;
transition-delay : 0.1s;
.btn { background : blue ; transition : background 0.3 s ; }
.btn:hover { background : purple ; }
.btn:active { background : navy ; }
Timing Functions
Controls how the transition progresses over time. Hover the track area in the demo to see all four run simultaneously.
Preview index.html style.css
< div class = "rows" >
< div class = "row" >
< span class = "row-label" >linear</ span >
< div class = "track ease-linear" >
< div class = "ball" ></ div >
</ div >
</ div >
< div class = "row" >
< span class = "row-label" >ease-in</ span >
< div class = "track ease-in" >
< div class = "ball" ></ div >
</ div >
</ div >
< div class = "row" >
< span class = "row-label" >ease-out</ span >
< div class = "track ease-out" >
< div class = "ball" ></ div >
</ div >
</ div >
< div class = "row" >
< span class = "row-label" >ease-in-out</ span >
< div class = "track ease-in-out" >
< div class = "ball" ></ div >
</ div >
</ div >
</ div >
< p >Hover to see each track animate.</ p > .rows {
display : flex ;
flex-direction : column ;
gap : 1 rem ;
& . row {
display : flex ;
align-items : center ;
gap : 1 rem ;
& . row-label {
width : 10 rem ;
font-size : .8 rem ;
flex-shrink : 0 ;
}
& .track {
flex : 1 ; height : 2.5 rem ;
background : #f1f5f9 ;
border-radius : 99 px ;
position : relative ;
overflow : hidden ;
cursor : pointer ;
}
}
}
& .ball {
position : absolute ;
left : 8 px ; top : 50 % ;
transform : translateY ( -50 % );
width : 1.75 rem ; height : 1.75 rem ;
background : #6366f1 ;
border-radius : 50 % ;
transition-duration : .6 s ;
transition-property : left ;
}
.rows:hover .ball {
left : calc ( 100 % - 2.25 rem );
}
.ease-linear .ball {
transition-timing-function : linear ;
}
.ease-in .ball {
transition-timing-function : ease-in ;
}
.ease-out .ball {
transition-timing-function : ease-out ;
}
.ease-in-out .ball {
transition-timing-function : ease-in-out ;
}
transition-timing-function : linear; /* constant speed */
transition-timing-function : ease; /* slow → fast → slow (default) */
transition-timing-function : ease-in ; /* starts slow, ends fast */
transition-timing-function : ease-out ; /* starts fast, ends slow */
transition-timing-function : ease-in-out ; /* slow → fast → slow (symmetric) */
/* custom cubic-bezier — use cubic-bezier.com to find values */
transition-timing-function : cubic-bezier(0 .34 , 1 .56 , 0 .64 , 1); /* spring-like */
/* steps — discrete jumps, useful for sprite animation */
transition-timing-function : steps(5, end);
Delay & Staggering
transition-delay waits before the transition starts. Apply increasing delays to siblings to create a stagger effect. Hover the row in the demo.
Preview index.html style.css
< div class = "stagger-row" >
< div class = "s-item" ></ div >
< div class = "s-item" ></ div >
< div class = "s-item" ></ div >
< div class = "s-item" ></ div >
< div class = "s-item" ></ div >
</ div >
< p class = "hint" >Hover the row to trigger stagger.</ p > .stagger-row {
display : flex ;
gap : .75 rem ;
padding : .5 rem ;
border-radius : 8 px ; cursor : pointer ;
}
.stagger-row:hover .s-item {
opacity : 1 ;
transform : translateY ( 0 );
}
.s-item {
width : 3 rem ; height : 3 rem ;
border-radius : 6 px ;
background : #6366f1 ; opacity : 0.1 ;
transform : translateY ( 12 px );
transition : opacity .35 s ease , transform .35 s ease ;
}
.s-item:nth-child ( 1 ) { transition-delay : 0 ms ; }
.s-item:nth-child ( 2 ) { transition-delay : 80 ms ; }
.s-item:nth-child ( 3 ) { transition-delay : 160 ms ; }
.s-item:nth-child ( 4 ) { transition-delay : 240 ms ; }
.s-item:nth-child ( 5 ) { transition-delay : 320 ms ; }
.hint {
font-size : .8 rem ;
color : #94a3b8 ;
margin-top : .75 rem ;
}
.item {
opacity : 0 ;
transform : translateY ( 12 px );
transition : opacity 0.4 s ease , transform 0.4 s ease ;
}
.item:nth-child ( 1 ) { transition-delay : 0 ms ; }
.item:nth-child ( 2 ) { transition-delay : 80 ms ; }
.item:nth-child ( 3 ) { transition-delay : 160 ms ; }
.item:nth-child ( 4 ) { transition-delay : 240 ms ; }
.item:nth-child ( 5 ) { transition-delay : 320 ms ; }
.parent:hover .item {
opacity : 1 ;
transform : translateY ( 0 );
}
Multiple Properties
Separate multiple transitions with a comma. Each property can have its own duration, timing, and delay.
Preview index.html style.css
< div class = "card" >
< h3 >Card title</ h3 >
< p >Hover to see multiple properties transitioning together.</ p >
</ div > .card {
width : 200 px ;
padding : 1.5 rem ;
background : white ;
border : 1 px solid #e2e8f0 ;
border-radius : 12 px ;
cursor : pointer ;
transition :
transform .2 s ease ,
box-shadow .2 s ease ,
border-color .2 s ease ;
}
.card:hover {
transform : translateY ( -4 px ) scale ( 1.02 );
box-shadow : 0 12 px 32 px rgb ( 0 0 0 / .12 );
border-color : #6366f1 ;
}
.card h3 {
margin : 0 0 .5 rem ;
font-size : 1 rem ;
}
.card p {
margin : 0 ;
color : #64748b ;
font-size : .875 rem ;
}
.card {
background : hsl ( 247 , 70 % , 30 % );
transform : scale ( 1 );
border-radius : 8 px ;
box-shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.3 );
transition :
background 0.4 s ease ,
transform 0.3 s cubic-bezier ( 0.34 , 1.56 , 0.64 , 1 ),
border-radius 0.4 s ease ,
box-shadow 0.4 s ease ;
}
.card:hover {
background : hsl ( 175 , 65 % , 35 % );
transform : scale ( 1.05 );
border-radius : 24 px ;
box-shadow : 0 16 px 40 px rgba ( 0 , 0 , 0 , 0.5 );
}
What Can (and Can’t) Be Transitioned
By default, only properties with numeric or color values can be transitioned. For example, display cannot — you would use opacity + visibility instead.
Preview index.html style.css
< div class = "rows" >
< div class = "row" >< span class = "row-label" >color ✓</ span > < div class = "swatch t-color" ></ div ></ div >
< div class = "row" >< span class = "row-label" >opacity ✓</ span > < div class = "swatch t-opacity" ></ div ></ div >
< div class = "row" >< span class = "row-label" >transform ✓</ span > < div class = "swatch t-transform" ></ div ></ div >
< div class = "row" >< span class = "row-label" >box-shadow ✓</ span > < div class = "swatch t-shadow" ></ div ></ div >
< div class = "row" >< span class = "row-label" >display ✗</ span > < div class = "t-display" >< div class = "swatch" ></ div ></ div ></ div >
</ div >
< p style = "font-size:.8rem;color:#94a3b8;margin-top:1rem" >Hover each row.</ p > .rows {
display : flex ;
flex-direction : column ;
gap : .75 rem ;
& . row {
display : flex ;
align-items : center ;
gap : 1 rem ;
& . row-label {
width : 11 rem ;
flex-shrink : 0 ;
}
}
}
.swatch {
display : inline-block ;
width : 3 rem ; height : 2 rem ;
border-radius : 6 px ;
background : #6366f1 ;
cursor : pointer ;
transition : all .4 s ease ;
}
/* can transition */
.t-color:hover { background : #10b981 ; }
.t-opacity { opacity : 1 ; }
.t-opacity:hover { opacity : 0.1 ; }
.t-transform:hover { transform : scale ( 1.5 ) rotate ( 12 deg ); }
.t-shadow:hover { box-shadow : 0 8 px 24 px rgba ( 99 , 102 , 241 , .5 ); }
/* cannot transition display */
.t-display { position : relative ; }
.t-display::after {
content : 'display can \' t transition' ;
position : absolute ; left : 3.5 rem ; top : 50 % ; transform : translateY ( -50 % );
font-size : .75 rem ; color : #ef4444 ; white-space : nowrap ;
}
/* can't transition display: none → block */
/* use opacity + visibility instead */
.panel {
opacity : 0 ;
visibility : hidden ; /* hidden removes from tab order */
transition : opacity 0.3 s , visibility 0.3 s ;
}
.panel.visible {
opacity : 1 ;
visibility : visible ;
}
/* expand height workaround — transition max-height instead */
.drawer { max-height : 0 ; overflow : hidden ; transition : max-height 0.4 s ease ; }
.drawer.open { max-height : 500 px ; }