Skip to content
animorio
IT
All components

The table aligned on the point

Tables, Text, TransitionsGSAPAdvancedCompressed weight: Demo 13 KBGSAP 28 KBFreeDownload the zip

In the minimal style: a small profit and loss in two columns of tabular figures, every figure hung on its decimal point. Above it two choices, the units (€ or € k) and the decimals (0 or 2): changing them, every figure is set again and stays aligned. The characters no longer needed roll away, the ones that stay glide together to their new place (going to thousands the digits cross the point by three places), the rounded ones roll to their new digit like a wheel, the new ones roll in; the columns take their new width on springs and a thin vermilion guide drops from each column’s tab stop. Pointing at a row, its figures take weight without leaving the point.

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 table aligned on the point</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the table aligned on the point: a small profit and loss in two columns of tabular figures, every figure
         hung on the decimal point. Above it two choices, the units and the decimals: changing them the figures are
         set again, still aligned on the point; the characters that stay slide to their new places, the ones that
         change roll, and the columns take their new width on springs. A row under the pointer takes weight -->
    <section class="sheet" aria-labelledby="pl-title" data-sheet data-autoclic>
      <header class="sheet__head">
        <div class="sheet__titles">
          <h2 class="sheet__title" id="pl-title">Profit and loss</h2>
          <p class="sheet__note"><span>Year to 31 December,</span> <span class="note-roll"><span class="note-roll__in" data-note>in euros</span></span></p>
        </div>

        <div class="choices">
          <div class="choice" role="radiogroup" aria-label="Units" data-group="unit">
            <span class="choice__label" aria-hidden="true">Units</span>
            <span class="choice__opts">
              <button class="opt" type="button" role="radio" aria-checked="true" data-value="1"><span class="opt__word">€</span></button>
              <button class="opt" type="button" role="radio" aria-checked="false" data-value="1000" aria-label="Thousands of euros"><span class="opt__word">€ k</span></button>
              <span class="choice__rule" aria-hidden="true" data-rule></span>
              <span class="choice__tick" aria-hidden="true" data-tick></span>
            </span>
          </div>
          <div class="choice" role="radiogroup" aria-label="Decimals" data-group="dec">
            <span class="choice__label" aria-hidden="true">Decimals</span>
            <span class="choice__opts">
              <button class="opt" type="button" role="radio" aria-checked="false" data-value="0" aria-label="No decimals"><span class="opt__word">0</span></button>
              <button class="opt" type="button" role="radio" aria-checked="true" data-value="2" aria-label="Two decimals"><span class="opt__word">2</span></button>
              <span class="choice__rule" aria-hidden="true" data-rule></span>
              <span class="choice__tick" aria-hidden="true" data-tick></span>
            </span>
          </div>
        </div>
      </header>

      <div class="board" data-board>
        <span class="board__rule" aria-hidden="true" data-top></span>
        <table class="figures" aria-labelledby="pl-title" data-table>
          <colgroup data-cols><col class="col-name"></colgroup>
          <thead><tr data-head><th class="head-name" scope="col"><span class="sr">Line</span></th></tr></thead>
          <tbody data-rows></tbody>
          <tfoot data-foot></tfoot>
        </table>
      </div>

      <p class="sr" aria-live="polite" data-live></p>
    </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.

47 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 table aligned on the point” from Animorio and fit it into my site

What it does

On a white background the title Profit and loss and under it, in grey, Year to 31 December, in euros. On the right two choices written small, Units with € and € k, Decimals with 0 and 2: the options stand on a hairline like the stops on a ruler and under the chosen one, darker and heavier, a vermilion tick stands up. Below, a real table: a thin black rule at the top, from which a vermilion tick hangs right over the decimal point of each column (the decimal tab stop of a typewriter), the heads 2025 and 2026, five rows divided by very light hairlines (Revenue, Materials, Salaries, Rent and utilities, Software and tools) and the total Operating profit, heavier, with a black rule above and the accountant’s double rule below. Costs are in brackets, in grey, and the closing bracket hangs out on the right without moving the digits. Choosing € k the note under the title rolls to in thousands of euros, the choice’s tick ducks, runs under the new option and stands up again a hair too tall; in each column a thin vermilion guide drops from the tab stop to the bottom of the table. Then, row after row and one column after the other, the characters no longer needed (the thousands comma, the last digits) roll up and vanish; the whole figure glides to the right as one piece and its digits cross the point, which stays where it is: 184,256.50 becomes 184.26, and the rounded 5 rolls up to 6 as it glides. The columns narrow on springs, the heads and the stops follow them, the guide withdraws. Back to €, the figures glide back and the new digits roll in from below one after the other, starting from the point. With 0 decimals the point and the cents roll away and the column glides right; rounding rolls the digits that change (19,540.80 becomes 19,541). With the pointer on a row its name and figures take weight on a spring, and everything stays on the same point. Still in little more than a second.

How it works

Every character has a key

A figure is not written again: it is taken apart. figure() takes the euros, rounds in whole cents (no floating point slips) and gives back one character at a time with its key, its absolute place value: d5 is the digit of the hundreds of thousands in any units (in € k it becomes the digit of the hundreds), c3 the thousands comma, p the point, ( and ) the brackets of a cost. On a change, the new character and the old one with the same key are the same element: if the digit is the same it slides, if rounding changed it it rolls; the keys no longer there go, the new ones come in.

Slots measured at the heaviest weight

