Skip to content
animorio
IT
All components

The steps that share the line

Steppers, Text, Transitions, PhysicsGSAPAdvancedCompressed weight: Demo 10 KBGSAP 28 KBFreeDownload the zip

The four steps of a checkout, Bag, Delivery, Payment and Review, set large in a row that always fills the same measure to the pixel: the current step takes the room, wide and heavy, and the others squeeze on the width axis to make it. Going on, the room moves to the next name on a spring and the weight follows a moment later; the steps done stay black but light. Thin rules part the steps, and the two around the current one are in the accent colour. 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>The steps that share the line</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/standard.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-stage>
    <!-- the checkout steps: four names that share one measure to the pixel. The current step takes the room, wide
         and heavy; the others squeeze on the width axis to make it. Going on, the room flows to the next name on a
         spring and the weight follows a beat later; the steps done stay in ink, light. Thin rules mark where one
         step ends and the next begins; the script places them, and the two around the current step are in the accent -->
    <section class="steps" aria-label="Checkout" data-steps>
      <div class="steps__row">
        <ol class="steps__list" data-row>
          <li class="step" data-step><button class="step__btn" type="button"><span class="step__mask"><span class="step__name">Bag</span></span></button></li>
          <li class="step" data-step><button class="step__btn" type="button"><span class="step__mask"><span class="step__name">Delivery</span></span></button></li>
          <li class="step" data-step><button class="step__btn" type="button"><span class="step__mask"><span class="step__name">Payment</span></span></button></li>
          <li class="step" data-step><button class="step__btn" type="button"><span class="step__mask"><span class="step__name">Review</span></span></button></li>
        </ol>
        <div class="steps__rules" aria-hidden="true" data-rules></div>
      </div>

      <!-- what is in the current step: it travels with the step's left edge -->
      <p class="steps__note" aria-live="polite" data-note><span class="note__track" data-note-track><span class="roll__item">2 items · $148.00</span></span></p>

      <div class="steps__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>
    </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.

36 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 “The steps that share the line” from Animorio and fit it into my site

What it does

On a white page four names in Roboto Flex, Bag, Delivery, Payment and Review, side by side, parted by five very thin vertical rules. The current step is black, heavy and wide; the others are very narrow and thin, grey for the ones to come and black for the ones done, and together they always fill the same measure, from the first rule to the last. The two rules around the current step are vermilion. Underneath, a small line says what is in that step (2 items · $148.00, Arrives Thursday, by 6 pm…) and travels with the step’s left edge; below it Back and a black pill button, Continue. Pressing Continue the room moves from the current name to the next on a spring: the first squeezes while the second widens, goes a breath past and settles, and the rules slide with them; the weight comes a moment later, so the new name first opens and then fills in, while the one left behind gets lighter and stays black. When the room settles the two vermilion rules feel the blow and stretch a little. Back does the opposite. On the last step the button says Place order: once the order is placed the four names, all light and black, share the measure in almost equal parts, every rule feels the blow one after the other and the button becomes Keep shopping, which brings the room back to the first step across the others. A step done is a button that goes back to it: with the pointer on it, it takes weight and turns vermilion without leaving its column.

How it works

A table of widths, measured once

Once the typeface is in (document.fonts.load), every name is written 154 times in a hidden box, at 22 values of the wdth axis and 7 weights, at 100 pixels; the widths are read all together, in one layout of the page, and kept divided by the size of the type, because they grow with it. The opsz axis stays fixed at 96 both in the table and on the page: left free, it would change the widths with the size of the text.

The room and the weight, two springs in steps

Where the room is and where the weight is are two numbers in steps (0 is Bag, 4 the order placed), each on its own spring, integrated together in steps of 1/240 of a second with the frame step capped at 1/30. The room’s spring, ROOM_SPRING, is stiffer and goes a breath past; the weight’s, WEIGHT_SPRING, is softer and does not bounce: the new name first opens and then fills in. How current a step is is a bell one step wide around the number; how done it is, a smoothstep.

