Skip to content
animorio
IT
All components

The Mexican wave nav

Navigation, Physics, HoverCanvasAdvancedCompressed weight: Demo 12 KBno librariesFreeDownload the zip

The top bar of a football club, with a stand behind it: hundreds of small square cards, one face dark and one gold. Under the current link the cards show gold in a pill; choosing another link the stand does the Mexican wave, a column of cards rising and turning over runs there, the old patch goes plain and the new one turns gold. Pointing at a link the cards shiver in a round wave.

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 Mexican wave nav</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/saira@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the top bar of a football club. Its background is a stand of supporters holding up hundreds of small cards,
         each with a plain face and a face in the club's gold: under the current link the cards show gold, and
         choosing another link a Mexican wave runs there, the cards rising and turning over as it passes -->
    <header class="ola" data-bar>
      <canvas class="ola__stand" aria-hidden="true" data-stand></canvas>
      <canvas class="ola__stand ola__stand--veil" aria-hidden="true" data-veil></canvas>
      <span class="ola__lights" aria-hidden="true"></span>

      <a class="brand" href="#" aria-label="Ostrel Athletic, home" data-brand>
        <svg class="brand__crest" viewBox="0 0 24 28" aria-hidden="true">
          <path class="brand__shield" d="M12 1.6 21.4 4.6v8.2c0 6.1-3.9 10.6-9.4 13.6C6.5 23.4 2.6 18.9 2.6 12.8V4.6z"/>
          <path class="brand__band" d="M3.4 16.2 20.6 7.4v4.4L4.9 19.9z"/>
          <path class="brand__ball" d="M12 4.9 13.6 8.3l3.7.4-2.8 2.5.8 3.6L12 13l-3.3 1.8.8-3.6-2.8-2.5 3.7-.4z"/>
        </svg>
        <span class="brand__name">Ostrel <span class="brand__light">Athletic</span></span>
      </a>

      <nav class="strip" aria-label="Main" data-strip>
        <ul class="list" data-list>
          <li><a class="link" href="#" data-link data-autoclic><span class="link__word">Fixtures</span></a></li>
          <li><a class="link" href="#" aria-current="page" data-link><span class="link__word">Squad</span></a></li>
          <li><a class="link" href="#" data-link data-autoclic><span class="link__word">Tickets</span></a></li>
          <li><a class="link" href="#" data-link data-autoclic><span class="link__word">Club</span></a></li>
          <li><a class="link" href="#" data-link data-autoclic><span class="link__word">Shop</span></a></li>
        </ul>
      </nav>

      <a class="cta" href="#" aria-label="Become a member" data-cta><span class="cta__long">Become a member</span><span class="cta__short">Join</span></a>
    </header>
  </main>

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

30 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 Mexican wave nav” from Animorio and fit it into my site

What it does

On a black background a bar 60 pixels tall with rounded corners: on the left the crest and name of the club, Ostrel Athletic, in the middle five links in capitals (Fixtures, Squad, Tickets, Club, Shop), on the right the white Become a member button. The bar's background is a stand seen from afar: a dense grid of small midnight blue square cards, each a little lighter or darker and held a little askew, as real supporters do. Under the current link the cards are turned to their gold face and make a clean pill, with the word dark on it. On arrival the stand does a first wave that comes in from the left and leaves the gold under Squad. Choosing another link the wave starts: a column of cards runs from the old link to the new one, and as it passes every card rises a little and makes half a turn on its own spring, shows gold, and a moment later another half turn takes it back to dark; as it turns it gets shorter, shows its pale edge and catches the floodlights. The old pill goes out behind the wave, the new one lights up as the wave arrives, and from there a ripple runs through the stand, bounces off the edges and dies away. Every word takes its colour from the cards under it: light on the dark, dark on the gold, even while the wave runs under it. Pointing at a link with the mouse, the cards under the pointer shiver in a round wave that spreads out; a finger, the Tab key and the button do it too. When everything is still the drawing stops.

How it works

The stand of cards

The bar is divided into a grid of cells CARD pixels apart (ten rows in a bar 60 pixels tall), and every cell is a card with an angle, a turning speed and the angle its spring pulls it to. Each also has a small slant at rest, a brightness, a stiffness and a delay of its own, taken from a seeded random generator: the stand looks alive but is the same at every load.

Half a turn on a spring

