Skip to content
animorio
IT
All components

The seat picker

Pickers, TransitionsGSAPMediumCompressed weight: Demo 6.5 KBGSAP 28 KBFreeDownload the zip

A cinema hall: the seat chosen jumps and its ticket, with the seat’s name, flies down to the summary along a curve while the total counts; a seat let go calls its ticket back, and the others close up, staying in the order of the seats.

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 seat picker</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/spline-sans@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the seat picker: a seat chosen jumps and its ticket flies down into the summary, the total counts, and a seat
         let go calls its ticket back -->
    <section class="hall" aria-labelledby="hall-title" data-hall data-autoclic>
      <header class="hall__head">
        <h2 class="hall__title" id="hall-title">Choose your seats</h2>
        <p class="hall__when">Saltwater Hours · Sat 9:15 pm · Hall 2</p>
      </header>
      <div class="hall__screen" aria-hidden="true">
        <svg viewBox="0 0 300 24" preserveAspectRatio="none"><path d="M6 20 Q150 -6 294 20"/></svg>
        <span>Screen</span>
      </div>
      <div class="hall__seats" role="group" aria-label="Seats" data-seats></div>
      <p class="hall__tip" aria-hidden="true" data-tip></p>
      <div class="hall__key" aria-hidden="true">
        <span><i class="seat seat--key"></i>Free</span>
        <span><i class="seat seat--key is-taken"></i>Taken</span>
        <span><i class="seat seat--key is-on"></i>Yours</span>
      </div>
      <div class="sum">
        <div class="sum__left">
          <p class="sum__label" id="sum-label">Your seats</p>
          <ul class="sum__chips" aria-labelledby="sum-label" data-chips></ul>
          <p class="sum__none" data-none>None yet</p>
        </div>
        <div class="sum__right">
          <p class="sum__total" aria-live="polite"><span class="sr">Total </span><span data-total>€0</span></p>
          <button class="sum__go" type="button" disabled data-go>Continue</button>
        </div>
      </div>
      <p class="sr" aria-live="polite" data-live></p>
    </section>
  </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.

38 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 seat picker” from Animorio and fit it into my site

What it does

On a black background a dark box with Choose your seats and Saltwater Hours · Sat 9:15 pm · Hall 2; under it, the screen (a light arc that draws itself when the page opens) and seven rows of seats, A to G, ten a row with an aisle in the middle: grey in the first three rows, violet in the others, dark the ones already taken; then the key and, at the bottom, Your seats, the total and Continue. Going over a seat a white label appears above it with row, number and price, sliding from seat to seat. Choosing one, the seat turns amber with its name, jumps, and its ticket flies down into the summary along a curve while the total counts up to the new price; letting it go, the ticket flies back up into the seat, which takes it with a little bow, and the other tickets close up. The tickets stay in the order of the seats. Past six seats the seat shakes and the label says Up to 6 seats.

How it works

The hall

The seats are made by the script: ROWS rows of PER_ROW buttons, split in half by an aisle (a grid whose columns come from two CSS variables); the seats in TAKEN are taken, and the prices are PRICES[0] in the first three rows and PRICES[1] in the others. Each seat says its row, number and price (aria-label) and whether it is chosen (aria-pressed).

The ticket’s flight

The new ticket goes straight into the summary, hidden, in its place in the order of the seats; a copy sets off from the seat and heads for it, measured on every frame (the other tickets may move meanwhile): x with power2.inOut and y with another ease draw a curve, and the copy goes from the seat’s size to the ticket’s in FLIGHT seconds. On arrival the real ticket shows with a small bounce.

The way back and the other tickets

Letting a seat go, its ticket flies back from the summary to the seat, which takes it dipping to 0.82 and coming back with back.out; before and after every change the other tickets’ places are measured, and they slide to their new places (FLIP).

The total and the limit

The total goes to the new value in 0.5 seconds; Continue lights up with the first seat. Past MAX seats the seat shakes (keyframes on x), and the label and an aria-live region say the limit.

Keys and the preview

The seats are a single Tab stop: the arrows move between rows and seats, Enter or space choose, and the label follows the focus. A click from a script (the library’s preview) is a pretend hand: it points at a seat, takes it or lets it go, one at a time, and ends with the hall as it was.

Values to change

NameFileDefaultWhat it changes
ROWSscript.js'ABCDEFG'The rows, from the screen to the back.
PER_ROWscript.js10The seats in a row, split by the aisle in the middle.
TAKENscript.js['A3', 'A4', 'B6', …]The seats already taken.
MAXscript.js6How many seats can be chosen.
PRICESscript.js[8, 10]The price of a seat in the first three rows, and in the others, in euros.
FLIGHTscript.js0.55The seconds a ticket takes to fly between its seat and the summary.

Try them in the Playground

When to use it

For choosing seats at a cinema, a theatre, a concert, on a train or a plane, the tables of a restaurant outside, the desks of an office.

On phones

The seats close up (22 pixels high and smaller gaps) so the ten of a row fit the width; on a tap the label does not show, as the finger would cover it: a chosen seat carries its name inside itself.

With reduced motion

The seat chosen does not jump, the ticket appears in the summary without flying (and disappears without going back), the total changes at once, the seat too many does not shake and the screen does not draw itself.

Pitfalls

  • A ticket flying to its place in the summary must chase it: if meanwhile another ticket comes in before it the place moves, and the copy, measured on every frame, gets there all the same.
  • A grid with repeat(var(--a), …) takes the number of seats from a CSS variable: changing the seats a row leaves the CSS as it is, and on a phone only the widths of the aisle and the letters change.
  • Seventy buttons would be seventy Tab stops: there is a single one (a moving tabindex) and the arrows to move about the hall.
  • Safari on iPhone gives the focus to the seat tapped: the label that follows the focus shows only for someone on the keys, or it would stay over the other seats; on a phone the name sits inside the chosen seat.

Libraries

GSAP 3.15.0, Spline Sans (font variabile) 5.3.0.

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

Testing

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