The row that fills to the pixel

Every frame each name says how wide it would like to be, from the table, at its natural axis (WIDE for the current step, NARROW for the others) and its weight. The measure, less the room of the steps, is shared out in proportion; then for each name the table gives, with a linear interpolation, the wdth value that makes it exactly as wide as its share. A short name can run out of axis (Bag, at its widest, is still short): it keeps its widest and the others share what is left, until the sums come out.

The rules and the blow

The vertical rules stand where one step ends and the next begins, so they travel on the same spring with no animations of their own; their colour is mixed in CSS with color-mix between grey and vermilion by how much they frame the current step. When the room reaches the step it was going to, the two rules around it stretch by BLOW and come back on a short elastic; once the order is placed they all do, one after the other. The blow goes on the rule’s inner element, because the script moves the outer one.

The travelling line, the buttons and the keys

The line under the steps follows the left edge of the current step, worked out from the same columns, and never leaves the measure; its words and the button’s roll, the old ones leave first and the new ones rise after. The pill takes the width of the new words. 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. A step done under the pointer takes HOVER_WEIGHT of weight, and the solver narrows it on the wdth axis so that it stays in its column.

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 way back to the first step. A real click, a touch or a key stops it where it is; when nothing moves, the frame loop switches off.

Values to change

NameFileDefaultWhat it changes
NOTESscript.js['2 items · $148.00', …]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.
ROOM_SPRINGscript.js[230, 0.74]The spring that hands the room from step to step: how stiff it is and how damped (1 means no overshoot).
WEIGHT_SPRINGscript.js[95, 0.9]The spring of the weight, which comes after the room: how stiff and how damped. Softer, the new name stays open and thin longer before it fills in.
WEIGHTscript.js[300, 820, 230]The weight of a name: a step to come, the current step, a step done (100 to 1000).
WIDEscript.js128The width of the typeface (wdth axis) the current step aims for, before the measure is shared out (25 to 151).
NARROWscript.js44The same for the other steps (25 to 151): lower, the more room the current step takes.
HOVER_WEIGHTscript.js160How much weight a step done takes under the pointer: it stays in its column all the same.
MEASUREscript.js620The widest the row may be, in pixels.
MAX_SIZEscript.js48The biggest the names may be, in pixels: once there, it is the measure that comes in to meet the names.
BLOWscript.js0.16How much the two rules around the step stretch when the room lands on it (0 to 0.6).

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 with short names: wherever the steps are also the page’s headline and the type alone has to say how far along you are, with no numbers, icons or circles.

On phones

The same animations with the sizes adapted: the row takes the width of the screen and the names get smaller just enough to fill it, still to the pixel; the line under the steps stays inside the measure even when the current step is the last, and the two buttons go to the two sides, 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 and no blow on the rules: the room, the weight and the rules are at once in their new place and the row of steps comes back with a short fade; the words under the steps and in the button change with a fade, the old ones first and the new ones after. On opening everything fades in.

Pitfalls

  • The table is measured at 100 pixels and the names sit at 44: with the optical size left to the browser (font-optical-sizing) the opsz axis would change the drawing and the width of the letters between the two sizes, and the row would not add up. That is why opsz is fixed at 96 both in the table and on the page.
  • Bag, the shortest name, given the current step’s room reached the end of the wdth axis and the row came up some fifteen pixels short: a name that runs out of axis keeps its widest and the others share the rest.
  • Before the typeface arrived the table was made with the fallback font and the names spilled out of the measure: the row stays hidden until document.fonts.load has finished (with a fallback after 2.5 seconds), and the table is made again once every font is in.
  • With reduced motion the two lines under the steps crossed as they faded and for a moment read one on top of the other: the old one fades first, the new one comes after.
  • The line under the steps, which follows the current step, ran out of the measure on the right at the last step: it stops where its end meets the last vertical rule.

Libraries

GSAP 3.15.0, Roboto Flex (font variabile) 5.3.0.

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

Testing

Tested on 18 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