Skip to content
animorio
IT
All components

The palette that plays

Search, Transitions, Physics, TextGSAP, CanvasAdvancedCompressed weight: Demo 17 KBGSAP 28 KBFreeDownload the zip

The command palette of a podcast editor, where every command is a clip with its audio drawn under it: as you type it plays louder the closer it matches, and the loudest rise to the top. A row that has to jump over the others travels as sound, its words off, and finds them again in its new place; the command picked plays in amber and, once left, dies away slowly like the needle of a VU meter.

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 palette that plays</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/radio-canada@5.3.0/standard.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the command palette of a podcast editor. Every command is a clip of sound: the better it matches what is
         typed, the louder its waveform plays, and the loudest go to the top. The command picked plays in amber;
         moving on, its sound fades out behind you. The rows are made by the script from the list in script.js -->
    <div class="desk" data-desk data-autoclic>
      <button class="open" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="cmd-dialog" aria-keyshortcuts="Control+K Meta+K" data-open>
        <svg class="open__lens" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.5 4.5a6 6 0 1 0 0 12a6 6 0 1 0 0-12zM15 15l4.5 4.5"/></svg>
        <span class="open__text">Commands</span>
        <span class="keys" aria-hidden="true"><kbd class="key" data-mod>Ctrl</kbd><kbd class="key">K</kbd></span>
      </button>

      <div class="pal" id="cmd-dialog" role="dialog" aria-modal="true" aria-label="Commands" data-pal hidden>
        <div class="pal__head">
          <svg class="pal__lens" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.5 4.5a6 6 0 1 0 0 12a6 6 0 1 0 0-12zM15 15l4.5 4.5"/></svg>
          <input class="pal__input" type="text" role="combobox" aria-expanded="true" aria-controls="cmd-list" aria-autocomplete="list" aria-label="Search commands" placeholder="Search commands…" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="go" data-input>
          <span class="pal__caret" aria-hidden="true" data-caret></span>
          <kbd class="key key--word" aria-hidden="true">esc</kbd>
        </div>

        <div class="pal__list" id="cmd-list" role="listbox" aria-label="Commands" data-list>
          <canvas class="pal__wave" aria-hidden="true" data-wave></canvas>
          <div class="pal__none" aria-hidden="true" data-none>
            <p class="none__what" data-none-what></p>
            <p class="none__tip">Try a word like noise, chapter or export.</p>
          </div>
        </div>

        <div class="pal__foot">
          <p class="pal__hints" data-hints>
            <span class="hint hint--keys"><kbd class="key key--icon"><svg viewBox="0 0 24 24"><path d="M12 18.5v-13M7 10l5-5 5 5"/></svg></kbd><kbd class="key key--icon"><svg viewBox="0 0 24 24"><path d="M12 5.5v13M7 14l5 5 5-5"/></svg></kbd>Move</span>
            <span class="hint hint--keys"><kbd class="key key--icon"><svg viewBox="0 0 24 24"><path d="M18.5 5.5v7H6.5M10.5 8.5l-4 4 4 4"/></svg></kbd>Run</span>
            <span class="hint hint--keys"><kbd class="key key--word">esc</kbd>Close</span>
            <span class="hint hint--touch">Tap a command to run it</span>
          </p>
          <p class="pal__done" data-done>
            <svg class="pal__tick" viewBox="0 0 24 24" aria-hidden="true" data-done-tick><path d="M5.5 12.5l4.2 4.2L18.5 7.5"/></svg>
            <svg class="pal__info" viewBox="0 0 24 24" aria-hidden="true" data-done-info><path d="M12 3.5a8.5 8.5 0 1 0 0 17a8.5 8.5 0 1 0 0-17zM12 11v5.5M12 7.6v.4"/></svg>
            <span data-done-text></span>
          </p>
        </div>
      </div>

      <p class="sr" role="status" aria-live="polite" data-said></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.

98 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 palette that plays” from Animorio and fit it into my site

What it does

On a black background a dark button with a lens, the word Commands and two drawn keys, Ctrl and K (on a Mac the Command sign and K). Pressed, or with Ctrl K from anywhere, the palette opens below it: a Search commands… field with the esc key and, below, the commands in three groups, Clip, Sound and Episode, each with its icon and its shortcuts on the right; Publish to feed is off and says Export first. Under every row lies its audio, a waveform of little bars made of syllables, different for every command: faint under the words, open in the room between the name and the keys, silent under the keys. The command picked plays in amber, its audio starting on the left and running to the end in a moment. Typing, every command plays louder the better it holds the letters typed, which turn honey-coloured: the loudest rise to the top of their group and the group with the loudest command to the top of the list. The rows that keep their order slide to their new place with their words on; a row that has to jump over others turns its words off, and its audio flies alone to the new place mixing with the ones it meets, then the words come back there. The ones that no longer match turn their words off and their audio drops to nothing. With the arrows or the pointer the pick moves to another command, which lights up from the left, while the sound of the one left dies away slowly behind it, like a needle falling back: holding the arrow down leaves a trail. Enter runs it: a playhead crosses the row and the audio behind it turns white as if rendered, the keys make way for a tick that draws itself, below appears for instance Loudness set to −16 LUFS, and the palette closes. On Publish to feed the row shakes its head and the foot says why it cannot run. If nothing matches the palette says so and suggests a few words. Escape or a click outside close it, and the focus goes back to the button.

