Text scramble
Text scramble renders a single <span> that "decodes" its text: whenever text changes each character cycles through random glyphs before settling on its final one, rippling across the string from left to right. It is ideal for dashboard widgets that refresh, status reveals and headline transitions.
TIP
The decode respects prefers-reduced-motion: when reduced motion is requested the text swaps straight to its target instead of scrambling. The current text is always exposed as the accessible label, so screen readers read the real value rather than the random glyphs.
The component also exposes two imperative methods through a template ref: set(text) decodes to new text, and replay() re-runs the decode on the current text. A finished event fires once the animation settles.
Props
text: string
The text that is displayed. Whenever this value changes, the label decodes from the text on screen to the new one through random characters.
characters?: string
The pool of characters the scramble cycles through while decoding.
Default: A-Za-z0-9
duration?: number
The total decode duration, in milliseconds.
Default: 900
skip-unchanged?: boolean
Keeps characters that are identical at the same index static, so only the parts that actually change decode. Turn this off to re-scramble the whole string.
Default: true
speed?: number
How often each cell swaps to a new random character while decoding, in milliseconds. Lower values cycle faster.
Default: 45
stagger?: number
How much the per-character reveal ripples across the string, between 0 and 1. 0 decodes every character together, 1 reveals them one after another.
Default: 0.5
Emits
finished: []
Triggered when the decode animation settles on the final text.
Examples
Widget refresh
Decode a value every time a dashboard tile refreshes, so the update reads as a live recompute rather than a silent swap.
<template>
<FluxFlex
align="start"
direction="vertical"
:gap="15">
<FluxPane style="width: 261px;">
<FluxPaneBody>
<FluxFlex
align="start"
direction="vertical"
:gap="3">
<span style="color: var(--gray-500); font-size: 13px; font-weight: 500;">Active region</span>
<FluxVisualTextScramble
:text="region"
style="font-size: 24px; font-weight: 700;"/>
</FluxFlex>
</FluxPaneBody>
</FluxPane>
<FluxSecondaryButton
icon-leading="rotate"
label="Refresh"
@click="refresh"/>
</FluxFlex>
</template>
<script
lang="ts"
setup>
import { FluxFlex, FluxPane, FluxPaneBody, FluxSecondaryButton } from '@flux-ui/components';
import { FluxVisualTextScramble } from '@flux-ui/visuals';
import { ref } from 'vue';
const regions = ['eu-west-1', 'us-east-1', 'ap-south-1', 'sa-east-1'];
const index = ref(0);
const region = ref(regions[0]);
function refresh(): void {
index.value = (index.value + 1) % regions.length;
region.value = regions[index.value];
}
</script>Replay
Call replay() through a template ref to re-run the decode on demand, without changing the text.
<template>
<FluxFlex
align="start"
direction="vertical"
:gap="15">
<FluxVisualTextScramble
ref="scramble"
text="ACCESS GRANTED"
style="color: var(--success-600); font-size: 24px; font-weight: 700; letter-spacing: 0.08em;"/>
<FluxSecondaryButton
icon-leading="rotate"
label="Replay"
@click="replay"/>
</FluxFlex>
</template>
<script
lang="ts"
setup>
import { FluxFlex, FluxSecondaryButton } from '@flux-ui/components';
import { FluxVisualTextScramble } from '@flux-ui/visuals';
import { useTemplateRef } from 'vue';
const scrambleRef = useTemplateRef<InstanceType<typeof FluxVisualTextScramble>>('scramble');
function replay(): void {
scrambleRef.value?.replay();
}
</script>Characters
Narrow the scramble pool with characters, for example to digits only so a counter decodes through numbers.
<template>
<FluxFlex
align="center"
direction="horizontal"
:gap="15">
<FluxSecondaryButton
icon-leading="minus"
@click="step(-1843)"/>
<FluxVisualTextScramble
characters="0123456789"
:text="value.toLocaleString('en-US')"
style="min-width: 120px; font-size: 33px; font-weight: 700; text-align: center;"/>
<FluxSecondaryButton
icon-leading="plus"
@click="step(1843)"/>
</FluxFlex>
</template>
<script
lang="ts"
setup>
import { FluxFlex, FluxSecondaryButton } from '@flux-ui/components';
import { FluxVisualTextScramble } from '@flux-ui/visuals';
import { ref } from 'vue';
const value = ref(50000);
function step(delta: number): void {
value.value = Math.max(0, value.value + delta);
}
</script>