Skip to content
animorio
IT
All components

The leading accordion

Accordions, Text, TransitionsGSAP, CSS onlyAdvancedCompressed weight: Demo 8.6 KBGSAP 28 KBFreeDownload the zip

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.

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 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>

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.

28 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 leading accordion” from Animorio and fit it into my site

What it does

On a white background five questions in Roboto Flex, split by thin grey lines, each with a plus made of two hairlines on the right: What is the smallest run you print?, Which papers can I choose?, Can you match a brand colour?, How long does an order take?, Do you ship abroad? Under each shut question sits its answer, with no leading: all its lines stand on the same baseline, squashed to a tenth of their height, narrow on the width axis, heavier on the grade axis and with the spaces between words closed up. What you get is a clean black two-pixel rule made of text, frayed a little at the right where the lines end one after another; no letter can be read. Under the pointer the rule opens into three or four thinner rules, four pixels apart, on a spring, and you see it is made of lines; the question takes a little weight. On a click the leading grows live: the farthest line leaves first, then the others, each runs a pixel past its place and comes back, and as they part the lines swell in height as far as the room between them allows, so they never cover each other; meanwhile they widen to their full width, their strokes lighten and the words take back their spaces, until the answer can be read. The open question turns blacker and a little narrower, and the two hairlines of the plus turn and lie on each other in a vermilion minus. Opening another question, the open one shuts at the same time: its lines head back to the rule, the nearest first, flattening as they get close, each goes a pixel through and back, and the minus turns on the same way round into a plus. When the page opens the questions arrive one after another, the rules are printed line after line and the first answer opens.

How it works

The answer cut into lines

Each 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.

The rule made of text

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.

The leading that grows

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.

As tall as the room it has

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, the pointer, the keys

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.

Values to change

NameFileDefaultWhat it changes
FIRSTscript.js0The question open when the page opens, from 0; −1 for none.
ONLY_ONEscript.jstrueOpening a question shuts the one that is open.
LEADINGscript.js1.6The leading of an open answer, in times the type size.
OPENscript.js0.72The seconds a line takes to reach its place (shutting takes a little less).
STAGGERscript.js0.07The seconds between one line leaving and the next.
OVERSHOOTscript.js1The pixels a line runs past its place before it settles back.
SQUASHscript.js0.11How flat a shut line is, in times its height: all of them together make the rule.
SHUT_INKscript.js1How dark each flat line is: on top of one another they make an even rule.
SHUT_WIDTHscript.js45The width axis of the shut lines, narrower than the open ones (25 to 100).
SHUT_OPTICALscript.js32The optical size of the shut lines: Roboto Flex only narrows far at bigger sizes.
SHUT_GRADEscript.js150The grade axis of the shut lines: heavier strokes, same width (0 to 150).
PEEKscript.js4The pixels the flat lines part under the pointer, line after line.

Try them in the Playground

When to use it

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.

On phones

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.

With reduced motion

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.

Pitfalls

  • The first shut state kept the lines on top of one another at full size, in a light ink that added up: in the poster it read as broken text, a misprint, and text covered other text. Now the shut lines are squashed to a tenth on their own baseline: a clean rule, with no letters that can be read.
  • Just squashed, the lines made a dotted rule, broken at every space: with full ink, the spaces closed to −0.18 em and the lines filling each other’s gaps the rule is nearly even.
  • Swelling the lines on a timing of their own, halfway through opening two lines still close together covered each other: the height now comes from the room between a line and its neighbours, so they cannot cover each other at any moment. The parting under the pointer does not count, otherwise the lines under it turned into tiny, almost readable text.
  • The wght axis will not do to thicken the shut lines: measured, at weight 700 a line gets 6% longer and on a phone the rule would run out of the column. The strokes thicken on the GRAD axis, which keeps the words just as long, to the pixel.
  • At 16 pixels Roboto Flex’s width axis hardly works (from 100 to 40 the line got only 10% shorter): with the optical size raised to 32 the shut lines narrow by a quarter, and as they open you can see them widen.
  • The preview’s round left the lines parted by a few pixels: the pointer’s spring lasted longer than the opening and pulled them back up. Opening now stops the pointer’s spring; and with a 25.5-pixel leading the questions fell on half pixels and the pluses looked of different weights, which is why the leading and the list’s height are rounded to the pixel.

Libraries

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.

Testing

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