Skip to content
animorio
IT
All components

The button that justifies itself like a column of text

Buttons, Text, HoverGSAP, CSS onlyMediumCompressed weight: Demo 5.3 KBGSAP 28 KBFreeDownload the zip

In the minimal style, on white and made only of type: the Read the essay of a small review, set on three ragged lines, one left, one right and one centred, like a typesetting proof. Under the pointer the lines slide and justify into one perfect block: every word widens on the width axis to the same measure, two margin hairlines rise at the sides and the pill tightens round the block. Pressed, the block closes into a single line and a vermilion arrow steps forward.

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 button that justifies itself</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/archivo@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the button that justifies itself: the Read the essay of a small review, on white. The words sit on three
         ragged lines, set left, right and centre like a typesetting proof. Under the pointer the three lines slide
         and justify into one perfect block, every word widened on the width axis to the same measure, and the pill
         tightens round it. Pressed, the block closes into one line and a drawn arrow steps forward -->
    <div class="just" data-just>
      <p class="just__brand">Ostrom Review</p>
      <button class="just__btn" type="button" data-btn data-autoclic aria-label="Read the essay">
        <span class="just__guide just__guide--l" data-guide aria-hidden="true"></span>
        <span class="just__guide just__guide--r" data-guide aria-hidden="true"></span>
        <span class="just__stack" data-stack aria-hidden="true"></span>
        <span class="just__single" data-single aria-hidden="true">
          <span class="just__row" data-row></span>
          <svg class="just__arrow" viewBox="0 0 24 14" data-arrow><path d="M1.5 7h19M15 1.8 20.6 7 15 12.2" data-arrow-path></path></svg>
        </span>
      </button>
      <p class="just__note" data-note>Autumn issue · 12 min read</p>
      <p class="sr" aria-live="polite" data-live></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.

16 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 button that justifies itself like a column of text” from Animorio and fit it into my site

What it does

On a white background, under the small name Ostrom Review, a tall pill with a very light grey outline; inside, in Archivo, the three words Read, the, essay on three ragged lines: the first set left, the second right, the third centred, each as wide as it is. Under the pill the small line Autumn issue · 12 min read. Under the pointer the outline turns black and the lines justify on a spring: each one slides left and widens to the same measure, first on the width axis of the typeface and, when the axis runs out, with the space between the letters; the block goes a little past and settles, two thin margin hairlines rise at the sides and the pill tightens by a few pixels round the block. Pressed, the pill gives and springs back, the three lines close in on the middle one and vanish, the pill turns low and wide and a single line arrives inside, Read the essay, word by word, with a vermilion arrow that draws itself and takes a step forward; the line below turns vermilion and says Opening the essay. After a moment the button goes back to its ragged lines.

How it works

Every word measured eight times

Once the typeface has arrived, the script measures every word at eight points of Archivo’s width axis, from 62 to 125, with a hidden element. So to give a word any width it is enough to read the table backwards: the axis value is found between two nearby measures, in proportion.

Justifying with the axis and with space

The measure of the block is the longest word times MEASURE. A single number, p, goes from 0 (ragged) to 1 (justified): for each line the wanted width is its natural one plus (measure − natural) times p. The axis gives what it can; what is missing becomes space between the letters, divided over the letters minus one, so the last letter ends right on the margin. The position too goes from the ragged one to the left margin with p.

The spring

GSAP takes p to 1 with back.out(1.7): it goes a little over, the words widen for a moment past the margin and come back. With p the two margin hairlines rise too, on a vertical scale, and the pill tightens by SHRINK pixels.

The single line

On click the lines move in towards the middle one and vanish, while GSAP takes the pill from the block’s size to the single line’s with back.out; the three words of the line come in one after the other, the arrow draws itself with the dash offset and steps forward and back. After HOLD seconds everything comes back.

Accessible

It is a real button, with the label Read the essay read out whole; an aria-live line says Opening the essay. With reduced motion the lines are justified at once under the pointer and on click the two forms swap with a fade.

Values to change

NameFileDefaultWhat it changes
LABELscript.jsRead|the|essayThe words of the button, one per line, split by |.
NOTEscript.jsAutumn issue · 12 min readThe small line under the pill.
NOTE_DONEscript.jsOpening the essayThe small line while the essay opens.
ALIGNscript.jsleft|right|centerHow the ragged lines are set at rest, one per line.
MEASUREscript.js1.16How wide the justified block is, compared with the longest word.
JUSTIFYscript.js0.6The seconds the lines take to justify.
SHRINKscript.js5How much the pill tightens round the block, in pixels.
HOLDscript.js1.3The seconds the single line stays before coming back.

Try them in the Playground

When to use it

For the button that opens a long text: the Read of an article or an essay, a blog’s Continue, a magazine’s index, the page of a publisher or a type foundry.

On phones

The measures are worked out again on the size of the type, which follows the width and height of the screen; a tap runs the whole cycle, justification included, because without a mouse there is no hover.

With reduced motion

The lines are justified at once under the pointer, with no spring; on click the block and the single line swap with a fade.

Pitfalls

  • A word’s width does not grow in proportion with the axis: that is why it is measured at eight points and the table is read, instead of working it out with a formula.
  • Letter spacing is added after the last letter too: divided over the letters minus one, the last letter ends on the margin and the extra space stays outside, invisible.
  • The measures must be taken again when the typeface arrives (document.fonts.ready) and on every resize: measured with the fallback face, the justified lines would overshoot the margin.
  • The pill’s width and height are written by the script: left to the content, it would jump on every frame while the words widen.

Libraries

GSAP 3.15.0, Archivo (font variabile) 5.3.0.

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

Testing

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