Every character sits in a slot of its own, placed by the script: the digits (tabular, tnum) all get the slot of the widest, measured once at the heaviest weight a row can take (the total’s, under the pointer), and so do the comma, the point and the brackets. Inside its slot the character is centred: a row that takes weight grows bolder inside its slots and never leaves the point. The slots are counted from the point, the whole part to the left, the point and the decimals to the right, then the closing bracket hanging out.

Three beats

Every change has three beats: first the characters no longer needed roll up and fade (in half the time of a roll, each RIPPLE seconds after the one nearer the point), then the ones that stay all set off together, so the figure glides as one piece, on a damped spring with one pixel too many, and last the new ones roll in from below. Every cell starts ROW_DELAY after the one above and COLUMN_DELAY after the one beside it; the columns start in the direction of the movement, the one in front first, so a figure never runs into the next column.

In the frame of the point where it ends

The columns change width on springs (the table’s col elements, written every frame), and the heads, the stops and the guides with them. If the figures sat on the moving point they would make two journeys at two different paces and scatter: so every character lives in the frame of the point where its column will end, and every frame it is given back however far the column still has to go. On a change the characters are carried into the new frame without moving on screen: each makes one journey only, straight from its old place to its new one.

The wheel and the window

Every slot is a window with clip-path inset(0 -6px), which cuts above and below and lets the sides breathe. A digit that changes rolls like a wheel: up towards a higher digit, down towards a lower one, with back.out(1.1), a pixel past and back. The slot moves with a transform the script writes, the digits inside with GSAP’s yPercent: one element for the script, the child for GSAP.

A real table, keys and the preview

A table with a colgroup, th scope col and scope row, and for every figure a hidden text that says minus 41,380.25 euros (the slots are aria-hidden). The choices are two radiogroups with the arrows, Home and End; the rows are reached with tab and walked with the up and down arrows, and the focused row takes weight as under the pointer. An aria-live region says Figures in thousands of euros, two decimals. A click from a script on the box (the library’s preview) goes all the way round: € k, 0 decimals, €, 2 decimals, with a row taking weight, and back to how it was.

Values to change

NameFileDefaultWhat it changes
LINESscript.js[['Revenue', 184256.5, 212840], …]The lines: the name, then the figure of each year in euros (a cost is negative and is shown in brackets).
YEARSscript.js['2025', '2026']The column heads, one for each figure of a line.
TOTALscript.js'Operating profit'The name of the total, worked out from the lines.
THOUSANDSscript.jsfalseWhen the page opens: in euros (false) or in thousands of euros (true).
DECIMALSscript.js2When the page opens: 0 or 2 decimals.
REST_WEIGHTscript.js380The weight of the figures (100 to 1000).
HOVER_WEIGHTscript.js660The weight of a row’s figures under the pointer.
TOTAL_WEIGHTscript.js600The weight of the total’s figures.
SLIDEscript.js[170, 20]The spring of every character that slides to its new place: how stiff, and how much it is held back.
COLUMNSscript.js[150, 19]The spring of the columns’ widths.
ROLLscript.js0.42The seconds a character takes to roll to its new figure.
RIPPLEscript.js0.014The seconds between a character rolling in or out and the next one, going out from the point.
ROW_DELAYscript.js0.04The seconds between a row and the next.
COLUMN_DELAYscript.js0.1The seconds between one column and the other.

Try them in the Playground

When to use it

For numbers someone really reads down a column: the profit and loss in an accounting panel, a wholesale price list, a project’s cost summary, the budgets of a finance app, a small company’s results page. It suits a few rows (three to nine) and one to three columns; choosing units and decimals helps anyone moving between the detail and the overview.

On phones

At 390 pixels the two choices go under the title, Units on the left and Decimals on the right; the figures are 14.5 pixels and narrow to 86% on Roboto Flex’s width axis, the columns come closer (12 pixels apart) and long names wrap, narrowed. A tap on a row gives it weight, another tap or a tap outside the table takes it back. The animations are the same.

With reduced motion

Nothing slides and nothing rolls: changing units or decimals the figures fade out, the table is set again in place and the new figures fade back in; the choice’s tick is at once under the new option, the note changes with a fade and the guides do not drop. A row under the pointer takes weight at once, with no spring. When the page opens everything fades in.

Pitfalls

  • With the figures sitting on the moving point, every character made two journeys (its own and the column’s on its spring, which started at different moments), and halfway through a change the figures scattered and ran into the next column: now every character lives in the frame of the point where it ends, and every frame the column still on its way is taken off.
  • With the characters of a figure set off one after the other, the starts fell on different frames, and at 350 pixels a second a frame is 6 pixels: the figures fanned out. The ones that stay now all set off together; the staggered rhythm stays in the rows, the columns and the characters coming and going.
  • The columns that always started from the left ran into each other when they narrowed: the brackets of 2025 went into those of 2026. Now the column in front, in the direction of the movement, goes first.
  • A character leaving or changing while still outside its window (one change after another) went visibly through the middle of the slot: if it is out already, it is removed at once.
  • On the weight axis the width of the glyphs changes too, even in tabular figures: with slots measured at the normal weight, the row under the pointer would have left the point. The slots are measured once at the heaviest weight and the character is centred inside.
  • The vermilion guide went over the year heads: it starts below the heads and sits under the figures in the painting order.

Libraries

GSAP 3.15.0, Roboto Flex (font variabile, con gli assi della larghezza e del peso) 5.3.0.

Compressed with gzip, the three demo files weigh 13 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