Skip to content
animorio
IT
All components

The typewriter field

Forms, Text, SVGGSAP, CanvasAdvancedCompressed weight: Demo 18 KBGSAP 28 KBFreeDownload the zip

The email field of a sign-up typed on a strip of paper wound on the platen: the strike point stays still and at every key the paper moves one notch, while a typebar flicks up from below and leaves the letter inked a little differently every time. Backspace lifts the letter off with correction tape, the bell shakes near the margin, and on sending the return lever throws the carriage back, the platen rolls a line and the machine types the answer by itself.

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 typewriter field</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/courier-prime@5.3.0/400.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/courier-prime@5.3.0/400-italic.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/courier-prime@5.3.0/700.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the newsletter sign-up of a short fiction magazine, typed on a strip of paper wound on a black rubber platen:
         the strike point stays still behind the metal guide and the paper moves, the return lever throws the carriage
         back when the address is sent, the knurled knob turns with the platen and the bell shakes near the margin -->
    <form class="tw" action="#" novalidate data-tw data-autoclic>
      <div class="tw__top">
        <label class="tw__label" for="tw-input">Email address</label>
      </div>
      <svg class="tw__bell" viewBox="0 0 28 22" aria-hidden="true" data-bell>
        <g data-bell-body>
          <path class="bell__dome" d="M8 16.2c0-4.6 2.6-7.6 6-7.6s6 3 6 7.6z"/>
          <path class="bell__base" d="M6.4 16.6h15.2"/>
          <path class="bell__stem" d="M14 8.6V6.4"/>
          <circle class="bell__knob" cx="14" cy="5.4" r="1.4"/>
        </g>
        <path class="bell__ring" d="M5.4 8.2c-1.3 1.5-1.8 3.4-1.5 5.4M2.6 6.4C.8 8.6.1 11.3.6 14" data-ring/>
        <path class="bell__ring" d="M22.6 8.2c1.3 1.5 1.8 3.4 1.5 5.4M25.4 6.4c1.8 2.2 2.5 4.9 2 7.6" data-ring/>
      </svg>
      <div class="tw__field" data-field>
        <svg class="tw__lever" viewBox="0 0 34 40" aria-hidden="true" data-lever>
          <defs>
            <linearGradient id="tw-chrome" x1="0" y1="0" x2="1" y2="0.35">
              <stop offset="0" stop-color="#8a8e95"/>
              <stop offset="0.28" stop-color="#ffffff"/>
              <stop offset="0.55" stop-color="#a4a8af"/>
              <stop offset="0.78" stop-color="#e9ebee"/>
              <stop offset="1" stop-color="#5d6168"/>
            </linearGradient>
          </defs>
          <g data-lever-arm>
            <path class="lever__arm" d="M12.4 34C12.3 25.6 9.8 17 5.2 10.2l3.6-2.4C13.4 14.8 17 24 17.6 34z"/>
            <path class="lever__blade" d="M.9 8.8C.2 6.2 1.7 2.9 5 1.4 8.1 0 10.6.9 10.9 3c.3 1.9-1.2 3.9-3.3 5.3-2.4 1.6-5.9 2.6-6.7.5z"/>
            <path class="lever__shine" d="M2.6 6.6c.9-1.9 2.6-3.3 4.7-3.8"/>
          </g>
          <circle class="lever__boss" cx="15" cy="34" r="4.4"/>
          <circle class="lever__screw" cx="15" cy="34" r="1.3"/>
        </svg>
        <svg class="tw__knob" viewBox="0 0 16 50" preserveAspectRatio="none" aria-hidden="true">
          <defs>
            <linearGradient id="tw-knob" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" stop-color="#000" stop-opacity="0.85"/>
              <stop offset="0.3" stop-color="#000" stop-opacity="0"/>
              <stop offset="0.42" stop-color="#fff" stop-opacity="0.07"/>
              <stop offset="0.7" stop-color="#000" stop-opacity="0"/>
              <stop offset="1" stop-color="#000" stop-opacity="0.9"/>
            </linearGradient>
          </defs>
          <rect class="knob__body" x="0" y="0" width="16" height="50" rx="4.5"/>
          <path class="knob__ridges" data-ridges/>
          <path class="knob__grooves" data-grooves/>
          <rect x="0" y="0" width="16" height="50" rx="4.5" fill="url(#tw-knob)"/>
        </svg>
        <div class="tw__window" data-window>
          <div class="tw__sheet" data-sheet>
            <div class="tw__paper" data-paper></div>
            <div class="tw__lines" aria-hidden="true" data-lines>
              <span class="tw__ph" data-ph></span>
            </div>
            <input class="tw__input" id="tw-input" name="email" type="text" inputmode="email" autocomplete="email" autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="send" maxlength="254" aria-describedby="tw-help" aria-invalid="false" data-input>
          </div>
          <div class="tw__shade" aria-hidden="true"></div>
          <div class="tw__guide" aria-hidden="true" data-guide>
            <svg class="guide__plate" viewBox="-32 0 64 60">
              <defs>
                <linearGradient id="tw-plate" x1="0" y1="0" x2="0" y2="1">
                  <stop offset="0" stop-color="#f6f7f8"/>
                  <stop offset="0.35" stop-color="#c3c7cd"/>
                  <stop offset="0.7" stop-color="#8b9098"/>
                  <stop offset="1" stop-color="#5c6067"/>
                </linearGradient>
              </defs>
              <path class="guide__index" d="M-3.4 7.6h6.8L0 12.2z" data-index/>
              <path class="guide__body" d="M-27 44.6c0-.9.7-1.6 1.6-1.6h21.6L0 47.4 3.8 43h21.6c.9 0 1.6.7 1.6 1.6v9.8H-27z"/>
              <path class="guide__slots" d="M-15.5 46.6v4.6M15.5 46.6v4.6"/>
              <path class="guide__lip" d="M-26 43.6h21.9M4.1 43.6H26"/>
            </svg>
            <span class="tw__tape" data-tape></span>
            <span class="tw__bar" data-bar></span>
          </div>
        </div>
        <button class="tw__key" type="submit" data-key>
          <span class="key__top">
            <span class="key__cap">
              <span class="key__word" data-key-word>Subscribe</span>
              <svg class="key__tick" viewBox="0 0 24 24" aria-hidden="true" data-key-tick><path d="M6.4 12.6l3.8 3.8 7.6-8.6"/></svg>
            </span>
          </span>
        </button>
      </div>
      <div class="tw__help" aria-hidden="true" data-help><span class="help__line is-help">A new short story every other Sunday.</span></div>
      <span class="sr" id="tw-help" data-help-sr></span>
      <span class="sr" aria-live="polite" data-live></span>
    </form>
  </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.

