Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Text

Pro

Backspace Text for React

Text that types a line, backspaces only as far as it must and types the correction, as a person fixing a typo would. For React with Base UI, when a line should read as a thought being revised.

Framework

Backspace Text types a line behind a caret, then corrects it: it deletes back only as far as the next line shares its start, and types the rest from there. "Build a website." loses "website." and becomes "Build an app.", which in turn becomes "Build a business.", so the line reads as one thought being revised rather than three lines in a row. A typo works the same way: type it, notice, backspace two letters, carry on.

Use it for a hero line that rewrites its last word, a message typed with a slip and fixed, or a command edited before it runs. To type and delete whole lines instead, use Typing Text; to swap a word without typing, Flip Words. A small timer moves it one keystroke at a time and a CSS keyframe animation blinks the caret, with no animation library; with reduced motion the lines show whole.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import { BackspaceText } from "@/components/ui/backspace-text";
<BackspaceText text={["Build a website.", "Build an app."]} />

Demos

Typo

loop={false} stops on the last line, so a typo is typed, fixed and left alone. A short pause is the moment the slip is noticed; delay is the wait before the first character.

Preview width

Command

A line that shares all but its end with the next is only extended: here the closing quote goes and the message grows. cursor="block" draws a terminal's caret, deleteSpeed paces each backspace, and trigger="view" waits, caret blinking, until the text scrolls into view.

Preview width

Accessibility

Screen readers hear the text, never the keystrokes: a visually hidden copy says the line it stops on, or every line once, in order, when it loops; the typed text and the caret are hidden from them. The caret blinks once a second, well under the three flashes a second that can trigger seizures, and stops with reduced motion, where each line shows whole, and a script that stops shows only its last line.

FAQ

How far back does it delete?

To the longest start the line shares with the next one. "I write code." and "I write apps." share "I write ", so only "code." goes. Lines with nothing in common are deleted whole, as Typing Text would.

Does it loop?

Yes, by default: after the last line it corrects back into the first, from what the two share, and goes round again. With loop={false} it stops on the last line, typed, and calls onComplete.

How do I tune the speed?

speed is the average time per typed character (60 ms), give or take jitter; deleteSpeed the time per backspace (40 ms); pause how long a whole line stays before it's corrected (1400 ms). A slower deleteSpeed makes the correction read as deliberate.

How is it different from Typing Text?

Typing Text types each line and deletes it whole before the next. Backspace Text keeps what the lines share and retypes only what changes, so it suits lines that differ by a word or a letter.

What do screen readers hear?

What the text settles on: with loop={false}, the last line only, so a deliberate typo is never read out. When it loops, every line once, one after the other.

Will the text around it move as it types?

It grows and shrinks like any text, so in a centred heading the line re-centres as it changes. To hold the layout, give its container a fixed width that fits the longest line.

What happens with reduced motion?

Nothing is typed. A script that stops shows its last line from the start; one that loops shows each line whole, swapping when the next would be typed, and the caret holds still.

API Reference

BackspaceText

<span> data-slot="backspace-text"

PropType
textstring[]

The lines in turn: each is typed, then deleted only as far back as it shares its start with the next, which is typed from there.

cursor?"bar" | "block" | "underscore" | "none"

The caret after the text. @default "bar".

Type
"bar" | "block" | "underscore" | "none"
Default
"bar"
delay?number

The wait before the first character, in milliseconds. @default 0.

Default
0
deleteSpeed?number

The time to delete one character, in milliseconds. @default 40.

Default
40
jitter?number

How much each keystroke's wait varies, from 0 (even) to 1. @default 0.5.

Default
0.5
loop?boolean

Whether the last line is corrected back into the first and it all starts over; otherwise it stops on the last line.

Default
true
onComplete?() → void

Called once the last line is typed, when it doesn't loop.

Returns
void
pause?number

How long a typed line stays before it's corrected, in milliseconds. @default 1400.

Default
1400
speed?number

The time to type one character, on average, in milliseconds. @default 60.

Default
60
trigger?"mount" | "view"

When the typing starts: as soon as the page shows it, or once it scrolls into view. @default "mount".

Type
"mount" | "view"
Default
"mount"
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-state?string
All 97 text effects