Skip to content
animorio
IT
Back to the library

The headline that fills the column

Text, HoverGSAP, CSS onlyAdvancedCompressed weight: Demo 8.3 KBGSAP 28 KBFreeDownload the zip

A justified three-line headline, in the minimal style: every line arrives squeezed and hairline-thin and breathes out, wider and fuller, until it fills the column to the pixel; then the last word squeezes to make room for the next, and the two always share the whole column. Point at a line and it gets lighter without leaving the measure.

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 headline that fills the column</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 column: two hairlines mark its edges; the script builds the headline inside, letter by letter, and
         stretches every line on the width and weight axes until it fills the column exactly -->
    <div class="measure" data-measure>
      <i class="guide" data-guide aria-hidden="true"></i>
      <i class="guide" data-guide aria-hidden="true"></i>
      <h1 class="head" data-head></h1>
      <p class="foot" data-foot>
        <span class="foot__brand">Kerf Foundry</span>
        <span class="foot__axes" aria-hidden="true"><span>wdth <b data-wd>100</b></span><span>wght <b data-wg>800</b></span></span>
      </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>

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.

22 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 headline that fills the column” from Animorio and fit it into my site

What it does

On ivory paper two very thin lines draw themselves from the top and mark the edges of a column. Inside, the headline «Give it room to breathe» arrives one line at a time: the letters appear from the middle outwards, squeezed narrow with hairline strokes, then the line breathes, widens and gains body letter after letter, passes the edge of the column by a whisker and comes back, stopping exactly from one line to the other. As it touches the edges, two small marks pop out of the lines at its height with a little bounce. The last line is red and it changes: «breathe» squeezes and thins against the left edge to let «stretch» in from the right, growing into the space it leaves; through the whole change the two words together are exactly as wide as the column. Then come «wander», «settle» and «breathe» again. Under the headline, on the right, the axis values of the red word (wdth and wght) count as it changes. At the end a breath runs down the headline: the lines, one after the other, turn light and wider without changing their measure, then fill up again; then the headline breathes out, every line squeezes in from its ends and fades, the edge lines withdraw and it all starts again. Pointing at a line with the mouse (or keeping a finger on it on a phone) gives that line the same breath: it gets lighter and wider, always inside the column.

How it works

A typeface that really squeezes

Roboto Flex has a width axis from 25 to 151 and a weight axis from 100 to 1000, but Fontsource's «wdth» file has the optical size axis fixed at 14, where the width barely changes (a narrow line is 80% as wide as a wide one). With the «standard» file, which keeps the opsz axis, and «opsz» 144 written in font-variation-settings, at width 25 and weight 100 the same line becomes a sliver: a fifth of its full width. Every letter is an inline-block span with its own values, written on each frame only when they change.

The right size, searched for

The column is 56% of the window wide (on a phone all of it, minus the margins). The type size is chosen by measuring every line at 100 px: big enough that every line, at the widest, reaches the column; small enough that each fits at the narrowest; as close as possible to the size that fills the column at a normal width. Then for every line and every word a binary search on the wdth axis (fifteen steps, measuring the real getBoundingClientRect) finds the width that makes it as long as the column. If the axis is not enough, letter spacing does the rest.

The breath

Every letter has two numbers from 0 to 1: b (the width, from SQUEEZE to the one that fills) and g (the weight, from 100 to WEIGHT). On the way in, the weight arrives first with power2.out and the width after it with a damped spring written as an easing function (BOUNCE sets the damping, and the spring ends exactly on 1), staggered from the middle outwards: so the line passes the column by a few pixels and comes back. On the way out the opposite, from the ends to the middle, with power3.in.

Two words, one column

For every word of the last line a table of 41 widths is measured in advance, from the word squeezed and thin to the word full. During the change a number p goes from 0 to 1 with expo.inOut: the old word gets (1 − p) of the column, the new one p, minus a space between the two that opens and closes along a sine. From the table, by interpolation, comes how wide and how heavy each must be; when the space is less than the narrowest the word can be, scaleX squashes it the rest of the way. The sum is always the column.

Lighter without leaving

For every line the width that fills the column is also searched at eleven weights between WEIGHT and the light weight (260, or 900 if WEIGHT is light): a number h from 0 to 1 picks the weight, and the right width is interpolated between the ones found. The pointer on a line takes h to 1 with a soft spring and back to 0 when it leaves; on a phone a finger held on the line does it. The same number, moved by the timeline with yoyo and staggered line by line, is the breath at the end of the round.

Values to change

NameFileDefaultWhat it changes
LINESscript.js['Give it', 'room to']The first lines of the headline: each one widens until it fills the column. Lines of similar length breathe best.
WORDSscript.js['breathe', 'stretch', 'wander', 'settle']The words of the last line, one at a time: each squeezes to make room for the next, and the last brings back the first.
WEIGHTscript.js800The weight of the filled lines, from 100 (a hairline) to 1000 (black).
SQUEEZEscript.js25The width the letters start from and go back to, on the wdth axis: 25 is the narrowest the typeface goes.
BOUNCEscript.js0.4How far a line goes past the column before it settles: 0 stops dead, 1 bounces a lot.
HOLDscript.js1.8How many seconds a word stays before the next one squeezes it out.
SPEEDscript.js1The speed of the whole animation: 2 is twice as fast, 0.5 half as fast.
LOOPscript.jstrueWhether the headline breathes out at the end and starts again. With false it stays, after the final breath.

Try them in the Playground

When to use it

For the opening headline of a site where typography is the star: a type foundry, a design studio, a magazine, a portfolio, the page of an event or of a product with a motto. It works with short sentences, a few words per line of similar length; the changing last line is good for saying several things in little space (services, adjectives, cities).

On phones

The same animation: the column is as wide as the screen minus the margins and the type is measured again to fill it. Keeping a finger on a line makes it lighter, as with the mouse; lifting it, the line fills up again.

With reduced motion

Nothing widens or moves: the edge lines and the lines of the headline fade in already full, still from edge to edge; the change of word is a fade (the old one fades out, the new one appears in its place). No final breath and no breath under the pointer; at the end everything fades and starts again.

Pitfalls

  • With Fontsource's «wdth» file Roboto Flex barely squeezed: that file has the optical size fixed at 14, where the width axis changes a line by only 20%. The «standard» file with «opsz» 144 written by hand is needed: there the same line goes from a fifth to the full width.
  • The weight changes the width too: the width that fills the column has to be searched at the final weight, and for the breath at every weight in between (eleven weights, interpolated in between: the line stays in the column within a couple of pixels).
  • The line never went past the column, even with the spring: the weight arrived after the width and its growth hid the bounce. With the weight arriving first (power2.out, shorter) the bounce of the width shows: a few pixels past the edges and back.
  • The fromTo tweens of a timeline write their starting values as soon as they are created: once the timeline was built, the exit ones left the headline full for a frame. At the end of the build a reset() empties everything again.
  • In a fromTo, a property written only in the starting values is ignored: in the reduced-motion version the edge lines stayed invisible because scaleY: 1 was only in the from. It has to be written in both.
  • A word narrower than its minimum (in the moment it comes in or goes out) cannot reach its size with the axes: there the word has an explicit width and a scaleX from the left edge, when it is already a thin sliver and the squash does not show.

Libraries

GSAP 3.15.0, Roboto Flex (font variabile, assi opsz, wght e wdth) 5.3.0.

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

Testing

Tested on 14 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 animations