
The menu that opens from its button
Menus, Transitions, Text
Free
The frequently asked questions of a small print studio, in the minimal style: a shut answer does not vanish, it becomes its own lines squashed flat and laid at the same height, a dark two-pixel rule made of text under the question. Opening it, the leading grows live: the lines come away one after another, the farthest first, and as they part they swell back to their height, widen on the typeface’s axes and settle a pixel past their place, until they can be read.
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 leading accordion</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/full.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="stage">
<!-- the questions of a small print studio. A shut answer is all its lines set on top of one another, with no
leading: one dark rule made of text under the question. Opening it, the leading grows and the lines come
apart one after another until they can be read. The script cuts each answer into its lines; the paragraph
stays for screen readers -->
<div class="faq" data-faq data-autoclic>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q1" aria-expanded="false" aria-controls="faq-a1"><span class="faq__text">What is the smallest run you print?</span><span class="faq__icon" aria-hidden="true"><span class="faq__bar"></span><span class="faq__bar"></span></span></button></h3>
<div class="faq__a" id="faq-a1" role="region" aria-labelledby="faq-q1"><p class="faq__src">Fifty copies. Below that the plates cost more than the paper, so we print you a single proof first and run the rest once you are happy with it.</p></div>
</div>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q2" aria-expanded="false" aria-controls="faq-a2"><span class="faq__text">Which papers can I choose?</span><span class="faq__icon" aria-hidden="true"><span class="faq__bar"></span><span class="faq__bar"></span></span></button></h3>
<div class="faq__a" id="faq-a2" role="region" aria-labelledby="faq-q2"><p class="faq__src">Twelve uncoated stocks, from a thin 90 gsm text paper to a 350 gsm card, all from one mill. We post a free sample pack anywhere in Europe.</p></div>
</div>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q3" aria-expanded="false" aria-controls="faq-a3"><span class="faq__text">Can you match a brand colour?</span><span class="faq__icon" aria-hidden="true"><span class="faq__bar"></span><span class="faq__bar"></span></span></button></h3>
<div class="faq__a" id="faq-a3" role="region" aria-labelledby="faq-q3"><p class="faq__src">Every ink is mixed by hand, so yes. Send us a Pantone reference or a printed swatch and we match it within a shade before the run starts.</p></div>
</div>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q4" aria-expanded="false" aria-controls="faq-a4"><span class="faq__text">How long does an order take?</span><span class="faq__icon" aria-hidden="true"><span class="faq__bar"></span><span class="faq__bar"></span></span></button></h3>
<div class="faq__a" id="faq-a4" role="region" aria-labelledby="faq-q4"><p class="faq__src">Five to seven working days from the proof you approve. When the press is free a rush job can be ready in three; just ask before you order.</p></div>
</div>
<div class="faq__item" data-item>
<h3 class="faq__head"><button class="faq__q" type="button" id="faq-q5" aria-expanded="false" aria-controls="faq-a5"><span class="faq__text">Do you ship abroad?</span><span class="faq__icon" aria-hidden="true"><span class="faq__bar"></span><span class="faq__bar"></span></span></button></h3>
<div class="faq__a" id="faq-a5" role="region" aria-labelledby="faq-q5"><p class="faq__src">Yes, flat between two boards so the corners stay sharp. Europe takes two to four days and the rest of the world about a week, always tracked.</p></div>
</div>
</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.
28 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 leading accordion” from Animorio and fit it into my siteEach answer is a real paragraph. The script sets it once in a hidden box as wide as the column, one span per word, and reads each word’s offsetTop: where it changes, a new line begins. Each line becomes an absolutely positioned div that does not wrap, moved only by a transform. The paragraph stays for screen readers, hidden from sight, and has the hidden attribute while the answer is shut. If the column changes width, the lines are cut again.
Shut, every line sits at y zero, at the same height as the others, squashed with scaleY to SQUASH (0.11) and with its transform-origin on its own baseline, measured with an empty span aligned to the baseline: together they make a two-pixel rule. The shut lines have the wdth axis at SHUT_WIDTH, the GRAD axis at SHUT_GRADE (it thickens the strokes without making the words longer), the opsz axis at SHUT_OPTICAL (in Roboto Flex the width only narrows far at bigger sizes) and word-spacing at −0.18 em, so the rule has fewer breaks; the ink is SHUT_INK.
Opening, each line goes from zero to its place, i times the leading (LEADING times the type size), plus OVERSHOOT pixels, with power3.out in OPEN seconds, then back to its place with sine.inOut. The farthest line leaves first and the others STAGGER seconds later, so no line ever crosses another. Shutting is the other way round, the nearest first and with power3.in, and each line lands a pixel through the rule and comes back.
GSAP only moves each line’s height on the page. On every frame one function gives each line the height of the room between it and its neighbours, divided by the leading (between SQUASH and 1.05): so two lines never cover each other, not even halfway, and the lines swell as the leading grows, a hair over while they run a pixel too far. From the same height come the width, grade, optical size, spaces and ink; the box’s height sits under the lowest line. The list has a fixed height, all shut plus the longest answer, so its top never moves.
The plus is two 1.5-pixel hairlines: opening, the horizontal one turns by 180 degrees and the vertical one by 90 with back.out, and they lie on each other in a vermilion minus; shutting, they keep turning the same way round. Under the pointer, or at the first touch of a finger, the rule opens into thinner rules, PEEK pixels apart, with an elastic.out: the lines move apart but stay flat. Each question is a button in a heading with aria-expanded and aria-controls; the arrows, Home and End go from one question to another.
| Name | File | Default | What it changes |
|---|---|---|---|
FIRST | script.js | 0 | The question open when the page opens, from 0; −1 for none. |
ONLY_ONE | script.js | true | Opening a question shuts the one that is open. |
LEADING | script.js | 1.6 | The leading of an open answer, in times the type size. |
OPEN | script.js | 0.72 | The seconds a line takes to reach its place (shutting takes a little less). |
STAGGER | script.js | 0.07 | The seconds between one line leaving and the next. |
OVERSHOOT | script.js | 1 | The pixels a line runs past its place before it settles back. |
SQUASH | script.js | 0.11 | How flat a shut line is, in times its height: all of them together make the rule. |
SHUT_INK | script.js | 1 | How dark each flat line is: on top of one another they make an even rule. |
SHUT_WIDTH | script.js | 45 | The width axis of the shut lines, narrower than the open ones (25 to 100). |
SHUT_OPTICAL | script.js | 32 | The optical size of the shut lines: Roboto Flex only narrows far at bigger sizes. |
SHUT_GRADE | script.js | 150 | The grade axis of the shut lines: heavier strokes, same width (0 to 150). |
PEEK | script.js | 4 | The pixels the flat lines part under the pointer, line after line. |
For the frequently asked questions of a print studio, a publisher, a magazine, a studio or a shop: wherever there are many answers and one is read at a time, and you want them to show as text even when shut. It works best with answers of two to five lines; with a single line the rule is just a normal line, a little narrower.
The list narrows to the screen with 16 pixels of margin on each side; the questions go down to 18 pixels and the answers to 15, so they run to three or four lines. The first touch of a finger opens the rule like the pointer, and the tap opens and shuts. The shut questions are 80 pixels tall.
No moving lines: the answer goes from the rule to the open text (and back) already in place, in a short fade; the plus becomes a vermilion minus straight away and the first answer is already open. Under the pointer the rule does not open.
GSAP 3.15.0, Roboto Flex (font variabile, con gli assi della larghezza, del grado e del corpo ottico) 5.3.0.
Compressed with gzip, the three demo files weigh 8.6 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.