Skip to content

Text Flow Examples

The examples below assume that you have imported the component and set up the theme.

Basic

Slot the starting text, then assign new strings to value from script. Every assignment animates from the current text to the next one, character by character.

Reduced Motion

Your system is set to reduce motion, so the animations shown on this page will not play for you.

0
<div class="text-flow-basic">
<sv-text-flow class="flow">0</sv-text-flow>
<button type="button" class="shuffle-button">Shuffle</button>
<script type="application/x-typescript" module>
const textFlow = document.querySelector<TextFlow>(".text-flow-basic .flow")!;
const shuffleButton = document.querySelector<HTMLButtonElement>(".text-flow-basic .shuffle-button")!;
shuffleButton.addEventListener("click", () => {
textFlow.value = String(Math.floor(Math.random() * 10000));
});
</script>
</div>
<style>
.text-flow-basic {
display: flex;
flex-direction: column;
gap: 1em;
align-items: center;
padding-block: 1em;
.flow::part(text) {
font-size: 2rem;
}
.shuffle-button {
padding: 0.5em 2em;
color: var(--sv-accent-content);
cursor: pointer;
background-color: var(--sv-accent);
border: none;
border-radius: var(--sv-bdr-rad-md);
}
}
</style>

Smart Diff

use-diff switches the character diff from index comparison to edit distance, so unchanged characters stay put. The swap only changes the red channel between 255 and 0: with use-diff nothing after it moves, while the default index mode drags the change through every following character.

rgb(255, 0, 50) default
rgb(255, 0, 50) use-diff
<div class="text-flow-smart-diff">
<div class="variant">
<sv-text-flow class="flow">rgb(255, 0, 50)</sv-text-flow>
<span>default</span>
</div>
<div class="variant">
<sv-text-flow class="flow" use-diff>rgb(255, 0, 50)</sv-text-flow>
<span>use-diff</span>
</div>
<button type="button" class="swap-button">Swap</button>
<script type="application/x-typescript" module>
const colors = ["rgb(255, 0, 50)", "rgb(0, 0, 50)"];
const flows = document.querySelectorAll<TextFlow>(".text-flow-smart-diff .flow");
const swapButton = document.querySelector<HTMLButtonElement>(".text-flow-smart-diff .swap-button")!;
swapButton.addEventListener("click", () => {
for (const flow of flows) {
flow.value = colors.find(color => color !== flow.value)!;
}
});
</script>
</div>
<style>
.text-flow-smart-diff {
display: grid;
gap: 1.25em;
justify-items: center;
padding-block: 1em;
.variant {
display: grid;
gap: 0.4em;
justify-items: center;
span {
font-size: 0.8em;
color: var(--sv-gray-3);
}
}
.flow::part(text) {
font-family: monospace;
font-size: 1.25rem;
}
.swap-button {
padding: 0.5em 2em;
color: var(--sv-accent-content);
cursor: pointer;
background-color: var(--sv-accent);
border: none;
border-radius: var(--sv-bdr-rad-md);
}
}
</style>

Timing

--sv-text-flow-dur and --sv-text-flow-ease control the duration and easing of the roll. The duration and timing-function attributes override the variables, but also skip the reduced-motion fallback baked into --sv-text-flow-dur, so prefer the variables.

4250 default
4250 --sv-text-flow-dur: 1.2s
4250 --sv-text-flow-ease: ease-in-out
<div class="text-flow-timing">
<div class="variant">
<sv-text-flow class="flow">4250</sv-text-flow>
<span>default</span>
</div>
<div class="variant">
<sv-text-flow class="flow slow">4250</sv-text-flow>
<span>--sv-text-flow-dur: 1.2s</span>
</div>
<div class="variant">
<sv-text-flow class="flow eased">4250</sv-text-flow>
<span>--sv-text-flow-ease: ease-in-out</span>
</div>
<button type="button" class="shuffle-button">Shuffle</button>
<script type="application/x-typescript" module>
const flows = document.querySelectorAll<TextFlow>(".text-flow-timing .flow");
const shuffleButton = document.querySelector<HTMLButtonElement>(".text-flow-timing .shuffle-button")!;
shuffleButton.addEventListener("click", () => {
const value = String(1000 + Math.floor(Math.random() * 9000));
for (const flow of flows) {
flow.value = value;
}
});
</script>
</div>
<style>
.text-flow-timing {
display: grid;
gap: 1.25em;
justify-items: center;
padding-block: 1em;
.variant {
display: grid;
gap: 0.4em;
justify-items: center;
span {
font-size: 0.8em;
color: var(--sv-gray-3);
}
}
.flow::part(text) {
font-size: 1.5rem;
}
.slow {
--sv-text-flow-dur: 1.2s;
}
.eased {
--sv-text-flow-ease: ease-in-out;
}
.shuffle-button {
padding: 0.5em 2em;
color: var(--sv-accent-content);
cursor: pointer;
background-color: var(--sv-accent);
border: none;
border-radius: var(--sv-bdr-rad-md);
}
@media (prefers-reduced-motion: reduce) {
.slow {
--sv-text-flow-dur: 0s;
}
}
}
</style>

Styling

The host renders as display: contents, so box styles like padding and background belong on ::part(text). Inherited text properties, like font and color, can sit on the host itself. Digits are tabular out of the box, so numbers keep a steady width while they roll.

82,451 inherited font styles
+12.4% ::part(text)
<div class="text-flow-styling">
<div class="variant">
<sv-text-flow class="headline">82,451</sv-text-flow>
<span>inherited font styles</span>
</div>
<div class="variant">
<sv-text-flow class="badge">+12.4%</sv-text-flow>
<span>::part(text)</span>
</div>
</div>
<style>
.text-flow-styling {
display: grid;
gap: 1.5em;
justify-items: center;
padding-block: 1em;
.variant {
display: grid;
gap: 0.4em;
justify-items: center;
span {
font-size: 0.8em;
color: var(--sv-gray-3);
}
}
.headline {
font-size: 1.8rem;
font-weight: 700;
color: var(--sv-accent);
}
.badge {
--sv-accent: light-dark(oklch(63% 0.1197 150deg), oklch(63% 0.105 150deg));
&::part(text) {
padding: 0.2em 0.6em;
font-weight: 600;
color: var(--sv-accent);
background-color: color-mix(in srgb, var(--sv-accent) 12%, transparent);
border-radius: calc(infinity * 1px);
}
}
}
</style>