Skip to content

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.

Dashboard

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.

Active regioneu-west-1

<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.

ACCESS GRANTED

<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.

50,000

<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>