Skip to content
animorio
IT
All components

The flaring bell

Notifications, Transitions, Physics, TextGSAP, CSS onlyAdvancedCompressed weight: Demo 14 KBGSAP 28 KBFreeDownload the zip

The notification bell of an app for making music together: the bell in the button, the preview of a notification that has just arrived and the panel are the same shape. When a notification arrives the bell rings, drops out of the button and flares into a pill that says who wrote, then turns back into a bell and the badge counts one more; pressed, it flares like a skirt into the panel. Once read, the yellow rows fade from the left and the letters lose their weight right where the edge passes.

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, viewport-fit=cover">
  <title>The flaring bell</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/recursive@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the notification bell of a music studio app. The white bell in the button, the short preview of a notification
         that has just arrived and the panel are one and the same shape: script.js draws its outline at every frame
         and every measure of it (the dome, the flare of the rim, the corners, the little knob on top) rides its own
         spring, so the bell drops out of the button and flares like a skirt into the preview or into the panel, and
         gathers back into a bell. The badge rides the bell's shoulder and lands in the panel's heading. -->
    <div class="inbox" data-inbox>
      <button class="bell" type="button" aria-expanded="false" aria-controls="inbox-panel" aria-label="Notifications, 2 unread" data-bell data-autoclic>
        <svg class="bell__ghost" viewBox="0 0 56 56" aria-hidden="true"><path data-ghost/></svg>
      </button>

      <div class="sheet" data-sheet>
        <p class="peek" aria-hidden="true" data-peek><span class="peek__face" data-peek-face></span><span class="peek__text" data-peek-text></span></p>

        <section class="panel" id="inbox-panel" aria-label="Notifications" data-panel inert>
          <header class="panel__head" data-head>
            <h2 class="panel__title">Notifications</h2>
            <span class="panel__slot" aria-hidden="true" data-slot></span>
            <button class="panel__all" type="button" data-readall data-autoclic>
              <svg class="panel__tick" viewBox="0 0 20 20" aria-hidden="true"><path d="M4.5 10.6l3.4 3.3 7.6-7.7" pathLength="1" data-tick/></svg>
              <span data-readall-label>Mark all as read</span>
            </button>
          </header>

          <ol class="notes" data-list>
            <li class="note-item" data-unread>
              <button class="note" type="button">
                <span class="note__wash" aria-hidden="true"></span>
                <span class="note__face face-2" aria-hidden="true">MH</span>
                <span class="note__text">
                  <span class="note__line"><b>Mira Holm</b> mentioned you in <b>Harbour Lights</b></span>
                  <span class="note__said">“Could you take the bridge vocals tonight?”</span>
                </span>
                <span class="note__time">12m</span>
              </button>
            </li>
            <li class="note-item" data-unread>
              <button class="note" type="button">
                <span class="note__wash" aria-hidden="true"></span>
                <span class="note__face note__face--app" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M5 10.5v3M8.5 7v10M12 9v6M15.5 5v14M19 9.5v5"/></svg></span>
                <span class="note__text">
                  <span class="note__line">Your mix of <b>Low Tide</b> is ready</span>
                  <span class="note__said">WAV · 48 kHz · 4 min 12 s</span>
                </span>
                <span class="note__time">40m</span>
              </button>
            </li>
            <li class="note-item">
              <button class="note" type="button">
                <span class="note__wash" aria-hidden="true"></span>
                <span class="note__face face-4" aria-hidden="true">KM</span>
                <span class="note__text">
                  <span class="note__line"><b>Kofi Mensah</b> approved <b>Night Ferry</b></span>
                  <span class="note__said">“Sounds finished to me. Off to mastering.”</span>
                </span>
                <span class="note__time">3h</span>
              </button>
            </li>
            <li class="note-item">
              <button class="note" type="button">
                <span class="note__wash" aria-hidden="true"></span>
                <span class="note__face face-1" aria-hidden="true">IM</span>
                <span class="note__text">
                  <span class="note__line"><b>Ines Moreau</b> invited you to <b>Salt Flats</b></span>
                  <span class="note__said">“Four songs, we start on Monday.”</span>
                </span>
                <span class="note__time">1d</span>
              </button>
            </li>
          </ol>
        </section>
      </div>

      <span class="badge" aria-hidden="true" data-badge><span class="badge__num" data-num><span></span></span><span class="badge__word" data-word>new</span></span>
      <p class="sr" role="status" aria-live="polite" aria-atomic="true" 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.

