
The menu that opens from its button
Menus, Transitions, Text
Free
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.
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>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
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 siteA 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.
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.
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.
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.
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 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.
| Name | File | Default | What it changes |
|---|---|---|---|
LINES | script.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). |
YEARS | script.js | ['2025', '2026'] | The column heads, one for each figure of a line. |
TOTAL | script.js | 'Operating profit' | The name of the total, worked out from the lines. |
THOUSANDS | script.js | false | When the page opens: in euros (false) or in thousands of euros (true). |
DECIMALS | script.js | 2 | When the page opens: 0 or 2 decimals. |
REST_WEIGHT | script.js | 380 | The weight of the figures (100 to 1000). |
HOVER_WEIGHT | script.js | 660 | The weight of a row’s figures under the pointer. |
TOTAL_WEIGHT | script.js | 600 | The weight of the total’s figures. |
SLIDE | script.js | [170, 20] | The spring of every character that slides to its new place: how stiff, and how much it is held back. |
COLUMNS | script.js | [150, 19] | The spring of the columns’ widths. |
ROLL | script.js | 0.42 | The seconds a character takes to roll to its new figure. |
RIPPLE | script.js | 0.014 | The seconds between a character rolling in or out and the next one, going out from the point. |
ROW_DELAY | script.js | 0.04 | The seconds between a row and the next. |
COLUMN_DELAY | script.js | 0.1 | The seconds between one column and the other. |
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.
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.
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.
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.
Tested on 16 October 2026
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.
With your account’s email and password.
or with your email
You’re signed in as .
With your account, you can download the zip of every animation and every pack, for free.