23rd

Typewriter

A real, playable typewriter in SVG — each key presses down, its typebar swings up to strike the ribbon, and the carriage steps along. Click it and type.

A typewriter that actually types — glossy enamel, chrome, a black rubber platen, a red-and-black ribbon, and an ivory sheet. Every key on the keyboard is wired to its own typebar in the basket. Press a letter and its key goes down, that bar swings up out of the fan, the ribbon lifts, and the letter lands on the sheet as the carriage steps one character left. Capitals hold shift, which drops the basket. At the end of a line the return lever kicks, the platen turns the paper up a line, and the carriage slides home. With loop on, the finished sheet is pulled out and a fresh one rolls in from behind the platen.

And you can use it. Click the machine and type: letters strike, Enter pulls the return lever, Backspace moves the carriage back a space so the next letter overstrikes (backspace and underscore underlines, like the real thing), and the margin returns the carriage for you. Or click the keys themselves — the space bar, the shift keys (they lock), and the return lever all work. The first key you press stops the script and the page is yours. The sheet leans as the carriage slams across and wobbles back upright, and the whole machine takes each blow.

Preview

Click it and type

Props
Enamel
12 / s
0.30
32

Uneven keys, rests after punctuation

Pull the sheet out and type it again

Type on it, or click its keys

Key clicks, bell, and return

Installation

pnpm dlx shadcn@latest add @23rd/typewriter

Or install straight from the public GitHub source registry:

pnpm dlx shadcn@latest add radiumcoders/23rd.dev/typewriter

Usage

Give the parent a size. The machine scales to fit and stays centered, with room above it for the sheet to grow as lines feed up.

"use client"

import { Typewriter } from "@/components/ui/typewriter"

export function Letter() {
  return (
    <div className="h-96 w-full overflow-hidden rounded-xl border">
      <Typewriter text={"Dear reader,\nthe ribbon is fresh."} />
    </div>
  )
}

\n starts a new line; everything else wraps at words to columns characters, and the sheet, platen, and carriage travel resize to match. Type once and stop, or slow it down and switch the clicks on.

<Typewriter loop={false} onDone={() => console.log("typed")} />
<Typewriter color="#D2463A" speed={8} columns={40} sound />

sound synthesizes the key clicks, the margin bell, and the carriage return with Web Audio; browsers only let it play after the visitor has clicked or pressed a key on the page. With interactive on, the machine is focusable and shows a focus ring; key repeat is ignored, the way a real typewriter would. Screen readers get the whole text at once. prefers-reduced-motion: reduce prints the finished page with no animation, and typing pauses while the typewriter is off-screen or the tab is hidden.

Props

PropTypeDefault
colorstring"#8EC3B5" — body enamel, any CSS color
textstringa short letter — \n breaks a line
speednumber12 — characters per second
humanizebooleantrue — uneven keys, rests after punctuation
jitternumber0.3 — 0–1, per-letter baseline, tilt, and ink
columnsnumber32 — line length on the sheet, 16–60
loopbooleantrue — pull the sheet out and type it again
holdnumber2600 — ms on the finished page before looping
startDelaynumber600 — ms before the first keystroke
soundbooleanfalse — synthesized clicks, bell, and return
interactivebooleantrue — click it and type, or click its keys
labelstringnone — text on a chrome nameplate on the front
onDone() => voidcalled each time the last character lands
classNamestringReact only
classstringSvelte only

color paints the enamel of the body and paper rest; its highlights, shadows, and gloss are mixed from that one color. Chrome, rubber, keys, and ribbon stay as they are. The sheet is ivory, a touch dimmer in dark mode so it doesn't glare.