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
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/typewriterOr install straight from the public GitHub source registry:
pnpm dlx shadcn@latest add radiumcoders/23rd.dev/typewriterUsage
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
| Prop | Type | Default |
|---|---|---|
color | string | "#8EC3B5" — body enamel, any CSS color |
text | string | a short letter — \n breaks a line |
speed | number | 12 — characters per second |
humanize | boolean | true — uneven keys, rests after punctuation |
jitter | number | 0.3 — 0–1, per-letter baseline, tilt, and ink |
columns | number | 32 — line length on the sheet, 16–60 |
loop | boolean | true — pull the sheet out and type it again |
hold | number | 2600 — ms on the finished page before looping |
startDelay | number | 600 — ms before the first keystroke |
sound | boolean | false — synthesized clicks, bell, and return |
interactive | boolean | true — click it and type, or click its keys |
label | string | none — text on a chrome nameplate on the front |
onDone | () => void | called each time the last character lands |
className | string | React only |
class | string | Svelte 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.