
The menu that opens from its button
Menus, Transitions, Text
Free
In the minimal style: the catalogue search of a small type foundry. As you type, in the names that match the letters found take weight one after another, on a measured spring, and each one narrows just enough to keep its width: the names never shift sideways. The entries that no longer match thin out to a hairline and leave, the others close up on real springs, and the count is in tabular figures that roll.
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 search that weighs the letters</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/standard.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the catalogue search of a small type foundry. Typing, the letters found in every name take weight one after
another, while each letter narrows just enough to keep its width: the names never move sideways. The rows
that stop matching thin out and leave, the others ride on springs to their new places. The rows are made by
the script from the list at the top of script.js -->
<div class="weigh" data-search data-autoclic>
<div class="weigh__field" data-field>
<svg class="weigh__lens" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.5 4.75a5.75 5.75 0 1 0 0 11.5a5.75 5.75 0 1 0 0-11.5zM14.75 14.75l4.75 4.75"/></svg>
<input class="weigh__input" id="weigh-input" type="text" inputmode="search" enterkeyhint="search" role="combobox" aria-label="Search typefaces" aria-autocomplete="list" aria-expanded="true" aria-controls="weigh-list" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" placeholder="Search the catalogue" data-input>
<span class="weigh__caret" aria-hidden="true" data-caret></span>
<span class="weigh__count" aria-hidden="true"><span class="weigh__num" data-num></span><span class="weigh__unit" data-unit>typefaces</span></span>
<button class="weigh__clear" type="button" aria-label="Clear the search" tabindex="-1" data-clear>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 8l8 8M16 8l-8 8"/></svg>
</button>
<span class="weigh__rule" aria-hidden="true"></span>
</div>
<div class="weigh__room" data-room>
<span class="weigh__band" aria-hidden="true" data-band></span>
<ul class="weigh__list" id="weigh-list" role="listbox" aria-label="Typefaces" data-list></ul>
<div class="weigh__none" data-none hidden>
<p class="none__word" aria-hidden="true"><span class="none__letters" data-none-letters></span><span class="none__strike" data-strike></span></p>
<p class="none__what">No typeface in the catalogue matches.</p>
<p class="none__try">Try <span data-tries></span></p>
</div>
</div>
<p class="sr" role="status" aria-live="polite" data-said></p>
</div>
</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.
57 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 search that weighs the letters” from Animorio and fit it into my siteOnce the face is loaded, the script sets every letter of the catalogue at a grid of 9 weights by 9 widths (the wght axis from 100 to 900, the wdth axis from 62 to 125) in a hidden box and measures its width with getBoundingClientRect, in ems. Kerning is off (font-kerning: none), so each letter’s width is its own and the sum gives the whole name.
For any weight holdWidth() interpolates the row of the grid between the two nearest weights and solves the width on the wdth axis that keeps the letter as wide as at rest (REST at wdth 100). It does so on every frame of the GSAP tween carrying the weight, so also in the spring’s overshoot: measured in the browser, the name stays the same width to a hundredth of a pixel.
match() looks for the letters typed, in order, inside the name with a small dynamic programme: letters side by side, at the start of a word and early in the name score more, a long gap scores less. It keeps the best placement, and those are the letters that take weight, one after another every STAGGER seconds, on a damped spring curve written as a GSAP ease function.
The rows are positioned absolutely and each has a real spring (the stiffness and damping of SPRING, the step capped at 1/30 of a second and split into steps of 1/240), which keeps its speed if the target changes while it moves. They stay in the catalogue’s order: a letter more only takes rows away and the others close up, a letter less brings them back. The ones leaving thin out to FAINT and fade in 0.16 seconds; the ones staying start a moment later, and new rows wait for the others to have gone past. The loop stops when everything is still.
The field has role combobox, aria-expanded and aria-activedescendant on the row picked, which has role option and aria-selected; the arrows move the band (on a spring too), Enter picks, Escape empties, and a hidden aria-live line says how many typefaces there are. The digits of the count change one by one from the right, rolling up or down. With no results the word typed loses weight letter by letter and a line strikes it; the words in TRIES replace it in the field.
A click from a script (detail 0, the library’s preview) starts a GSAP timeline about 10 seconds long with a pretend caret and without taking the focus: it types ser, goes down a row, picks, types mo, then zq which finds nothing, and finally Escape brings everything back to rest. A real touch or key stops it at once.
| Name | File | Default | What it changes |
|---|---|---|---|
TYPEFACES | script.js | [['Esker Sans', 'Sans', 12], ['Ashlar Grotesk', 'Sans', 9], …] | The catalogue: name, class and number of styles, the most licensed first. |
SHOWN | script.js | 6 | How many typefaces the list shows at most. |
REST | script.js | 300 | The weight of the names at rest (100 to 900). |
HIT | script.js | 780 | The weight a letter found takes (100 to 900). |
FAINT | script.js | 110 | The weight of the names that leave and of a word that finds nothing. |
STAGGER | script.js | 0.07 | The seconds between one letter found and the next taking weight. |
SETTLE | script.js | 0.6 | The seconds a letter takes to take its weight and settle. |
SPRING | script.js | [420, 32] | The spring that carries the rows to their places: stiffness and damping. |
TRIES | script.js | ['serif', 'mono'] | The words offered when nothing matches. |
For the search of a type foundry, a catalogue, a shop with few products, a product’s documentation or a command palette: wherever the names are short, you search as you type and it helps to see at once which letters found the result. The search() function is the only one to change to ask a server for the results.
The field takes the width of the screen minus 32 pixels; the words in the field are 17 pixels (from 16 up iPhone does not zoom the page when it is tapped) and the keyboard shows the search key. The names drop to 20 pixels and the rows stay 52 tall for the finger: a tap brings the band onto the row and picks it. The drawn Enter key is not there, as a phone does not need it; everything else, weights, springs and count, is the same.
No springs and no rolling: the rows fade in and out already in their place, the band jumps from row to row, the digits of the count change with a fade and the focus rule lights up instead of drawing. The letters found change weight all together in a fifth of a second, with no wave and no overshoot, and keep their width; the line striking the word not found is already in place.
GSAP 3.15.0, Archivo (font variabile, assi wght e wdth) 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.