Skip to content
animorio
IT
All components

The question-mark accordion

Accordions, SVG, PhysicsGSAPAdvancedCompressed weight: Demo 8.7 KBGSAP 28 KBFreeDownload the zip

The questions of a puzzles-by-post subscription, each with a pencil question mark in the margin. Opening one, the question mark straightens up into an exclamation mark in ink: the hook unbends, the dot drops like a weight and pulls the answer down, the questions below ride down with it and it lands with a squash and a little bounce. Closed again, the stroke is reeled back up and curls into a question once more.

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 question-mark accordion</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/schibsted-grotesk@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the questions of a puzzles-by-post subscription, one answer open at a time. In the margin of each question a
         pencil question mark; opening it, the question mark straightens into an exclamation mark in ink, its dot drops
         like a weight and pulls the answer open, and the questions below ride down on it. The marks are drawn by the
         script -->
    <div class="faq" data-faq>
      <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">When does a new puzzle arrive?</span></button></h3>
        <div class="faq__a" id="faq-a1" role="region" aria-labelledby="faq-q1" hidden><div class="faq__inner"><p>Every other Friday, in a flat envelope that fits any letterbox: one big puzzle for the weekend and three small ones for the week.</p></div></div>
        <svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
      </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">What if I get stuck?</span></button></h3>
        <div class="faq__a" id="faq-a2" role="region" aria-labelledby="faq-q2" hidden><div class="faq__inner"><p>Every puzzle comes with three sealed hints, each a little less subtle than the last. Open them in order, or hold out: the solutions arrive with the next pack.</p></div></div>
        <svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
      </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">How hard are the puzzles?</span></button></h3>
        <div class="faq__a" id="faq-a3" role="region" aria-labelledby="faq-q3" hidden><div class="faq__inner"><p>Each pack starts gentle and ends tough. Most people crack the big one in an evening, alone or round a table with friends.</p></div></div>
        <svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
      </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">Can I pause my subscription?</span></button></h3>
        <div class="faq__a" id="faq-a4" role="region" aria-labelledby="faq-q4" hidden><div class="faq__inner"><p>Yes, for up to three months, from your account page. Paused packs are never charged, and you can start again whenever you like.</p></div></div>
        <svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
      </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">Can I give it as a gift?</span></button></h3>
        <div class="faq__a" id="faq-a5" role="region" aria-labelledby="faq-q5" hidden><div class="faq__inner"><p>Choose three, six or twelve packs and add a note. The first one goes out in a plain envelope, so the surprise holds.</p></div></div>
        <svg class="faq__mark" aria-hidden="true" focusable="false"><g><path class="faq__stroke"/><rect class="faq__dot"/></g></svg>
      </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.

24 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 question-mark accordion” from Animorio and fit it into my site

What it does

On a white background, alone in the middle, a list of five questions split by thin rules: When does a new puzzle arrive?, What if I get stuck?, How hard are the puzzles?, Can I pause my subscription?, Can I give it as a gift?. In the right-hand margin of each one stands a large grey question mark, as if written in pencil. On arrival the question marks are written one after another, from the tip of the hook down to the stem, and their dot pops in; then the second question opens. Opening a question, its question mark straightens up: the hook unbends upwards while the colour goes from pencil grey to ink blue, the dot comes away and falls with gravity, pulling the answer down, which shows line by line as the dot passes it; the questions below ride down with it. The dot lands on the baseline of the answer's last line, squashes and makes a little bounce, which the questions below follow; what is left is an exclamation mark as tall as the question and its answer, wide at the top and thin by the dot. While it falls the stroke gets a little thinner, as if stretched. Opening another one, the open one closes at the same time: the stroke goes back up like a tape measure reeling in, the hook forms again while the stem is still long, like a walking stick, and curls a touch past its shape before settling. Under the mouse, or with the keyboard's focus, a question mark leans towards its question, curious, and gets darker; pressed, it squashes.

How it works

A question mark made of angles

The question mark's line, a Bézier curve that goes from the stem into a 202-degree arc of a circle, is resampled into 40 equal steps, and only the direction of each step is kept. It is drawn on every frame starting from the bottom of the stem and going up one step at a time: so the shape is not a fixed drawing but a list of angles to rebuild it from.

Straightening up

To straighten it, each direction moves towards the vertical by multiplying its difference by (1 − p), and the hook shortens from 33.8 to 16 units: at p = 1 the line is straight and reaches the top of the box. p sits on a quick spring (STRAIGHTEN) when opening and on a slower, springier one when closing, which takes the hook a little past its shape. The mark is not a stroke but a filled shape with a width at every point: even along the question mark, a wedge in the exclamation mark (0.62 strokes by the dot, 1.45 at the top), and thinner when it falls fast.