24 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 typewriter field” from Animorio and fit it into my site

What it does

On a white background the words Email address and, under them, the glossy black carriage of a typewriter: on the left the knurled platen knob with the chrome return lever sticking up, in the middle an ivory strip of paper on the black rubber platen (the platen’s curve shows above and below the paper, with a soft highlight), over the paper a small metal guide with a V notch marking the strike point, and on the right an ivory key with a chrome rim, Subscribe. Below, in grey, A new short story every other Sunday. At rest the paper shows you@example.com in pencil, ending right at the notch, and on the left the edge of the sheet. With the field touched, a ring of ink appears round the carriage and the caret blinks at the notch. At every key the typebar flicks up from under the guide, strikes, the letter stays printed a little darker and then dries, and the paper jumps one space to the left with a small jolt: the letters took different amounts of ink, sit a touch higher or lower, lean slightly, are patchy where the ribbon gave less and now and then struck twice. Space moves the carriage on without striking. Backspace brings the carriage back one space, a strip of white correction tape comes up and the letter comes away curling upwards and disappears. An address pasted or filled in by the browser types itself out, letter after letter, quickly. Six letters before the margin a bell appears above the notch and rocks, with two little arcs at its sides, without a sound. An address longer than the paper slides away to the left, and taking the caret back to the start brings the carriage back to show it. Pressing Enter or Subscribe with a wrong address shakes the carriage, the ring and the index at the top turn red and what is missing is typed in red under the field, for example Add the ending, like .com; as soon as the address is right the red letters lift off and the grey line comes back. With a right address the return lever kicks, the carriage runs back to the margin and bangs into it, the platen turns carrying the line up, squashed by the curve, while the knob turns with it; then the machine types You’re on the list. by itself, a tick draws itself on the key and The first story comes on Sunday. is typed below. A click on the paper or a key turns the platen back and brings the address back, to change it.

How it works

The real field travels with the paper

The input is real (a label, autocomplete email, inputmode email for the keyboard with the @, enterkeyhint send, aria-invalid and aria-describedby pointing at a line for screen readers, an aria-live region for errors and the answer). It sits inside the moving sheet, thousands of pixels wide so it never scrolls by itself, with its letters transparent and its caret visible: its letters fall exactly under the drawn ones (a monospaced face measured with measureText), so its caret, the selection, autofill and the phone keyboard work where they are seen.

The machine’s queue