Turning a card means moving the angle its spring pulls it to by π, always forward: the gold face is the one in view when the cosine of the angle is negative. The spring (stiffness FLIP, damping WOBBLE) is integrated in steps of 1/120 of a second, so even a stiff spring stays steady; the frame step is at most 1/30 of a second, and the card goes a little past and comes back, as if turned by a hand.

The wave

Choosing a link, every card on the way between the old pill and the new one gets two turns booked: to gold at the moment the column reaches it (the distance divided by SPEED, plus LEAN milliseconds per row, so the column leans, plus its own delay) and to the face it must keep BAND/SPEED seconds later. The cards off the way that must change face turn in order of distance. The new pill is a rounded box round the word, measured on the cards' centres.

A real wave on the grid

Under the cards runs the discrete 2D wave equation, solved 240 times a second: every cell moves towards its neighbours (u'' = c²∇²u), a hand brings it back to rest (−ω²u), it loses a little motion and the edges of the bar reflect. The column of the wave pushes up the cells it crosses and leaves a wake; on arrival and under the pointer a gaussian bell opens a ring that spreads. The wave's value tips and lifts the cards.

Light from the angle

Every card is drawn on the canvas as a rectangle as tall as the cosine of its angle, raised and a little enlarged while it turns and on the crest. Its light is the diffuse part (normal times the direction of the floodlights, from above and in front) plus a glint when the face looks at the lights; edge on, its pale edge shows, as thick as the sine of the angle.

Words that always read

Every frame the cards under each word that show gold are counted and written into --on: the CSS mixes the word's colour from light to dark with color-mix and gives it a halo the colour of the cards under it, which covers the gaps between them. When nothing moves any more the loop stops.

Values to change

NameFileDefaultWhat it changes
CARDscript.js6How far apart the cards are, in pixels: less makes more of them.
SPEEDscript.js560How fast the wave runs from the old link to the new one, in pixels a second.
BANDscript.js150How wide the column of gold cards running with the wave is, in pixels.
FLIPscript.js380How stiff the spring that turns each card over is: higher, a quicker flip.
WOBBLEscript.js0.34How little a card rocks once it has turned: less rocks more.
LIFTscript.js2.6How far a card rises as it turns and on the crest, in pixels.
RIPPLEscript.js0.9How strongly the cards shiver round the pointer on a link (0, not at all).
LEANscript.js9How much the column leans, in milliseconds between one row of cards and the next.

Try them in the Playground

When to use it

For the header of a club, a league, a festival, a bar where people watch the games, a sports shop or a stadium ticket app. With the colours in :root it becomes any club's stand: just change --card and --team.

On phones

The bar is 56 pixels tall and as wide as the screen less 32: the crest stays without the name and the button just says Join (screen readers still hear Become a member). The cards shrink with the bar, always ten rows. The links scroll sideways inside the bar, which fades out on the side where more are hidden; the chosen link slides to the middle and the wave already runs to where it will land. Scrolling by hand, the pill follows the word by turning cards. A finger on a link opens the round wave where it touches.

With reduced motion

No wave, no springs and no ripples: the gold pill is already in place under the current link and, choosing another link, the old stand fades away over the new one, with the pill already under the chosen word. Presses do not shrink anything.

Pitfalls

  • With the pure wave equation, the crest of the wave left the cards tipped for good: with edges that reflect, the mean displacement has nothing to bring it back to zero. A −ω²u term, the supporter's hand putting the card straight, brings it back to rest.
  • With a gold column as short as a card's turn, the cards got their second turn before finishing the first and the gold never showed: the column lasts BAND/SPEED seconds, longer than a turn.
  • The floodlights' glint added in white turned the whole new pill pale yellow while the ripple crossed it: the glint is narrow and faint and the diffuse light has a ceiling.
  • With words dark or light according to the current link, the gold column running under the other links made them unreadable for a moment: every word takes its colour from the share of gold cards under it, with a steep threshold, and a halo of the same colour covers the gaps between the cards.
  • On a phone the chosen link scrolls to the middle of the strip as the wave starts: the column aimed at where the word used to be. Now the wave runs to where the word will land, and while the script scrolls the strip the pill is not worked out again.
  • A stiff spring, integrated with the frame's step, could blow up at the high ends of the knobs: the spring is integrated in steps of 1/120 of a second.

Libraries

Saira (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 12 KB, and no libraries are needed.

Testing

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