Shuffle
A cyberpunk-inspired text reveal with random character decryption effect.
Overview
A text animation simulating a decryption process by cycling through random characters before settling on the final string. Perfect for futuristic UIs, data visualizations, or impactful headlines.
Features
- Rapid random character cycling
- Configurable reveal speed
- Zero external dependencies
- Auto-play on mount
- Monospace font optimized
- Hover effect built-in
Preview
ZENBLOCKS
Installation
Method 1: CLI (Recommended)
Install via the ZenBlocks CLI to automatically handle dependencies:
npx shadcn@latest add https://zenblocks-three.vercel.app/r/shuffle.jsonMethod 2: Manual
-
Install Dependencies
No external dependencies required.
-
Copy the Source Code
Copy the code below into
components/zenblocks/shuffle.tsx.
Click to expand source
"use client";
import React, { useState, useEffect, useRef } from "react";
import { cn } from "@/lib/utils";
interface ShuffleProps extends React.HTMLAttributes<HTMLSpanElement> {
text: string;
duration?: number;
delay?: number;
}
export function Shuffle({
text = "Shuffle",
duration = 0.5,
className,
...props
}: ShuffleProps) {
const [displayText, setDisplayText] = useState(text);
const [isHovered, setIsHovered] = useState(false);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const originalText = useRef(text);
useEffect(() => {
originalText.current = text;
setDisplayText(text);
}, [text]);
const startShuffle = () => {
if (intervalRef.current) clearInterval(intervalRef.current);
let iteration = 0;
const steps = duration * 30;
const increment = text.length / steps;
intervalRef.current = setInterval(() => {
setDisplayText((prev) =>
originalText.current
.split("")
.map((letter, index) => {
if (index < iteration) {
return originalText.current[index];
}
return String.fromCharCode(65 + Math.floor(Math.random() * 26));
})
.join("")
);
if (iteration >= originalText.current.length) {
if (intervalRef.current) clearInterval(intervalRef.current);
setDisplayText(originalText.current);
}
iteration += increment;
}, 30);
};
const handleMouseEnter = (e: React.MouseEvent<HTMLSpanElement>) => {
setIsHovered(true);
startShuffle();
props.onMouseEnter?.(e);
};
useEffect(() => {
startShuffle();
return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
}
}, []);
return (
<span
className={cn("inline-block whitespace-nowrap", className)}
onMouseEnter={handleMouseEnter}
{...props}
>
{displayText}
</span>
);
}Usage
import { Shuffle } from "@/components/zenblocks/shuffle";
<h1>
<Shuffle text="ACCESS GRANTED" />
</h1>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| text | string | - | Final string to display |
| duration | number | 1.0 | Animation time in seconds |
| className | string | - | Styling for text span |
Accessibility
- Recommended to detect
prefers-reduced-motionand skip animation - Use
aria-labelon parent to announce final text immediately - Ensure sufficient contrast ratios
- Avoid for critical information that needs instant readability
Customization
Typography
Works best with monospaced fonts to prevent layout jitter during character changes.
<Shuffle
className="font-mono tracking-widest"
text="SYSTEM_READY"
/>Duration
<Shuffle text="LOADING" duration={2.0} />Motion Behavior
- Cycle: Replaces characters every 30-50ms
- Reveal: Linearly increases index of "solved" characters left to right
- Character set: Random uppercase letters (A-Z)
Performance Notes
- Uses
setIntervalfor animation loop - Cleaned up on unmount
- If switching text rapidly, use unique
keyprop to force re-mount
Examples
Badge
<div className="badge">
<span className="dot" />
<Shuffle text="v2.0 Live" />
</div>Hero Headline
<h1 className="text-6xl font-bold">
<Shuffle text="WELCOME" duration={1.5} />
</h1>Notes
- Avoid using for long paragraphs (shifting text can move layout)
- Best for short, impactful phrases
- Character width changes can cause jitter with non-monospace fonts
- Component is client-side only