Skip to content
animorio
IT
All components

The search that finishes the word

Search, Text, TransitionsGSAP, CSS onlyAdvancedCompressed weight: Demo 12 KBGSAP 28 KBFreeDownload the zip

In the minimal style: the search of a personal journal that finishes the word as you type. The rest of the topic waits after the caret, in the same face but thin and narrow, and every letter typed over it inks in, taking weight and width on a spring. The field’s line closes in under the word written and a dotted line measures what is left; Tab completes it, the arrows choose another topic and the end of the word rolls to the new one.

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 search that finishes the word</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/full.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the search of a personal journal. Typing, the field finishes the word: the rest of the best topic waits
         after the caret in hairline, condensed letters, and every letter typed over it inks in. The thin line of the
         field closes in under the word written, and a dotted line measures what is left to write. Tab, the right
         arrow or a tap on the grey letters complete it; the arrows choose another topic; Enter searches.
         The letters and the rows are made by the script from the list at the top of script.js -->
    <div class="finish" data-finish data-autoclic>
      <div class="finish__field" data-field>
        <svg class="finish__lens" viewBox="0 0 24 24" aria-hidden="true"><path d="M10.5 4.75a5.75 5.75 0 1 0 0 11.5a5.75 5.75 0 1 0 0-11.5zM14.75 14.75l4.75 4.75"/></svg>
        <div class="finish__wrap" data-wrap>
          <div class="finish__shake" data-shake>
            <span class="finish__holder" aria-hidden="true" data-holder>Search the journal</span>
            <div class="finish__track" aria-hidden="true" data-track>
              <span class="finish__text" data-text></span>
              <span class="finish__caret" data-caret></span>
            </div>
          </div>
          <input class="finish__input" id="finish-input" type="text" inputmode="search" enterkeyhint="search" role="combobox" aria-label="Search the journal" aria-autocomplete="both" aria-expanded="false" aria-controls="finish-list" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" maxlength="20" data-input>
        </div>
        <button class="finish__key" type="button" tabindex="-1" aria-label="Complete the word" data-key>
          <span class="key__tab" aria-hidden="true">tab</span>
          <svg class="key__arrow" viewBox="0 0 20 20" aria-hidden="true"><path d="M4 10h11.5M11 5.5l4.5 4.5L11 14.5"/></svg>
        </button>
        <span class="finish__rule" aria-hidden="true">
          <span class="rule__ink" data-ink></span>
          <span class="rule__dots" data-dots></span>
          <span class="rule__tick" data-tick></span>
        </span>
      </div>

      <div class="finish__room" data-room>
        <span class="finish__band" aria-hidden="true" data-band></span>
        <ul class="finish__list" id="finish-list" role="listbox" aria-label="Topics" data-list></ul>
        <p class="finish__note" data-note></p>
      </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.

85 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 search that finishes the word” from Animorio and fit it into my site

What it does

On a white background a search field made only of a lens, a large light line of text, Search the journal, and a thin rule; below, Try Photography, Bread or Walking. With the focus in the field the rule turns blue across the whole width. At the first letter the blue line closes in on a spring until it sits right under the letter typed, and after the caret the rest of the most read topic starting that way appears: for p, hotography, in grey, very thin and narrow, opening from a sliver letter by letter. Under the grey part runs a dotted line ending in a small tick, like a dimension: it is what is left to write. Below the field the topics starting with those letters come up, the most read first, with how many essays each has; the one chosen has a grey band. If the letter typed is the one the field was waiting for, the grey letter inks in: it takes weight and width with a small overshoot and back, and the ones after it move to make room. If it goes another way, the grey ending drops and fades and the new topic’s ending opens in its place. The down and up arrows, or the pointer on the list, choose another topic and the ending in the field rolls: the old one leaves upwards, the new one comes in from below. Tab, the right arrow, the drawn tab key to the right of the field or a tap on the grey letters complete the word, and the letters ink in one after another while the blue line follows them. Enter searches: below it reads, for example, 9 essays on Pottery, and the field gives a small jolt. Deleting, the letters typed turn grey and thin again if the topic still holds them. If nothing starts that way the line turns grey and below it reads Nothing on with the letters typed; Enter makes the word shake. Escape closes the list, then empties the field and the blue line goes back to full width. With nothing typed, the pointer on one of the topics offered shows it in the field in grey, as a word to finish.

How it works

The real field under the drawn letters

The input is there and real, with role combobox, aria-autocomplete both, aria-expanded and aria-activedescendant, but its text and its caret are transparent: it keeps the keyboard, the selection, pasting and screen readers. The letters you see are spans drawn by the script, one per character and with kerning off, in the same face and size as the input, so a click lands in the right place. The caret is a blue segment placed after the right letter, measured with getBoundingClientRect.

Letters that stay, letters that go and letters that come

