Skip to content
animorio
IT
All components

The neon sign button

Buttons, SVG, TransitionsGSAP, CanvasAdvancedCompressed weight: Demo 9.4 KBGSAP 28 KBFreeDownload the zip

A restaurant’s neon sign, Book a table, is the button: the tubes light up like real neon, one at a time with an irregular stutter, and one of them struggles before it catches. Under the sign its reflection wavers as on wet asphalt, broken by ripples. Pressed, the sign dies tube by tube and comes back in other colours while the table is booked.

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 neon sign button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/big-shoulders-display@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-scene data-scheme="0">
    <div class="piece" data-piece>
      <!-- the sign is the button: glass tubes on a clear plate, lit by the script one tube at a time -->
      <button class="sign" type="button" aria-label="Book a table" aria-describedby="sign-status" data-sign data-autoclic>
        <span class="sign__body" data-body aria-hidden="true"></span>
      </button>
      <!-- the sign shining back from under it, as from wet ground: drawn by the script, row by row -->
      <canvas class="mirror" data-mirror aria-hidden="true"></canvas>
      <p class="status" id="sign-status" aria-live="polite">
        <span data-status>Kitchen open till 11 pm</span>
      </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.

18 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 neon sign button” from Animorio and fit it into my site

What it does

On a black background a clear plate screwed at its four corners holds a neon sign: a frame of blue tube with rounded corners, Book a table written in script in a pink tube, and a wine glass in the gap at the bottom of the frame. Right under it lies its reflection, as on wet asphalt, and further down the line Kitchen open till 11 pm. As the page opens the electrodes at the ends of the tubes warm up and glow, then each tube lights on its own with a few irregular flashes, and one of them, never the same, stutters dimly for more than a second before it catches. Little by little the sign shows upside down in the reflection, shorter, moved by the ripples, broken into streaks and fainter and fainter towards the bottom. With the pointer over it the tubes’ glow gets brighter and the lazy tube hiccups; every so often it does by itself too. Pressing, each tube dies at its own moment with a last flash and its light fades while the electrodes stay lit a moment longer; the line says Finding you a table. After a moment of darkness the sign lights again with the same stutter in the next colours (amber and red, then mint and violet, then pink and blue again), and the line says Booked: table for two, 8:30 pm in the colour of the sign’s words; shortly after it goes back to Kitchen open till 11 pm.

How it works

The tubes drawn by hand

The letters are SVG paths written by hand, one continuous stroke per tube the way real glass is bent: each letter round its own origin on the baseline, then moved into place by a function that adds the offset to every pair of coordinates. Each tube has its glass when off with a thin highlight, and at its two ends, measured with getPointAtLength, a dark cap where the tube goes through the plate to the electrode.

One number per tube

A tube’s light is two SVG layers over the glass: the lit tube (a wide faint stroke, the colour, an almost white core) and a halo blurred with a CSS filter. The flicker only changes one number per tube, which every frame becomes the opacity of the two layers: the browser never has to draw the glow again, only blend it, and a flicker of fifty flashes costs as much as one.

How a neon lights

For each tube a GSAP timeline warms the electrodes, then 1 to 4 flashes (times FLICKER) of random lengths and gaps, and finally holds it on. One of the tubes marked as lazy, picked at random but never last time’s, spends LAZY seconds in weak, dense flashes that get stronger and closer towards the end. Dying, each tube has a last flash, fades with power2.out, and its electrodes stay lit a little longer.

The reflection

The reflection is a canvas under the sign, as wide as it: its top edge is the edge of the water, and the canvas is just tall enough for the lower half of the sign, upside down and shortened (0.74, as seen from above). The lit tubes are redrawn small and blurred only when they change; then, thirty times a second, the canvas copies one row of the upside-down sign every two pixels, moved sideways by three slow waves, lighter or darker in streaks and fainter and fainter towards the bottom. While the sign is dark the canvas is not redrawn.

The colours and the states

The colours sit in the CSS in three pairs (words and frame) and data-scheme picks the one that is on: it changes while the sign is dark, so it lights straight up in the new colours, and the script reads them back from the CSS for the reflection. The button is a button with the aria-label Book a table; while booking it has aria-busy and the line below, aria-live, says how it is going.

Values to change

NameFileDefaultWhat it changes
FLICKERscript.js1How much the tubes stutter before they catch: 0 not at all, 2 a lot.
LAZYscript.js1.3The seconds the lazy tube struggles before it catches.
DIEscript.js0.7The seconds the sign takes to die when pressed.
DARKscript.js0.45The seconds of darkness before it lights again in the next colours.
GLOWscript.js1How strong the glow round the tubes is.
RIPPLEscript.js1How much the ripples move the reflection under the sign.
BOOKEDscript.jsBooked: table for two, 8:30 pmWhat the line under the sign says once the booking is done.
DONEscript.js3.4The seconds the line says Booked before it goes back.

Try them in the Playground

When to use it

For the main action on the site of a restaurant, a cocktail bar, a live music venue or an arcade: book, come in, get a ticket. It wants a dark page all to itself, like the opening of the site; among other content it would swallow everything.

On phones

The sign narrows to the width of the screen minus 32 pixels and the reflection is worked out again on the real size, as wide as the sign and lower in proportion; the line below gets a little smaller. A tap does what the click does: the sign dies and comes back in the new colours.

With reduced motion

No flicker and no flashes: the sign lights and dies in soft fades, the colours still change, the reflection holds still without ripples and the pressed button does not shrink.

Pitfalls

  • A flicker made by changing the tubes’ colours or filters had the browser redraw the blurred glow at every flash and the page stuttered; now each tube has its own layer already drawn and only its opacity changes.
  • A tube’s halo was written again only when the tube’s light changed, so with the pointer over the sign it did not brighten: now the halo remembers its own last written value and is updated when only the pointer passing over changes.
  • The reflection drawn at full density on a canvas as wide as the page cost more than everything else put together: now the canvas is as wide as the sign, at one pixel per pixel, redraws thirty times a second and never while the sign is dark.
  • The reflection’s blur was in fixed pixels and on a phone, with a small sign, it smeared everything: now it is a share of the sign’s width.
  • A pressed sign shrinks with a transform, and measures taken with getBoundingClientRect at that moment gave a smaller reflection: the width is taken with offsetWidth, which ignores transforms.

Libraries

GSAP 3.15.0, Big Shoulders Display (font variabile) 5.3.0.

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

Testing

Tested on 4 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: Done

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