The falling dot

Opening, the dot leaves with a push of 280 pixels a second and speeds up with GRAVITY; when it reaches the answer's height it bounces back with a share of its speed (BOUNCE) and squashes from its bottom, on a spring that makes it square again. The answer's height is exactly its drop: the answer opens from the top line by line, and the questions below, which sit in the page's normal flow, ride down and bounce with it and never overlap. The sums are done in steps of 1/240 of a second, with the frame step capped at 1/30.

On the baseline

The dot lands on the baseline of the answer's last line, not on its edge: the line is measured with a probe of no size, an inline-block span added at the end of the text and taken away at once, and the dot drops in proportion to the answer, so it gets there when the answer is fully open. The sizes are measured again when the width changes and when the fonts arrive.

Closing, keyboard and rest

Closing, the answer goes back to zero on a spring with no bounce (FOLD), the text fades in 0.14 seconds and below half a pixel the answer is hidden again. The questions are buttons inside an h3 with aria-expanded and aria-controls, the answers regions with aria-labelledby; arrows, Home and End go from one question to another, and the focus ring hugs the question. The marks' colour is a color-mix in CSS between pencil, ink and black, driven by two variables the script writes. The loop runs on GSAP's ticker only while something moves; the list has a fixed height, so its top never moves. The question after the open one has data-autoclic for the preview.

Values to change

NameFileDefaultWhat it changes
FIRSTscript.js1The question open when the page opens, from 0; −1 for none.
ONLY_ONEscript.jstrueOpening a question closes the open one; with false many can be open.
GRAVITYscript.js5200How hard the dot falls, in pixels a second squared: the higher, the quicker the answer opens.
BOUNCEscript.js0.3How high the dot bounces when it lands, as a share of its speed (0 not at all).
STRAIGHTENscript.js0.22The seconds the question mark takes to straighten up (curling back takes nearly twice as long).
FOLDscript.js0.32The seconds an answer takes to fold back up.
TILTscript.js12How far a question mark leans in under the mouse, in degrees; 0 not at all.
STROKEscript.js4The thickness of the marks, in pixels.

Try them in the Playground

When to use it

For the frequently asked questions of a service, a subscription, a game or an event, where the site has a lively, slightly witty tone: the exclamation mark in the margin shows from afar which answer is open and how long it is, and you can see the question finding its answer.

On phones

The list is at most 560 pixels wide and on a phone narrows to the screen, with a 16 pixel margin each side (358 on a 390 phone). Closed rows are 76 pixels tall on a computer and 64 on a phone, the questions go down to 18 pixels and the marks to 28 pixels tall, in a 48 pixel margin. Taps open and close like clicks, and a finger pressing a question squashes its question mark.

With reduced motion

No fall, no bounces and no leaning: the answer opens and closes at once, the question mark becomes an exclamation mark straight away and the answer's text fades in. When the page opens the list fades in with the second question already open; under the mouse the mark only gets darker.

Pitfalls

  • The first version only moved the bottom of the stem: the whole mark slid down in one piece, with a short exclamation mark at the bottom of the answer. The straight part of the stem has to grow as much as the drop, so the top stays by the question.
  • Straightened, the exclamation mark leaned by thirteen degrees: the direction of the curve's first step is not quite vertical, because the Bézier turns right at once. The reference is now the exact vertical, not the first step.
  • A long, even line with a small dot at its end read as a scrollbar, not an exclamation mark. It became a wedge, wide at the top and thin by the dot, and the dot grows by 30 per cent.
  • Landing on the answer's edge, the dot ended up about fifteen pixels below the last line, in its margin: it now lands on the baseline, measured with a probe, and the mark's drop is in proportion to the answer's.
  • Gravity starts from zero speed, like an ease-in: in the first frames almost nothing moved and the click seemed late. The dot leaves with a push of 280 pixels a second.
  • Closing a question with the mouse over it, the mark leaned at once while its stem was still long, and its top swept sideways. Now it only leans once it is a short question mark again.
  • A spring with no bounce reaches zero very slowly: the closed answer stayed visible to screen readers for almost a second. Below half a pixel it closes at once and is hidden again.
  • The focus ring round the whole button went outside the list and touched the answer below: it now hugs the question's text, and the question mark leans as under the mouse.

Libraries

GSAP 3.15.0, Schibsted Grotesk (font variabile) 5.3.0.

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

Testing

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