How it works

A voice for every command

Every command has its own waveform, always the same: its name gives a number that seeds a random generator, which lines up syllables (bells of different width and height) and a few pauses. The shape is sampled in bars 2 pixels wide every 4, as many as fit in the row, and sampled again when the window changes size.

The closer, the louder

The search wants every letter typed in the name, in order, and picks with dynamic programming the way of finding them that scores most: a point a letter, four if it follows the letter before, three if it starts a word, two if it starts the name, a little less for every letter skipped. The score divided by the best one's, raised to 1.6, is the row's volume; commands are sorted by score within their group, and groups by their best command.

Bars like a VU meter

Every bar has its own height and its own heat (from grey to amber), which chase their target exponentially: fast going up (0.03 seconds) and slow coming down (TRAIL), like the meters that measure audio. The pick lights the bars from the left one after the other in SWEEP seconds. The bars are drawn on one canvas under the rows, adding up the light where two audios cross, and only while something moves; under the words they show at 32 percent, under the keys not at all.

Sliding, or flying as sound

At every letter the rows and the group names take their new place on a spring (SPRING). Among the ones that stay, the longest increasing run of their old places keeps its order and slides with its words on, since it cannot cross anything; the others turn their words off in 0.06 seconds and travel as audio alone. A row turns its words back on only once it has arrived and no word that is on is passing, or about to pass, where it is; the list's height follows the same spring and never cuts a word that is on.

Running

Enter or a click start a timeline: the playhead crosses the row in RUN seconds, the audio behind it turns white, the keys fade and a tick draws itself with stroke-dashoffset, the foot says what was done, and after HOLD seconds the palette closes. Typing or moving the pick meanwhile stops the timeline and puts everything back.

A real combobox

The field has role combobox, aria-controls on the list and aria-activedescendant on the row picked; the list is a listbox with a group for each group and the rows are options with aria-selected, aria-disabled for the one that is off and aria-keyshortcuts. An aria-live region says how many commands there are and what was done. The window is a modal dialog: Tab stays in the field, Escape or a click outside close it and the focus goes back to the button, with its ring only for who uses the keys.

Values to change

NameFileDefaultWhat it changes
SHOWNscript.js7How many commands the palette shows at most.
RESTINGscript.js2How many commands of each group it shows while the field is empty.
TRAILscript.js0.32How slowly a row’s sound dies away once the pick has left it, in seconds.
SWEEPscript.js0.16The seconds the sound takes to run across the row picked.
SPRINGscript.js[700, 50]The spring that takes the rows to their new places: stiffness and damping.
RUNscript.js0.45The seconds the playhead takes to cross a command that runs.
HOLDscript.js0.8How long the palette says what was done before it closes, in seconds.
COMMANDSscript.js[['Clip', 'Split clip at playhead', 'S', 'Clip split at 12:48', 'split'], …]The commands: group, name, shortcut (mod is Cmd on a Mac and Ctrl elsewhere), what the foot says once it runs, icon.
LOCKEDscript.js['Publish to feed', 'Export first', 'Export the episode before publishing it']A command not available yet: its name, the note on its right and why.

Try them in the Playground

When to use it

For the command palette of an app that works with sound (podcasts, music, video, radio), or of any tool where you find an action by typing: the volume tells at a glance how close a command is to what you typed.

On phones

The palette takes the width of the screen minus the margins. On touch screens the shortcuts go (there is no keyboard to use) and their room goes to the audio; the foot says Tap a command to run it, the rows are 44 pixels tall and a tap runs one. The field’s text is 17 pixels, so iPhone does not zoom the page.

With reduced motion

Nothing slides and nothing grows: the rows that change place fade out and back in at their new place, the audio comes and goes with the words without rising, the pick changes colour with a fade and the playhead does not run: the audio turns white all at once.

Pitfalls

  • With the rows sorted by score, sliding they crossed and the words overlapped. Now only the longest increasing run of the old places slides with its words on, since it cannot cross anything; the other rows travel as audio alone on the canvas, which lies under the words.
  • A new row turned its words on as soon as it arrived, and right after a sliding row passed over it. Now a row waits until no word that is on fills, or has still to cross, the stretch between where it is and where it goes.
  • The list’s height, animated on its own, shrank before the rows went up and cut off the last one for a moment: now it follows the same spring and never goes below the bottom of the last word that is on.
  • The lowest bars, two pixels wide and two tall, looked like a row of dots: silence draws nothing and the quietest sound is still a little line at least three and a half pixels tall.
  • The full sound under the words made them hard to read: under the name it shows at a third and under the keys not at all, measuring the end of the real text with a Range (the name’s box is wider than its words).
  • Taking away a picked row without removing its aria-selected, coming back there were two picks: now a row that goes loses the pick, and a burst test checks that there is always just one.

Libraries

GSAP 3.15.0, Radio Canada (font variabile) 5.3.0.

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

Testing

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