73 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 flaring bell” from Animorio and fit it into my site

What it does

On a black background a dark round button 56 pixels tall, with a warm white bell in it and on its shoulder a yellow badge with the number of unread notifications, 2. With the mouse over it the bell swings a little and lets its rim down a touch, as if it were about to open. A moment later a notification arrives: the bell rings (it swings from its knob, the rim widens and narrows, the clapper lags behind), then drops out of the button and flares into a white pill under it, with the initials and Tomás left a note on Low Tide; the button keeps the bell’s grey print. After almost two seconds the pill gathers itself and rises back into the button as a bell, and as it lands the badge goes from 2 to 3 with rolling figures. Pressing the button the bell drops again and flares like a skirt: first the bottom and the rim fall, going a little past and back, then the top widens, the dome becomes the panel’s straight edge and the knob becomes the point that shows the button. The badge slides from the shoulder to the Notifications heading and becomes 3 new; the Mark all as read line and five notifications come in, the unread ones on a pale yellow ground and in a heavier weight. Mark all as read reads the rows one after the other: the yellow fades from the left with a soft edge and the letters get lighter right where the edge passes, while the badge counts down 3, 2, 1 and goes; the button says All caught up with a tick that draws itself. A row can also be read on its own, by tapping it. A notification that arrives with the panel open slides in at the top and the oldest folds away at the bottom; the print in the button gives a small ring. Closed (with the button, with Esc or tapping outside), the panel gathers itself and rises back into the button as a bell.

How it works

One shape

The bell, the pill and the panel are a single path, drawn again at every frame: a top that can be a dome or a straight edge with rounded corners, two sides that are a cubic Bézier curve, a rim, a knob and a clapper. With straight sides the control points sit at a third and two thirds of the chord; with the flare (fl = 1) the first goes straight down from the shoulder and the second sits near the rim and inwards, so the side opens like a bell’s. The knob and the clapper are drawn the same way round as the outline, and with the nonzero rule they join it.

A spring for every measure

Every measure of the path (where the top is, where the bottom is, how wide the top and the rim are, the two corner radii, the flare, the knob) is a damped spring with its own stiffness and damping, x'' = −ω²(x − target) − 2ζω·x', in steps of 1/240 of a second with the frame’s step limited to 1/30. Opening, the bottom and the rim are quick and bounce (ζ comes from BOUNCE), the top and the flare come later: for a moment the shape is a tall bell, then a skirt, then the panel. Closing, the top narrows first, so the bell forms again on its way up. When every spring is still the loop sleeps.

The path cuts the sheet

All the content lies on a warm white sheet wider and taller than the panel by ROOM pixels each side, and the path cuts it with clip-path: path(). So the words are always inside the shape, even halfway, and a flare that goes past the panel has room to show. The bell in the button is the sheet itself, cut to a bell: the button only holds its grey print, which shows when the bell goes out.

The ring and the pill

The ring is four more lightly damped springs (ζ about 0.14) that get a push: the swing round the knob, the rim widening, the dome squashing, and the clapper, which is pulled the opposite way to the swing and lags behind. They count only near the button: a factor that goes to zero as the top moves away, and as soon as the shape leaves their damping rises to 0.95. Then the shape goes to the pill, as wide as its measured text, and after PEEK seconds it becomes the bell again; as it lands the badge counts one more.

The badge travels

The badge has two springs for its position: with the panel closed its place is the bell’s shoulder, turned with the swing; open, the place kept for it beside the heading, read from the page’s measures. On the way down the black ring that sets it off from the button thins out, and the word new unfolds. The figures roll: the old one leaves upwards and the new one comes from below with back.out when the number goes up, the other way when it goes down.