At every change the value is compared with the drawn text (same start, same end). What is added at the end joins a queue of strikes, what is taken off the end a queue of corrections; every step is a GSAP timeline: the typebar rises with power3.in, at the impact the letter is inked and the carriage moves on by its width, with a jolt and a kick of the platen that carry on by themselves. When steps pile up the hand speeds up to PASTE seconds a letter. A change in the middle of the line (the machine could not do it) flushes the queue and the letters move to their places.

The ink

Once, on a canvas, a value noise is drawn (blotches the size of a letter, a finer mottle, the ribbon’s diagonal weave and a few fibres that took no ink) and becomes the alpha mask of every letter, taken from a different spot. With mask-composite: intersect a gradient at a random angle is added to it, because a typebar never lands flat. On top, every letter has its own density, a shift off the line of JITTER pixels, a lean of TILT degrees and now and then a faint second strike in text-shadow; INK sets how uneven it all is.

The platen and the return

A line sits on the platen’s cylinder: it rises by the radius times the sine of its angle, is squashed by the cosine and dims as it turns away. On sending, the lever turns round its pivot, the carriage runs to the margin with power2.in and bangs into it, and the platen turns one line carrying the address up and the new line down; the knob’s grooves, lines round a cylinder seen from the front, are redrawn only while it turns. After subscribe() has answered the reply types itself; a click or a key turns the platen back.

Errors, the bell and the preview

The check says what is missing and it is typed under the field letter by letter in red, with the same ink mask; the carriage shakes and the guide’s index turns red. When the carriage passes BELL letters before the margin the bell rocks, once a line. A click from a script on the form (detail 0, the library’s preview) types an address with a slip corrected and sends it, then one without its ending, refused in red, finished and sent.

Values to change

NameFileDefaultWhat it changes
PLACEHOLDERscript.js'you@example.com'The address pencilled on the paper while the field is empty.
DONEscript.js'You’re on the list.'What the machine types by itself on the new line once the address has gone.
NOTEscript.js'The first story comes on Sunday.'The line typed under the field once the address has gone.
STRIKEscript.js0.06The seconds the typebar takes to fly up and hit the paper.
CARRIAGEscript.js0.07The seconds the carriage takes to jump one space.
JOLTscript.js1.2How many pixels the carriage overshoots at every jump (and how much the platen kicks).
INKscript.js0.45How uneven the ink is: 0 every letter alike, 1 patchy and faint.
JITTERscript.js0.6How many pixels a letter can stray above or below the line.
TILTscript.js0.6How many degrees a letter can lean.
PASTEscript.js0.035The seconds between two strikes when a whole address is pasted or filled in by the browser.
BELLscript.js6How many letters before the margin the bell rocks.
WAITscript.js0.5How many seconds the subscription takes in the demo, before the answer is typed.

Try them in the Playground

When to use it

In the newsletter sign-up of a magazine, a writer, a publisher, a bookshop or a short fiction site: where writing is the product and an ordinary field would be a missed chance. It stays a real email field, with the browser’s autofill and the phone’s keyboard.

On phones

On a 390-pixel phone the carriage is as wide as the screen, stays 60 tall, the letters drop to 16 pixels (so iOS does not zoom the page when the field is touched) and the key narrows to 86. A tap opens the email keyboard with an Enter key that says Send; the paper, the lever and the bell are the same, with fewer letters before the margin.

With reduced motion

No typebar, no jumps or jolts: the paper moves one space at once and the letter fades in at its place, and leaves the same way. On sending, the line fades and the answer appears letter by letter with no lever and no turning platen; the carriage does not shake, the bell appears and fades standing still, the lines under the field swap with a fade.

Pitfalls

  • With type email the field does not say where the caret is and strips the spaces at the end of the value: the field’s text and the drawn one, laid letter by letter on each other, would no longer have matched. The field is of type text with inputmode email, which gives the same keyboard on phones.
  • The letters looked like digital Courier: a single noise mask was too light. Now the mask also has the ribbon’s speckle and a few empty fibres, and a random gradient per letter is added with mask-composite, the side struck less hard.
  • Every step of the queue lasted as long as its trimmings (the lever springing back with elastic.out, the jolts after the jump): the answer came late and a fast typist piled up delay. Now a step ends when the carriage lands, and the rest carries on by itself.
  • The highlight on the paper’s edge was on a still layer and showed on the bare rubber left of the sheet too: now it is on the paper, which moves.
  • The new line under the field came in while the last letters of the typed one were still lifting, and two lines sat on each other: now it waits for the last.
  • Rolling the paper back to correct it, the answer’s line passed behind the guide still dark: going down it fades sooner than going up.
  • With the address brought back, the bell rang again at the next key: now it remembers whether the line has already passed the mark.

Libraries

GSAP 3.15.0, Courier Prime 5.3.0.

Compressed with gzip, the three demo files weigh 18 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