Skip to content
animorio
IT
All components

Steps with the advancing line

Steppers, Transitions, Physics, SVGGSAPAdvancedCompressed weight: Demo 9.3 KBGSAP 28 KBFreeDownload the zip

The four steps of a checkout, Cart, Shipping, Payment and Review, over a thin line: going on, the head of the line runs to the next step in the accent colour and the ink catches it up a moment later on a spring; going back, the line winds itself in. Where the ink lands the line trembles a pixel between two ticks, like a string; the step done moves aside, takes weight and its tick draws itself. Minimal style.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Steps with the advancing line</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the checkout steps: one thin line under four names. Going on, the head of the line runs to the next tick and
         the tail catches it up on a spring; going back, the line winds itself in. The steps done take weight and a
         tick drawn by hand, the current one has its name in the accent -->
    <section class="flow" aria-label="Checkout" data-flow>
      <ol class="flow__steps" data-steps>
        <li class="step" data-step><button class="step__btn" type="button"><span class="step__lift"><svg class="step__check" viewBox="0 0 14 12" aria-hidden="true"><path d="M1.6 6.6l3.5 3.4 7.3-8.2"/></svg><span class="step__name">Cart</span></span></button></li>
        <li class="step" data-step><button class="step__btn" type="button"><span class="step__lift"><svg class="step__check" viewBox="0 0 14 12" aria-hidden="true"><path d="M1.6 6.6l3.5 3.4 7.3-8.2"/></svg><span class="step__name">Shipping</span></span></button></li>
        <li class="step" data-step><button class="step__btn" type="button"><span class="step__lift"><svg class="step__check" viewBox="0 0 14 12" aria-hidden="true"><path d="M1.6 6.6l3.5 3.4 7.3-8.2"/></svg><span class="step__name">Payment</span></span></button></li>
        <li class="step" data-step><button class="step__btn" type="button"><span class="step__lift"><svg class="step__check" viewBox="0 0 14 12" aria-hidden="true"><path d="M1.6 6.6l3.5 3.4 7.3-8.2"/></svg><span class="step__name">Review</span></span></button></li>
      </ol>

      <!-- the line: a faint track, the ink of the steps done and, in the accent, the stretch that is moving.
           The three share one path, cut by two clip rectangles; the script draws them -->
      <svg class="flow__line" aria-hidden="true" data-line>
        <defs>
          <clipPath id="flow-track"><rect data-clip="track" x="0" y="-20" width="0" height="60"/></clipPath>
          <clipPath id="flow-ink"><rect data-clip="ink" x="0" y="-20" width="0" height="60"/></clipPath>
          <clipPath id="flow-run"><rect data-clip="run" x="0" y="-20" width="0" height="60"/></clipPath>
        </defs>
        <path class="line__track" clip-path="url(#flow-track)" data-path/>
        <path class="line__ink" clip-path="url(#flow-ink)" data-path/>
        <path class="line__run" clip-path="url(#flow-run)" data-path/>
        <g data-ticks></g>
      </svg>

      <div class="flow__foot">
        <p class="flow__note" aria-live="polite" data-note><span class="roll__item">Two items in your bag</span></p>
        <div class="flow__actions">
          <button class="back" type="button" data-back>
            <svg class="back__arrow" viewBox="0 0 16 14" aria-hidden="true"><path d="M15 7H1.5M7 1.5L1.5 7 7 12.5"/></svg>
            <span class="back__text">Back</span>
          </button>
          <button class="next" type="button" data-next data-autoclic>
            <span class="next__body" aria-hidden="true"></span>
            <span class="next__text" data-next-text><span class="roll__item">Continue</span></span>
            <svg class="next__arrow" viewBox="0 0 16 14" aria-hidden="true"><path d="M1 7h13.5M9 1.5L14.5 7 9 12.5"/></svg>
          </button>
        </div>
      </div>
    </section>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

35 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “Steps with the advancing line” from Animorio and fit it into my site

What it does

On a white page four names in Archivo, Cart, Shipping, Payment and Review, each at the start of its column, and under them a very thin grey line with a tick at the start of every step and one at the end. The current step has its name and tick in blue; the ones to come are grey and light. Underneath, on the left, a line that says what is in that step (Two items in your bag, Delivery in 2 to 4 working days…), on the right Back and a black pill button, Continue. Pressing Continue the head of the line leaves at once and runs to the next tick in blue, while the black ink follows it a moment later on a softer spring: for an instant the line is a long blue stroke, which the ink shortens as it arrives. When the ink passes the middle of the step the name moves aside to the right, turns black and heavy and a tick draws itself in its place; the name of the step reached is already blue. Where the ink lands the line trembles between the two ticks, a pixel or two, and the stretches next door barely feel it. Back does the opposite: the head goes back first and the tail follows it, the tick rubs itself out, the name loses weight and turns grey again. On the last step the button says Place order: the ink runs to the tick at the end, the whole line trembles and the button becomes Keep shopping, which winds the line back to the first step. The words of the button and of the line roll, and the pill takes the width of the new words. A step done is a button that goes back to it: with the pointer on it its name widens and turns blue.