At every change syncLetters() lines up the letters typed and then the ending of the topic chosen, and compares them with the spans already there: as long as the character is the same (ignoring case) the span stays and only changes state, inked or grey. The rest leaves: those spans are taken out of the line where they stand, set in absolute position at the same spot and made to drop and fade, so the letters left close up at once. The new ones open from a sliver, from the wdth axis at 25 to the grey one’s width.

Inking in with the face’s axes

Roboto Flex has the wght axis from 100 to 1000 and wdth from 25 to 151: the letters still to write sit at GHOST (weight 130, width 70), the written ones at INKED (470 and 100). GSAP carries both axes on a damped spring curve written as a function, which passes the goal by a few units and comes back, and rewrites font-variation-settings on every frame. The colour goes from grey to black with a CSS transition, delayed like the weight when the word is completed as a wave, one letter every STAGGER seconds.

The line under the word, on a real spring

The blue line has two ends and the dotted line a third, each on a real spring (the stiffness and damping of SPRING, the step capped at 1/30 of a second and split into steps of 1/240). On every frame the loop measures where the last letter typed and the last grey one end, even halfway through their animation, and the springs follow them: so the line closes in from the full width to under the first letter and then grows letter by letter. The loop stays on only while something moves, and wakes again on resize because the measures are in pixels.

The list and the choice

search() keeps the topics starting with the letters typed, the most read first; the topic chosen stays the same as long as it matches, so deleting does not change it under your fingers. The rows are positioned absolutely: the ones no longer valid fade in 0.12 seconds, the others wait and slide to their new place with a small overshoot, the new ones come up one after another. The arrows or the pointer move the band and roll the ending in the field; Tab and the right arrow complete it only when there is one, otherwise Tab does its usual job.

The preview’s tour

A click from a script (detail 0, the library’s preview) starts a GSAP timeline of about 9 seconds that types without taking the focus: p, then o (the ending goes from Photography to Poetry), the down arrow onto Pottery, t over the grey t, Tab, Enter, and finally Escape brings everything back to rest. While the tour runs other clicks from a script are ignored; a real touch or key stops it at once.

Values to change

NameFileDefaultWhat it changes
TOPICSscript.js[['Photography', 14], ['Poetry', 11], ['Pottery', 9], …]The journal’s topics, and how many essays each one has.
SHOWNscript.js5How many topics the list shows at most.
UNITscript.js['essay', 'essays']The word after the count, for one and for more.
GHOSTscript.js[130, 70]The weight and the width of the letters still to write.
INKEDscript.js[470, 100]The weight and the width of the letters written.
STAGGERscript.js0.04The seconds between one letter and the next inking in when the word is completed.
SETTLEscript.js0.55The seconds a letter takes to ink in and settle.
SPRINGscript.js[420, 35]The spring of the line under the word: stiffness and damping.
TRIESscript.js['Photography', 'Bread', 'Walking']The topics offered when nothing is typed.

Try them in the Playground

When to use it

For the search of a blog, a journal, a product’s documentation, a shop with short category names or the tag field of an app: wherever the words to search for are few and known and it helps to finish them for whoever types, as the browser’s address bar does. The search() function is the only one to change to ask a server for the topics.

On phones

The field takes the width of the screen minus 32 pixels and the words drop to 27 pixels, so even Photography fits easily; the field tells the keyboard to show the search key. A phone has no Tab key: in its place, to the right of the field, there is a circle with a drawn arrow, and a tap on the grey letters also completes the word. The rows stay 46 pixels tall for the finger and a tap picks one and searches it. Axes, springs and rolls are the same as on a computer.

With reduced motion

No springs, drops or rolls: the blue line jumps straight under the word, the grey ending fades in and out already in place, the list’s rows appear and fade without moving and the band jumps from row to row. The letters ink in, changing weight and width in a fifth of a second, with no wave and no overshoot, and the field neither jolts nor shakes its head.

Pitfalls

  • An input cannot give one part of its text a different weight: the grey letters cannot live inside the field. The input’s text is transparent and the letters you see are spans over it, in the same face and size and with kerning off on both, so clicks and selection land where you see them. The native caret, which knows nothing of the letters still moving, is off and we draw our own.
  • Taking the leaving letters out of the line, the ones after them slid left while still fading and overlapped the new ones. Now the positions of all of them are measured before any is touched, then each is set in absolute position at its spot and fades there in 0.2 seconds; the new ones wait a tenth of a second.
  • With the placeholder fading in 0.2 seconds, the first grey letter was born over a still visible Search the journal and read as a mess: now the placeholder goes in 0.06 seconds as soon as there is something in the field.
  • A row moving up to close a gap passed over the one fading out: now the leaving ones fade in 0.12 seconds and the ones staying start after 0.12.
  • The line under the word only followed the word at the end of the animation and lagged behind: now the loop measures the letters on every frame while they take weight and width, and the spring chases the real point.
  • Tab always completed and you could no longer leave the field with the keyboard: now we intercept it only when there is a grey ending to complete.

Libraries

GSAP 3.15.0, Roboto Flex (font variabile, assi wght e wdth) 5.3.0.

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

Testing

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