01. Basic Transition

A transition animates a CSS property whenever it changes (on hover, focus, class toggle, etc.). Without a transition the change is instant.

<button class="btn">Hover me</button>
.btn {
    display: inline-block;
    padding: .75rem 1.5rem;
    background: #6366f1;
    color: white;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    cursor: pointer;
    transition: background .25s ease, transform .15s ease, box-shadow .25s ease;
}
.btn:hover {
    background: #4f46e5;
    transform: translateY(-2px);
    box-shadow: 0 6px 20px 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.3s; }
.btn:hover  { background: purple; }
.btn:active { background: navy; }

02. Timing Functions

Controls how the transition progresses over time. Hover the track area in the demo to see all four run simultaneously.

<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: 1rem; 
 
    & .row  { 
        display: flex; 
        align-items: center; 
        gap: 1rem; 
 
        & .row-label { 
            width: 10rem; 
            font-size: .8rem; 
            flex-shrink: 0; 
        }
        & .track { 
            flex: 1; height: 2.5rem; 
            background: #f1f5f9; 
            border-radius: 99px; 
            position: relative; 
            overflow: hidden; 
            cursor: pointer; 
 
        }
    }
}
& .ball { 
    position: absolute; 
    left: 8px; top: 50%; 
    transform: translateY(-50%); 
    width: 1.75rem; height: 1.75rem; 
    background: #6366f1; 
    border-radius: 50%; 
    transition-duration: .6s; 
    transition-property: left; 
}
.rows:hover .ball { 
    left: calc(100% - 2.25rem); 
}
.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);

03. 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.

<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: .75rem; 
    padding: .5rem;
    border-radius: 8px; cursor: pointer;
}
.stagger-row:hover .s-item { 
    opacity: 1; 
    transform: translateY(0); 
}
.s-item {
    width: 3rem; height: 3rem; 
    border-radius: 6px;
    background: #6366f1; opacity: 0.1;
    transform: translateY(12px);
    transition: opacity .35s ease, transform .35s ease;
}
.s-item:nth-child(1) { transition-delay:   0ms; }
.s-item:nth-child(2) { transition-delay:  80ms; }
.s-item:nth-child(3) { transition-delay: 160ms; }
.s-item:nth-child(4) { transition-delay: 240ms; }
.s-item:nth-child(5) { transition-delay: 320ms; }
.hint { 
    font-size: .8rem; 
    color: #94a3b8; 
    margin-top: .75rem; 
}
.item {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity 0.4s ease, transform 0.4s ease;
}
 
.item:nth-child(1) { transition-delay: 0ms; }
.item:nth-child(2) { transition-delay: 80ms; }
.item:nth-child(3) { transition-delay: 160ms; }
.item:nth-child(4) { transition-delay: 240ms; }
.item:nth-child(5) { transition-delay: 320ms; }
 
.parent:hover .item {
    opacity: 1;
    transform: translateY(0);
}

04. Multiple Properties

Separate multiple transitions with a comma. Each property can have its own duration, timing, and delay.

<div class="card">
    <h3>Card title</h3>
    <p>Hover to see multiple properties transitioning together.</p>
</div>
.card {
    width: 200px;
    padding: 1.5rem;
    background: white;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    cursor: pointer;
    transition:
    transform   .2s ease,
    box-shadow  .2s ease,
    border-color .2s ease;
}
.card:hover {
    transform: translateY(-4px) scale(1.02);
    box-shadow: 0 12px 32px rgb(0 0 0 / .12);
    border-color: #6366f1;
}
.card h3 { 
    margin: 0 0 .5rem; 
    font-size: 1rem; 
}
.card p  { 
    margin: 0; 
    color: #64748b; 
    font-size: .875rem; 
}
.card {
    background:    hsl(247, 70%, 30%);
    transform:     scale(1);
    border-radius: 8px;
    box-shadow:    0 2px 8px rgba(0,0,0,0.3);
 
    transition:
        background     0.4s ease,
        transform      0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
        border-radius  0.4s ease,
        box-shadow     0.4s ease;
}
 
.card:hover {
    background:    hsl(175, 65%, 35%);
    transform:     scale(1.05);
    border-radius: 24px;
    box-shadow:    0 16px 40px rgba(0,0,0,0.5);
}

05. What Can (and Can’t) Be Transitioned

Only properties with numeric or color values can be transitioned. display cannot — use opacity + visibility instead.

<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: .75rem; 
 
    & .row { 
        display: flex; 
        align-items: center; 
        gap: 1rem; 
 
        & .row-label { 
            width: 11rem; 
            flex-shrink: 0; 
        }
    }
}
.swatch {
    display: inline-block; 
    width: 3rem; height: 2rem; 
    border-radius: 6px;
    background: #6366f1; 
    cursor: pointer;
    transition: all .4s 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(12deg); }
.t-shadow:hover  { box-shadow: 0 8px 24px rgba(99,102,241,.5); }
/* cannot transition display */
.t-display { position: relative; }
.t-display::after {
content: 'display can\'t transition';
position: absolute; left: 3.5rem; top: 50%; transform: translateY(-50%);
font-size: .75rem; 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.3s, visibility 0.3s;
}
.panel.visible {
    opacity: 1;
    visibility: visible;
}
 
/* expand height workaround — transition max-height instead */
.drawer { max-height: 0; overflow: hidden; transition: max-height 0.4s ease; }
.drawer.open { max-height: 500px; }