How it works

The line in steps, not pixels

The head and the tail of the line are two numbers in steps: 0 is the first tick, 4 the end. The pixels are worked out every frame from the width of the line, so a new window size halfway through a move goes unnoticed by the springs. The line is one SVG path drawn three times: the grey track, the ink and the blue stroke, each cut by a clipPath holding a rectangle, from 0 to the smaller of the two numbers for the ink, between the two for the blue.

Two springs tied together

The head goes to the chosen step on a stiff spring, HEAD_SPRING; the tail does not look at the step, it looks at the head, on a softer spring, TAIL_SPRING. And the head feels the tail: while the line is stretched, a share PULL of the tail’s force holds it back. The two springs are integrated together in steps of 1/240 of a second, with the frame step capped at 1/30: so the head arrives without a jolt, the tail catches it up, goes a pixel past and comes back.

Everything comes from the line

Names, ticks and checks have no animations of their own: every frame they are worked out from where the head and the tail are, with smoothstep. How done a step is depends on the ink between the middle and the end of its column; how current it is, on the head reaching its tick. From these two numbers come the weight and the width of the variable typeface (font-variation-settings), the shift of the name, the check (stroke-dashoffset) and the colour, mixed in CSS with color-mix between grey, blue and black. That is why going back everything undoes itself, in the same order backwards.

The string between two ticks

Every stretch between two ticks is a small damped oscillator, eleven times a second. When the tail reaches the tick it was going to, the stretch just filled (or just emptied, going back) gets a blow that grows with the distance travelled, the neighbours half and less. The path is then redrawn with half a sine between the two ticks and a little of the next one; when everything is still it is a straight line again and the loop switches off.

Buttons, words and keys

The words of the line and of the button roll: the old ones leave upwards and fade, the new ones rise a beat later. The pill takes the width of the new words with back.out when it grows and with no bounce when it shrinks, and a ResizeObserver keeps it right while the wdth axis widens on hover. The steps done are buttons with their own aria-label, the current one has aria-current="step", Back stays reachable with aria-disabled; the right and left arrow keys go on and back.

The preview’s tour

A click made by a script (isTrusted false, the preview’s) does not take a step but starts a GSAP timeline that shows every state: two steps on, one back, on again to the last, the order placed and the rewind to the first step. A real click, a touch or a key stops it where it is.

Values to change

NameFileDefaultWhat it changes
NOTESscript.js['Two items in your bag', …]The line under the steps: one for each step and one more for the order placed.
ACTIONSscript.js['Continue', 'Place order', 'Keep shopping']The button’s words: on the way, on the last step and once the order is placed.
HEAD_SPRINGscript.js[700, 0.85]The spring of the head of the line: how stiff it is and how damped (1 means no overshoot).
TAIL_SPRINGscript.js[130, 0.8]The spring of the tail, pulled by the head: how stiff and how damped. Softer, the line stretches further.
PULLscript.js0.25How much the tail holds the head back while the line is stretched (0 to 1).
PLUCKscript.js2How many pixels the line trembles between two ticks where the tail lands.
WEIGHTscript.js[360, 540, 680]The weight of a name: a step to come, the current step, a step done (100 to 900).
WIDTHscript.js[96, 106, 100]The width of the typeface (wdth axis) in the same three cases (62 to 125).

Try them in the Playground

When to use it

For the checkout of an online shop, a booking, a sign-up or a long form split into three, four or five steps: wherever you need to say calmly how far along you are, with no icons or circles, and make each step forward feel like something done.

On phones

The same animations with the sizes adapted: the four steps stay in a row across the screen, with slightly smaller names; the line of text goes on top and the two buttons underneath, Back on the left and the pill on the right. A touch goes on, back, and back to a step done by touching it.

With reduced motion

No springs, no running and no trembling: the line, the names and the checks are at once in their new place, and the steps and the line come back with a short fade; the words of the line and of the button change with a fade instead of rolling. On opening everything fades in.

Pitfalls

  • The blow on the string taken from the tail’s speed on landing was almost nothing (a fifth of a pixel), because a spring arrives slowly: the blow grows with the distance travelled, and so it shows.
  • Going back three steps the names in between all turned blue at once, because the accent followed the furthest point of the line: now it follows only the head, and the steps it leaves turn grey again.
  • The pill shrinking with back.out went for a moment below the width of the words and cut them: it grows with the bounce, shrinks without, and only once the old word has left.
  • The two lines rolling together, one above the other, were both half visible at the same instant: the old one leaves quickly and fades, the new one rises a beat later.
  • On opening the ticks sprang up before the track reached them: each one now starts when the track gets there.
  • On the phone the line under the steps disappeared: with the foot in a column, flex: 1 gave it no height. There it has flex: none.

Libraries

GSAP 3.15.0, Archivo (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 9.3 KB; the libraries: GSAP 28 KB.

Testing

Tested on 16 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All components