The reading

The yellow of a row has a mask, linear-gradient(90deg, transparent var(--f), #000 calc(var(--f) + 80px)), and --f runs from left to right in READ seconds. The words are split into letters, and each letter takes its weight from how far the edge has passed it, with the same smoothstep: from 560 to 420, and from 780 to 640 in the names. Recursive keeps every letter as wide at every weight, so the lines do not rewrap while they change. The badge counts one less when the edge is a third of the way along the row; Mark all as read starts the rows STAGGER seconds apart.

Values to change

NameFileDefaultWhat it changes
FIRSTscript.js0.6The seconds before the first notification arrives.
EVERYscript.js7The seconds between one notification and the next.
PEEKscript.js1.8The seconds the pill shows a new notification before the bell takes it in.
SPEEDscript.js1How fast the shape moves: 1 as designed, 2 twice as fast.
BOUNCEscript.js0.5How far the shape overshoots as it flares open: 0 not at all, 1 a lot.
RINGscript.js1How hard the bell rings when a notification arrives: 0 not at all.
READscript.js0.6The seconds the reading takes to sweep along a row.
STAGGERscript.js0.11The seconds between one row and the next when all are marked as read.
MAX_ROWSscript.js5The most notifications the panel keeps.
INCOMINGscript.jssei, da Tomás Rey left a note on Low Tide a Night Ferry is masteredThe notifications that arrive, in turn: who sends it (empty for the app itself), what happened (*stars* round a title make it bold) and what they said.

Try them in the Playground

When to use it

For the notification bell of an app or a dashboard, at the top in its bar: a place people look at often, where a new notification should be noticed without stopping the person at work and where you want to know who wrote before opening. It suits a few notifications at a time, mentions, replies, files ready; for messages that confirm an action just taken there are the ribbon toast and the stacked notifications.

On phones

The panel takes the width of the screen minus 32 pixels (358 at 390) and the pill goes no further; the button stays 56 pixels. With a finger one tap opens and closes, a tap on a row reads it and a tap outside closes the panel. A flare that goes past the edge of the screen for a moment is cut off and the page does not scroll sideways.

With reduced motion

The shape does not travel or flare: the bell fades and the panel (or the pill) shows in its place, and closing, the panel fades with its words and the bell comes back. The bell does not ring; the badge fades, changes place and comes back, and the figures fade instead of rolling. Reading, the yellow fades and the letters change weight all together, without moving.

Pitfalls

  • The bell that rings and right after becomes the pill took its swing along: the pill came out crooked, turned round the knob. Now the swing, the breathing rim and the finger’s press count only near the button (a factor that goes to zero as the top moves 14 pixels away), and as soon as the shape leaves their springs are damped to 0.95.
  • Changing the weight letter by letter with an ordinary typeface changes the width of the words and the lines rewrap halfway through the reading. The same line measured at 420 and at 700: Rethink Sans goes from 282 to 272 pixels, Schibsted Grotesk from 285 to 296, Recursive stays at 306.4. That is why the typeface is Recursive.
  • clip-path cannot paint outside its element’s box: cut on a sheet as wide as the panel, a rim flaring past the panel was cut off straight. The sheet is wider and taller by ROOM pixels each side, and the page has overflow-x: clip, so on a phone it does not scroll sideways.
  • On unread rows the keyboard’s focus ring did not show: the yellow is a positioned child, and the browser paints it over the button’s outline. The ring is now a box-shadow on ::after, which comes after the yellow.
  • With reduced motion the badge still travelled on its springs, and closing the panel the words vanished before the sheet, which stayed grey and empty for a moment. Now the badge fades, jumps to its new place and comes back, and the panel fades together with its words.
  • A bold title broke in the middle (Salt at the end of a line, Flats below): titles no longer break.

Libraries

GSAP 3.15.0, Recursive (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 14 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