Dependency-free text roll animation for vanilla JS, React, and Vue.
See the codeDependency-free text roll animation for tiny, tactile UI labels.
Choose your preferred package manager:
npm
npm install slot-text
pnpm
pnpm add slot-text
Bun
bun add slot-text
Yarn
yarn add slot-text
import "slot-text/style.css";
import { slotText, chromatic } from "slot-text";
const label = slotText(document.querySelector("#copy")!, "Copy");
// the classic Copy → Copied → Copy, in one call
label.flash("Copied", { enter: { color: chromatic() } });
That's it — import the CSS once, attach to an element, roll text.
The included playground has one button that flashes Copy → Copied → Copy.
npm run build
python3 -m http.server
Open http://localhost:8000/examples/basic/.
| Method | What it does |
|---|---|
set(text, options?) | Roll to new text — the text stays |
flash(text, options?) | Roll in, then auto-revert to the previous text |
destroy() | Clean up |
const label = slotText(element, "Copy", options);
label.set("Copied"); // permanent change
label.set("Copy", { direction: "down" });
label.set("Changes saved", {
rollBy: "word",
duration: 180,
stagger: 25,
}); // faster, word-by-word roll
label.flash("Copied"); // temporary — rolls back after 1.4s
label.destroy();
💡
flashis spam-safe — repeat clicks restart the revert timer instead of queuing extra rolls, and an explicitset()cancels any pending revert.
label.flash("Copied", {
revertAfter: 1400, // ms before rolling back
enter: { direction: "up", color: chromatic() }, // roll-in
exit: { direction: "down" }, // roll-back
});
import "slot-text/style.css";
import { SlotText } from "slot-text/react";
import { chromatic } from "slot-text";
<SlotText
text={copied ? "Copied" : "Copy"}
options={{ direction: copied ? "up" : "down", color: copied ? chromatic() : undefined }}
/>
<script setup lang="ts">
import "slot-text/style.css";
import { SlotText } from "slot-text/vue";
</script>
<template>
<SlotText text="Copied" :options="{ direction: 'up' }" />
</template>
import "slot-text/style.css";
import { createSignal } from "solid-js";
import { slotText } from "slot-text/solid";
const [label, setLabel] = createSignal("Copy");
<button aria-label={label()} onClick={() => setLabel("Copied")}>
<span use:slotText={{ text: label(), options: { direction: "up" } }} />
</button>
<script lang="ts">
import "slot-text/style.css";
import { slotText } from "slot-text/svelte";
let label = "Copy";
</script>
<button aria-label={label} on:click={() => label = "Copied"}>
<span use:slotText={{ text: label, options: { direction: "up" } }}></span>
</button>
| Option | Default | Description |
|---|---|---|
direction | "down" | Roll direction: "up" or "down" |
rollBy | "character" | Roll by "character" or "word" |
stagger | 45 | Delay between segments (ms); lower is faster |
duration | 300 | Per-segment animation time (ms); lower is faster |
exitOffset | 50 | Delay before the old character exits (ms) |
easing | springy bezier | CSS easing function |
bounce | 0.6 | Overshoot amount |
color | — | Color string, or (index, total) => string |
colorFade | 280 | Fade back to base color (ms) |
skipUnchanged | true | Don't re-roll identical characters |
interrupt | true | See below 👇 |
interruptinterrupt: true (default) — cuts off any roll in flight, starts fresh.interrupt: false — lets the current roll finish; the latest call plays
after it lands, duplicates are dropped. Ideal for spam-prone buttons.chromatic()Built-in rainbow color helper — pass it as color for a per-segment hue sweep.
By default, each user-perceived character animates in its own measured cell.
Set rollBy: "word" to keep each word together and roll it as one unit.
✅ Great with: monospace fonts, proportional Latin / Cyrillic / Greek (Geist, Inter, SF, …), italics, glyphs with overhang.
⚠️ Character mode tradeoffs:
AV sit slightly looser (invisible at label sizes).fi, fl, coding ligatures).Intl.Segmenter is available; older browsers fall back to Unicode code points.line-height: 1.3).Word mode preserves kerning, ligatures and joined-script shaping inside each word, but the whole word rolls together.
In short: ideal for short labels, numbers, statuses and commands — in essentially any font you'd use for those.
buildSlotText, animateSlotText, chromatic.(top 24 of 30)
1,152 followers · starred Jun 2026
4,596 followers · starred Sep 2026
1,983 followers · starred Jun 2026
5,603 followers · starred Jul 2026
Dependency-free text roll animation for vanilla JS, React, and Vue.
See the codeDependency-free text roll animation for tiny, tactile UI labels.
Choose your preferred package manager:
npm
npm install slot-text
pnpm
pnpm add slot-text
Bun
bun add slot-text
Yarn
yarn add slot-text
import "slot-text/style.css";
import { slotText, chromatic } from "slot-text";
const label = slotText(document.querySelector("#copy")!, "Copy");
// the classic Copy → Copied → Copy, in one call
label.flash("Copied", { enter: { color: chromatic() } });
That's it — import the CSS once, attach to an element, roll text.
The included playground has one button that flashes Copy → Copied → Copy.
npm run build
python3 -m http.server
Open http://localhost:8000/examples/basic/.
| Method | What it does |
|---|---|
set(text, options?) | Roll to new text — the text stays |
flash(text, options?) | Roll in, then auto-revert to the previous text |
destroy() | Clean up |
const label = slotText(element, "Copy", options);
label.set("Copied"); // permanent change
label.set("Copy", { direction: "down" });
label.set("Changes saved", {
rollBy: "word",
duration: 180,
stagger: 25,
}); // faster, word-by-word roll
label.flash("Copied"); // temporary — rolls back after 1.4s
label.destroy();
💡
flashis spam-safe — repeat clicks restart the revert timer instead of queuing extra rolls, and an explicitset()cancels any pending revert.
label.flash("Copied", {
revertAfter: 1400, // ms before rolling back
enter: { direction: "up", color: chromatic() }, // roll-in
exit: { direction: "down" }, // roll-back
});
import "slot-text/style.css";
import { SlotText } from "slot-text/react";
import { chromatic } from "slot-text";
<SlotText
text={copied ? "Copied" : "Copy"}
options={{ direction: copied ? "up" : "down", color: copied ? chromatic() : undefined }}
/>
<script setup lang="ts">
import "slot-text/style.css";
import { SlotText } from "slot-text/vue";
</script>
<template>
<SlotText text="Copied" :options="{ direction: 'up' }" />
</template>
import "slot-text/style.css";
import { createSignal } from "solid-js";
import { slotText } from "slot-text/solid";
const [label, setLabel] = createSignal("Copy");
<button aria-label={label()} onClick={() => setLabel("Copied")}>
<span use:slotText={{ text: label(), options: { direction: "up" } }} />
</button>
<script lang="ts">
import "slot-text/style.css";
import { slotText } from "slot-text/svelte";
let label = "Copy";
</script>
<button aria-label={label} on:click={() => label = "Copied"}>
<span use:slotText={{ text: label, options: { direction: "up" } }}></span>
</button>
| Option | Default | Description |
|---|---|---|
direction | "down" | Roll direction: "up" or "down" |
rollBy | "character" | Roll by "character" or "word" |
stagger | 45 | Delay between segments (ms); lower is faster |
duration | 300 | Per-segment animation time (ms); lower is faster |
exitOffset | 50 | Delay before the old character exits (ms) |
easing | springy bezier | CSS easing function |
bounce | 0.6 | Overshoot amount |
color | — | Color string, or (index, total) => string |
colorFade | 280 | Fade back to base color (ms) |
skipUnchanged | true | Don't re-roll identical characters |
interrupt | true | See below 👇 |
interruptinterrupt: true (default) — cuts off any roll in flight, starts fresh.interrupt: false — lets the current roll finish; the latest call plays
after it lands, duplicates are dropped. Ideal for spam-prone buttons.chromatic()Built-in rainbow color helper — pass it as color for a per-segment hue sweep.
By default, each user-perceived character animates in its own measured cell.
Set rollBy: "word" to keep each word together and roll it as one unit.
✅ Great with: monospace fonts, proportional Latin / Cyrillic / Greek (Geist, Inter, SF, …), italics, glyphs with overhang.
⚠️ Character mode tradeoffs:
AV sit slightly looser (invisible at label sizes).fi, fl, coding ligatures).Intl.Segmenter is available; older browsers fall back to Unicode code points.line-height: 1.3).Word mode preserves kerning, ligatures and joined-script shaping inside each word, but the whole word rolls together.
In short: ideal for short labels, numbers, statuses and commands — in essentially any font you'd use for those.
buildSlotText, animateSlotText, chromatic.(top 24 of 30)
1,152 followers · starred Jun 2026
4,596 followers · starred Sep 2026
1,983 followers · starred Jun 2026
5,603 followers · starred Jul 2026