30 CSS Keyframe Animations 29 / 30
CSS Morphing Progress Steps Animation
Four animated progress step UI patterns: horizontal step track, radial ring steps, morphing pill steps and animated vertical timeline — all scoped CSS keyframes.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="kf-29">
<h2>CSS Morphing Progress Steps</h2>
<!-- Horizontal steps -->
<div class="h-steps">
<div class="step-track">
<div class="step-node done">
<div class="node-circle">✓</div>
<div class="node-label">Account</div>
</div>
<div class="step-line done"></div>
<div class="step-node done">
<div class="node-circle">✓</div>
<div class="node-label">Profile</div>
</div>
<div class="step-line active"></div>
<div class="step-node active">
<div class="node-circle">3</div>
<div class="node-label">Payment</div>
</div>
<div class="step-line pending"></div>
<div class="step-node pending">
<div class="node-circle">4</div>
<div class="node-label">Review</div>
</div>
<div class="step-line pending"></div>
<div class="step-node pending">
<div class="node-circle">5</div>
<div class="node-label">Launch</div>
</div>
</div>
</div>
<!-- Radial rings -->
<div class="radial-steps">
<div class="radial-step">
<div class="radial-ring">
<svg viewBox="0 0 80 80"><circle class="ring-bg" cx="40" cy="40" r="35"/><circle class="ring-fill s1" cx="40" cy="40" r="35"/></svg>
<div class="ring-icon">📦</div>
</div>
<label>Order</label>
</div>
<div class="radial-step">
<div class="radial-ring">
<svg viewBox="0 0 80 80"><circle class="ring-bg" cx="40" cy="40" r="35"/><circle class="ring-fill s2" cx="40" cy="40" r="35"/></svg>
<div class="ring-icon">🚚</div>
</div>
<label>Shipped</label>
</div>
<div class="radial-step">
<div class="radial-ring">
<svg viewBox="0 0 80 80"><circle class="ring-bg" cx="40" cy="40" r="35"/><circle class="ring-fill s3" cx="40" cy="40" r="35"/></svg>
<div class="ring-icon">🗺️</div>
</div>
<label>Transit</label>
</div>
<div class="radial-step">
<div class="radial-ring">
<svg viewBox="0 0 80 80"><circle class="ring-bg" cx="40" cy="40" r="35"/><circle class="ring-fill s4" cx="40" cy="40" r="35"/></svg>
<div class="ring-icon">📬</div>
</div>
<label>Deliver</label>
</div>
</div>
<!-- Pill steps -->
<div class="pill-steps">
<div class="pill done-p"><span class="pill-dot"></span>Set up</div>
<div class="pill done-p"><span class="pill-dot"></span>Design</div>
<div class="pill active-p"><span class="pill-dot"></span>Build</div>
<div class="pill pending-p"><span class="pill-dot"></span>Review</div>
<div class="pill pending-p"><span class="pill-dot"></span>Deploy</div>
</div>
<!-- Vertical timeline -->
<div class="v-steps">
<div class="v-step done">
<div class="v-step-left">
<div class="v-dot">✓</div>
<div class="v-connector"></div>
</div>
<div class="v-step-right">
<h4>Requirements Gathered</h4>
<p>All project requirements documented and approved.</p>
</div>
</div>
<div class="v-step done">
<div class="v-step-left">
<div class="v-dot">✓</div>
<div class="v-connector"></div>
</div>
<div class="v-step-right">
<h4>Design Approved</h4>
<p>Wireframes and visual design signed off by stakeholders.</p>
</div>
</div>
<div class="v-step active-v">
<div class="v-step-left">
<div class="v-dot">●</div>
<div class="v-connector"></div>
</div>
<div class="v-step-right">
<h4>Development in Progress</h4>
<p>Frontend and backend implementation underway.</p>
</div>
</div>
<div class="v-step pend">
<div class="v-step-left">
<div class="v-dot">4</div>
<div class="v-connector"></div>
</div>
<div class="v-step-right">
<h4>QA Testing</h4>
<p>Automated and manual testing pipeline.</p>
</div>
</div>
<div class="v-step pend">
<div class="v-step-left">
<div class="v-dot">5</div>
</div>
<div class="v-step-right">
<h4>Production Launch</h4>
<p>Deploy to production and monitor.</p>
</div>
</div>
</div>
</div> <div class="kf-29">
<h2>CSS Morphing Progress Steps</h2>
<!-- Horizontal steps -->
<div class="h-steps">
<div class="step-track">
<div class="step-node done">
<div class="node-circle">✓</div>
<div class="node-label">Account</div>
</div>
<div class="step-line done"></div>
<div class="step-node done">
<div class="node-circle">✓</div>
<div class="node-label">Profile</div>
</div>
<div class="step-line active"></div>
<div class="step-node active">
<div class="node-circle">3</div>
<div class="node-label">Payment</div>
</div>
<div class="step-line pending"></div>
<div class="step-node pending">
<div class="node-circle">4</div>
<div class="node-label">Review</div>
</div>
<div class="step-line pending"></div>
<div class="step-node pending">
<div class="node-circle">5</div>
<div class="node-label">Launch</div>
</div>
</div>
</div>
<!-- Radial rings -->
<div class="radial-steps">
<div class="radial-step">
<div class="radial-ring">
<svg viewBox="0 0 80 80"><circle class="ring-bg" cx="40" cy="40" r="35"/><circle class="ring-fill s1" cx="40" cy="40" r="35"/></svg>
<div class="ring-icon">📦</div>
</div>
<label>Order</label>
</div>
<div class="radial-step">
<div class="radial-ring">
<svg viewBox="0 0 80 80"><circle class="ring-bg" cx="40" cy="40" r="35"/><circle class="ring-fill s2" cx="40" cy="40" r="35"/></svg>
<div class="ring-icon">🚚</div>
</div>
<label>Shipped</label>
</div>
<div class="radial-step">
<div class="radial-ring">
<svg viewBox="0 0 80 80"><circle class="ring-bg" cx="40" cy="40" r="35"/><circle class="ring-fill s3" cx="40" cy="40" r="35"/></svg>
<div class="ring-icon">🗺️</div>
</div>
<label>Transit</label>
</div>
<div class="radial-step">
<div class="radial-ring">
<svg viewBox="0 0 80 80"><circle class="ring-bg" cx="40" cy="40" r="35"/><circle class="ring-fill s4" cx="40" cy="40" r="35"/></svg>
<div class="ring-icon">📬</div>
</div>
<label>Deliver</label>
</div>
</div>
<!-- Pill steps -->
<div class="pill-steps">
<div class="pill done-p"><span class="pill-dot"></span>Set up</div>
<div class="pill done-p"><span class="pill-dot"></span>Design</div>
<div class="pill active-p"><span class="pill-dot"></span>Build</div>
<div class="pill pending-p"><span class="pill-dot"></span>Review</div>
<div class="pill pending-p"><span class="pill-dot"></span>Deploy</div>
</div>
<!-- Vertical timeline -->
<div class="v-steps">
<div class="v-step done">
<div class="v-step-left">
<div class="v-dot">✓</div>
<div class="v-connector"></div>
</div>
<div class="v-step-right">
<h4>Requirements Gathered</h4>
<p>All project requirements documented and approved.</p>
</div>
</div>
<div class="v-step done">
<div class="v-step-left">
<div class="v-dot">✓</div>
<div class="v-connector"></div>
</div>
<div class="v-step-right">
<h4>Design Approved</h4>
<p>Wireframes and visual design signed off by stakeholders.</p>
</div>
</div>
<div class="v-step active-v">
<div class="v-step-left">
<div class="v-dot">●</div>
<div class="v-connector"></div>
</div>
<div class="v-step-right">
<h4>Development in Progress</h4>
<p>Frontend and backend implementation underway.</p>
</div>
</div>
<div class="v-step pend">
<div class="v-step-left">
<div class="v-dot">4</div>
<div class="v-connector"></div>
</div>
<div class="v-step-right">
<h4>QA Testing</h4>
<p>Automated and manual testing pipeline.</p>
</div>
</div>
<div class="v-step pend">
<div class="v-step-left">
<div class="v-dot">5</div>
</div>
<div class="v-step-right">
<h4>Production Launch</h4>
<p>Deploy to production and monitor.</p>
</div>
</div>
</div>
</div>@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
.kf-29 *, .kf-29 *::before, .kf-29 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.kf-29 {
font-family: 'Inter', sans-serif;
background: #09090f;
color: #fff;
padding: 48px 24px;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
gap: 48px;
}
.kf-29 h2 {
font-size: 1rem;
color: #444;
letter-spacing: 3px;
text-transform: uppercase;
}
/* ── 1: Horizontal step progress ── */
.kf-29 .h-steps {
width: 100%;
max-width: 700px;
}
.kf-29 .step-track {
display: flex;
align-items: center;
position: relative;
}
.kf-29 .step-node {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
z-index: 2;
}
.kf-29 .node-circle {
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
font-weight: 600;
position: relative;
transition: all 0.3s;
}
.kf-29 .node-label {
font-size: 0.72rem;
color: #555;
letter-spacing: 0.5px;
text-align: center;
max-width: 80px;
}
.kf-29 .step-line {
flex: 1;
height: 3px;
background: #1e1e2a;
position: relative;
margin: 0 -2px;
margin-bottom: 28px;
}
.kf-29 .step-line::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
border-radius: 2px;
}
/* Completed step */
.kf-29 .step-node.done .node-circle {
background: #7c6af7;
color: #fff;
box-shadow: 0 0 0 0 rgba(124,106,247,0.4);
animation: kf-29-nodeshine 3s ease-in-out infinite;
}
@keyframes kf-29-nodeshine {
0%, 100% { box-shadow: 0 0 0 0 rgba(124,106,247,0.4); }
50% { box-shadow: 0 0 0 8px rgba(124,106,247,0); }
}
.kf-29 .step-node.done .node-label { color: #7c6af7; }
/* Active step */
.kf-29 .step-node.active .node-circle {
background: transparent;
border: 3px solid #7c6af7;
color: #7c6af7;
animation: kf-29-activepulse 1.5s ease-in-out infinite;
}
@keyframes kf-29-activepulse {
0%, 100% { border-color: #7c6af7; box-shadow: 0 0 0 0 rgba(124,106,247,0.5); }
50% { border-color: #a898fc; box-shadow: 0 0 0 6px rgba(124,106,247,0); }
}
.kf-29 .step-node.active .node-label { color: #ccc; }
/* Pending step */
.kf-29 .step-node.pending .node-circle {
background: #151525;
border: 2px solid #2a2a40;
color: #444;
}
/* Line fills */
.kf-29 .step-line.done::after {
background: #7c6af7;
width: 100%;
}
.kf-29 .step-line.active::after {
background: linear-gradient(90deg, #7c6af7, #b8acff);
animation: kf-29-linefill 2s ease-in-out infinite alternate;
}
@keyframes kf-29-linefill {
0% { width: 30%; }
100% { width: 85%; }
}
.kf-29 .step-line.pending::after { width: 0; }
/* ── 2: Circular / radial progress ── */
.kf-29 .radial-steps {
display: flex;
gap: 32px;
flex-wrap: wrap;
justify-content: center;
}
.kf-29 .radial-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.kf-29 .radial-ring {
position: relative;
width: 80px;
height: 80px;
}
.kf-29 .radial-ring svg {
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.kf-29 .ring-bg { fill: none; stroke: #1e1e30; stroke-width: 6; }
.kf-29 .ring-fill { fill: none; stroke-width: 6; stroke-linecap: round; stroke-dasharray: 220; }
.kf-29 .ring-fill.s1 { stroke: #7c6af7; stroke-dashoffset: 0; animation: kf-29-ring1 3s ease-in-out infinite; }
.kf-29 .ring-fill.s2 { stroke: #00d4ff; stroke-dashoffset: 55; animation: kf-29-ring2 3s ease-in-out infinite; }
.kf-29 .ring-fill.s3 { stroke: #ff6b35; stroke-dashoffset: 110; animation: kf-29-ring3 3s ease-in-out infinite; }
.kf-29 .ring-fill.s4 { stroke: #f7c948; stroke-dashoffset: 165; animation: kf-29-ring4 3s ease-in-out infinite; }
@keyframes kf-29-ring1 { 0%,100%{stroke-dashoffset:0} 50%{stroke-dashoffset:-10} }
@keyframes kf-29-ring2 { 0%,100%{stroke-dashoffset:55} 50%{stroke-dashoffset:45} }
@keyframes kf-29-ring3 { 0%,100%{stroke-dashoffset:110} 50%{stroke-dashoffset:90} }
@keyframes kf-29-ring4 { 0%,100%{stroke-dashoffset:165} 50%{stroke-dashoffset:155} }
.kf-29 .ring-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.2rem;
}
.kf-29 .radial-step label {
font-size: 0.7rem;
color: #555;
letter-spacing: 1px;
text-align: center;
text-transform: uppercase;
}
/* ── 3: Morphing pill steps ── */
.kf-29 .pill-steps {
display: flex;
gap: 8px;
align-items: center;
}
.kf-29 .pill {
height: 36px;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 0.8rem;
font-weight: 500;
overflow: hidden;
white-space: nowrap;
transition: all 0.4s;
}
.kf-29 .pill.done-p {
background: rgba(124,106,247,0.15);
border: 1px solid #7c6af7;
color: #7c6af7;
padding: 0 16px;
animation: kf-29-pilldone 4s ease infinite;
}
.kf-29 .pill.active-p {
background: #7c6af7;
color: #fff;
padding: 0 20px;
animation: kf-29-pillactive 4s ease infinite;
box-shadow: 0 4px 20px rgba(124,106,247,0.4);
}
.kf-29 .pill.pending-p {
background: #111125;
border: 1px solid #1e1e35;
color: #444;
padding: 0 14px;
animation: kf-29-pillpending 4s ease infinite;
}
@keyframes kf-29-pilldone {
0%, 100% { transform: scale(1); }
50% { transform: scale(0.98); }
}
@keyframes kf-29-pillactive {
0%, 100% { box-shadow: 0 4px 20px rgba(124,106,247,0.4); transform: scale(1); }
50% { box-shadow: 0 6px 30px rgba(124,106,247,0.6); transform: scale(1.03); }
}
@keyframes kf-29-pillpending {
0%, 100% { opacity: 0.6; }
50% { opacity: 0.8; }
}
.kf-29 .pill-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
/* ── 4: Vertical timeline steps ── */
.kf-29 .v-steps {
display: flex;
flex-direction: column;
gap: 0;
position: relative;
width: 100%;
max-width: 440px;
}
.kf-29 .v-step {
display: flex;
gap: 20px;
position: relative;
}
.kf-29 .v-step-left {
display: flex;
flex-direction: column;
align-items: center;
width: 40px;
flex-shrink: 0;
}
.kf-29 .v-dot {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
flex-shrink: 0;
z-index: 2;
}
.kf-29 .v-connector {
width: 2px;
flex: 1;
min-height: 32px;
margin: 2px 0;
}
.kf-29 .v-step-right {
padding: 4px 0 32px;
}
.kf-29 .v-step-right h4 { font-size: 0.9rem; font-weight: 600; color: #ccc; margin-bottom: 4px; }
.kf-29 .v-step-right p { font-size: 0.78rem; color: #555; line-height: 1.5; }
/* done */
.kf-29 .v-step.done .v-dot {
background: #7c6af7;
color: #fff;
animation: kf-29-vdoneshine 3s ease-in-out infinite;
}
@keyframes kf-29-vdoneshine {
0%,100%{box-shadow:0 0 0 0 rgba(124,106,247,0.4)}
50%{box-shadow:0 0 0 6px rgba(124,106,247,0)}
}
.kf-29 .v-step.done .v-connector { background: linear-gradient(180deg, #7c6af7, #a898fc); }
.kf-29 .v-step.done .v-step-right h4 { color: #fff; }
/* active */
.kf-29 .v-step.active-v .v-dot {
background: transparent;
border: 3px solid #7c6af7;
color: #7c6af7;
animation: kf-29-activepulse 1.5s ease-in-out infinite;
}
.kf-29 .v-step.active-v .v-connector {
background: linear-gradient(180deg, #a898fc, transparent);
animation: kf-29-vline 2s ease infinite alternate;
}
@keyframes kf-29-vline {
0% { opacity: 0.4; }
100% { opacity: 1; }
}
/* pending */
.kf-29 .v-step.pend .v-dot {
background: #111120;
border: 2px solid #1e1e30;
color: #333;
}
.kf-29 .v-step.pend .v-connector { background: #1a1a28; }
.kf-29 .v-step.pend .v-step-right h4 { color: #444; }
@media (prefers-reduced-motion: reduce) {
.kf-29 .node-circle,
.kf-29 .step-line.active::after,
.kf-29 .ring-fill,
.kf-29 .pill,
.kf-29 .v-dot,
.kf-29 .v-connector { animation: none; }
.kf-29 .step-line.active::after { width: 60%; }
} @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
.kf-29 *, .kf-29 *::before, .kf-29 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.kf-29 {
font-family: 'Inter', sans-serif;
background: #09090f;
color: #fff;
padding: 48px 24px;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
gap: 48px;
}
.kf-29 h2 {
font-size: 1rem;
color: #444;
letter-spacing: 3px;
text-transform: uppercase;
}
/* ── 1: Horizontal step progress ── */
.kf-29 .h-steps {
width: 100%;
max-width: 700px;
}
.kf-29 .step-track {
display: flex;
align-items: center;
position: relative;
}
.kf-29 .step-node {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
z-index: 2;
}
.kf-29 .node-circle {
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
font-weight: 600;
position: relative;
transition: all 0.3s;
}
.kf-29 .node-label {
font-size: 0.72rem;
color: #555;
letter-spacing: 0.5px;
text-align: center;
max-width: 80px;
}
.kf-29 .step-line {
flex: 1;
height: 3px;
background: #1e1e2a;
position: relative;
margin: 0 -2px;
margin-bottom: 28px;
}
.kf-29 .step-line::after {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
border-radius: 2px;
}
/* Completed step */
.kf-29 .step-node.done .node-circle {
background: #7c6af7;
color: #fff;
box-shadow: 0 0 0 0 rgba(124,106,247,0.4);
animation: kf-29-nodeshine 3s ease-in-out infinite;
}
@keyframes kf-29-nodeshine {
0%, 100% { box-shadow: 0 0 0 0 rgba(124,106,247,0.4); }
50% { box-shadow: 0 0 0 8px rgba(124,106,247,0); }
}
.kf-29 .step-node.done .node-label { color: #7c6af7; }
/* Active step */
.kf-29 .step-node.active .node-circle {
background: transparent;
border: 3px solid #7c6af7;
color: #7c6af7;
animation: kf-29-activepulse 1.5s ease-in-out infinite;
}
@keyframes kf-29-activepulse {
0%, 100% { border-color: #7c6af7; box-shadow: 0 0 0 0 rgba(124,106,247,0.5); }
50% { border-color: #a898fc; box-shadow: 0 0 0 6px rgba(124,106,247,0); }
}
.kf-29 .step-node.active .node-label { color: #ccc; }
/* Pending step */
.kf-29 .step-node.pending .node-circle {
background: #151525;
border: 2px solid #2a2a40;
color: #444;
}
/* Line fills */
.kf-29 .step-line.done::after {
background: #7c6af7;
width: 100%;
}
.kf-29 .step-line.active::after {
background: linear-gradient(90deg, #7c6af7, #b8acff);
animation: kf-29-linefill 2s ease-in-out infinite alternate;
}
@keyframes kf-29-linefill {
0% { width: 30%; }
100% { width: 85%; }
}
.kf-29 .step-line.pending::after { width: 0; }
/* ── 2: Circular / radial progress ── */
.kf-29 .radial-steps {
display: flex;
gap: 32px;
flex-wrap: wrap;
justify-content: center;
}
.kf-29 .radial-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.kf-29 .radial-ring {
position: relative;
width: 80px;
height: 80px;
}
.kf-29 .radial-ring svg {
width: 100%;
height: 100%;
transform: rotate(-90deg);
}
.kf-29 .ring-bg { fill: none; stroke: #1e1e30; stroke-width: 6; }
.kf-29 .ring-fill { fill: none; stroke-width: 6; stroke-linecap: round; stroke-dasharray: 220; }
.kf-29 .ring-fill.s1 { stroke: #7c6af7; stroke-dashoffset: 0; animation: kf-29-ring1 3s ease-in-out infinite; }
.kf-29 .ring-fill.s2 { stroke: #00d4ff; stroke-dashoffset: 55; animation: kf-29-ring2 3s ease-in-out infinite; }
.kf-29 .ring-fill.s3 { stroke: #ff6b35; stroke-dashoffset: 110; animation: kf-29-ring3 3s ease-in-out infinite; }
.kf-29 .ring-fill.s4 { stroke: #f7c948; stroke-dashoffset: 165; animation: kf-29-ring4 3s ease-in-out infinite; }
@keyframes kf-29-ring1 { 0%,100%{stroke-dashoffset:0} 50%{stroke-dashoffset:-10} }
@keyframes kf-29-ring2 { 0%,100%{stroke-dashoffset:55} 50%{stroke-dashoffset:45} }
@keyframes kf-29-ring3 { 0%,100%{stroke-dashoffset:110} 50%{stroke-dashoffset:90} }
@keyframes kf-29-ring4 { 0%,100%{stroke-dashoffset:165} 50%{stroke-dashoffset:155} }
.kf-29 .ring-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.2rem;
}
.kf-29 .radial-step label {
font-size: 0.7rem;
color: #555;
letter-spacing: 1px;
text-align: center;
text-transform: uppercase;
}
/* ── 3: Morphing pill steps ── */
.kf-29 .pill-steps {
display: flex;
gap: 8px;
align-items: center;
}
.kf-29 .pill {
height: 36px;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 0.8rem;
font-weight: 500;
overflow: hidden;
white-space: nowrap;
transition: all 0.4s;
}
.kf-29 .pill.done-p {
background: rgba(124,106,247,0.15);
border: 1px solid #7c6af7;
color: #7c6af7;
padding: 0 16px;
animation: kf-29-pilldone 4s ease infinite;
}
.kf-29 .pill.active-p {
background: #7c6af7;
color: #fff;
padding: 0 20px;
animation: kf-29-pillactive 4s ease infinite;
box-shadow: 0 4px 20px rgba(124,106,247,0.4);
}
.kf-29 .pill.pending-p {
background: #111125;
border: 1px solid #1e1e35;
color: #444;
padding: 0 14px;
animation: kf-29-pillpending 4s ease infinite;
}
@keyframes kf-29-pilldone {
0%, 100% { transform: scale(1); }
50% { transform: scale(0.98); }
}
@keyframes kf-29-pillactive {
0%, 100% { box-shadow: 0 4px 20px rgba(124,106,247,0.4); transform: scale(1); }
50% { box-shadow: 0 6px 30px rgba(124,106,247,0.6); transform: scale(1.03); }
}
@keyframes kf-29-pillpending {
0%, 100% { opacity: 0.6; }
50% { opacity: 0.8; }
}
.kf-29 .pill-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
/* ── 4: Vertical timeline steps ── */
.kf-29 .v-steps {
display: flex;
flex-direction: column;
gap: 0;
position: relative;
width: 100%;
max-width: 440px;
}
.kf-29 .v-step {
display: flex;
gap: 20px;
position: relative;
}
.kf-29 .v-step-left {
display: flex;
flex-direction: column;
align-items: center;
width: 40px;
flex-shrink: 0;
}
.kf-29 .v-dot {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.85rem;
flex-shrink: 0;
z-index: 2;
}
.kf-29 .v-connector {
width: 2px;
flex: 1;
min-height: 32px;
margin: 2px 0;
}
.kf-29 .v-step-right {
padding: 4px 0 32px;
}
.kf-29 .v-step-right h4 { font-size: 0.9rem; font-weight: 600; color: #ccc; margin-bottom: 4px; }
.kf-29 .v-step-right p { font-size: 0.78rem; color: #555; line-height: 1.5; }
/* done */
.kf-29 .v-step.done .v-dot {
background: #7c6af7;
color: #fff;
animation: kf-29-vdoneshine 3s ease-in-out infinite;
}
@keyframes kf-29-vdoneshine {
0%,100%{box-shadow:0 0 0 0 rgba(124,106,247,0.4)}
50%{box-shadow:0 0 0 6px rgba(124,106,247,0)}
}
.kf-29 .v-step.done .v-connector { background: linear-gradient(180deg, #7c6af7, #a898fc); }
.kf-29 .v-step.done .v-step-right h4 { color: #fff; }
/* active */
.kf-29 .v-step.active-v .v-dot {
background: transparent;
border: 3px solid #7c6af7;
color: #7c6af7;
animation: kf-29-activepulse 1.5s ease-in-out infinite;
}
.kf-29 .v-step.active-v .v-connector {
background: linear-gradient(180deg, #a898fc, transparent);
animation: kf-29-vline 2s ease infinite alternate;
}
@keyframes kf-29-vline {
0% { opacity: 0.4; }
100% { opacity: 1; }
}
/* pending */
.kf-29 .v-step.pend .v-dot {
background: #111120;
border: 2px solid #1e1e30;
color: #333;
}
.kf-29 .v-step.pend .v-connector { background: #1a1a28; }
.kf-29 .v-step.pend .v-step-right h4 { color: #444; }
@media (prefers-reduced-motion: reduce) {
.kf-29 .node-circle,
.kf-29 .step-line.active::after,
.kf-29 .ring-fill,
.kf-29 .pill,
.kf-29 .v-dot,
.kf-29 .v-connector { animation: none; }
.kf-29 .step-line.active::after { width: 60%; }
}How this works
The horizontal step track marks status via .done, .active, .pending classes. Completed circles pulse via kf-29-nodeshine animating box-shadow: 0 0 0 0 → 0 0 0 8px transparent, creating an expanding ripple. The active step adds a second border-colour swap (#7c6af7 → #a898fc) plus its own shadow pulse. The active connector line animates width: 30% → 85% with ease-in-out alternate for a progressing-flow look.
The radial steps use SVG circles with stroke-dasharray: 220; stroke-dashoffset: 0/55/110/165 for 0%/25%/50%/75% progress. Each runs a tiny ±10 dashoffset wobble for subtle life. The morphing pill row scales completed pills slightly, scales the active pill larger with a swelling shadow pulse, and fades pending pills' opacity. The vertical timeline reuses the same pulse pattern on dot nodes with a gradient connector.
Customize
- Change progress by swapping which steps have
.donevs.activevs.pendingclass names. - Recolour the active accent via the
#7c6af7hex used in.step-node.done,.active, and SVG strokes. - Slow the pulse cadence by changing
kf-29-nodeshinefrom3sto5sfor less attention-grabbing motion. - Adjust the radial fill ratio by editing the
stroke-dashoffsetinitial value — lower numbers = fuller arc. - Tighten the pill row by reducing the gap from
8pxto4pxfor denser progress indicators.
Watch out for
stroke-dasharray: 220assumes a circle withr=35— different radii need recalculation:2 * π * r.- The active line's
widthanimation hits layout — for many concurrent step trackers, replace withtransform: scaleX()andtransform-origin: left. - Pulse
box-shadowanimations are paint-heavy — fine for one progress tracker, expensive in a list of 20 onboarding cards.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 60+ | 12+ | 60+ | 60+ |