One of my favorite CSS animation tricks is creating a scrambled text effect without JavaScript. The key is using the steps()
timing function combined with a container that masks overflow. You start by stacking all possible character variations in a tall container, with each variation on its own line. Then, animate it using translateY
to create the illusion of characters changing.
The magic happens with steps()
- instead of smoothly sliding between positions, it jumps instantly between them. By setting something like steps(30)
, you tell CSS to show 30 distinct frames rather than a fluid motion. Combined with translateY moving through your stacked characters, this creates that satisfying scrambled effect where text appears to randomly change.
To prevent the animation from looking like a simple scroll, hide everything outside your target height with overflow: hidden
on the container. The final result is a convincing scramble effect that looks more complex than its straightforward implementation.
What makes this technique particularly elegant is that it's pure CSS - no need for JavaScript to swap characters. The text variations are already in your DOM, just hidden from view until the animation reveals them frame by frame.
import clsx from "clsx";
import { useMemo } from "react";
import { randomString } from "../../../const/random-string";
interface ComponentProps {
children?: string;
debug?: boolean;
duration?: number;
delay?: number;
lines?: number;
}
/**
* Add the following CSS to your stylesheet:
*
* [data-key-container]:hover [data-key] {
* animation: movement 0.5s both steps(
* calc(var(--lines) + 1)
* );
* animation-duration: var(--duration, 500ms);
* animation-delay: var(--delay, 0ms);
* }
*
* @keyframes movement {
* from {
* transform: translateY(0);
* }
* to {
* transform: translateY(
* calc(-1 * calc(var(--lines) + 1) * 20px)
* );
* }
* }
*/
export function Component({
children = "Book a call",
debug = false,
duration = 500,
delay = 40,
lines = 8,
}: ComponentProps) {
const chars = useMemo(() => {
return Array.from({ length: lines }).map(() =>
randomString(children.length),
);
}, [lines]);
return (
<a
data-key-container
href="https://cal.com/ninio/get-to-know"
target="_blank"
className={clsx(
"flex items-center", // layout
"bg-action-primary ring ring-action-primary", // colors
"gap-2 px-4", // spacings
"cursor-pointer transition-all", // interactions
"relative inset-shadow-inset h-8 rounded-full",
"font-medium font-mono text-content-inverted text-sm", // fonts
)}
rel="noreferrer"
>
<span
className={clsx(
"block h-5 font-mono",
debug && "[text-shadow:_0_0_1px_var(--color-black)]",
!debug && "overflow-hidden",
)}
>
<span className="pointer-events-none relative inline-flex">
{children.split("").map((letter, idx) => {
return (
<span
key={letter + idx.toString()}
data-key
className="flex flex-col"
style={{
// @ts-expect-error: Vars not allowed in typescript
"--duration": `${duration}ms`,
"--lines": lines,
"--delay": `${idx * delay}ms`,
}}
>
<span className="block h-5" suppressHydrationWarning>
{letter}
</span>
{Array.from({ length: lines }).map((_, i) => (
<span
key={i.toString()}
className="block h-5"
suppressHydrationWarning
>
{chars[i][idx]}
</span>
))}
<span className="block h-5" suppressHydrationWarning>
{letter}
</span>
</span>
);
})}
</span>
</span>
</a>
);
}
Design is more than aesthetics – it's the invisible force that shapes how we experience the digital world. For 16 years, I've been driven by the belief that exceptional frontend development can transform good design into unforgettable experiences. Every pixel, every interaction, every line of code serves a purpose in this greater vision.
I operate at the intersection of creativity and technology, where design principles meet development expertise. This convergence isn't just about making things look beautiful – it's about crafting interfaces that feel natural, intuitive, and effortless. As both designer and developer, I bridge the gap between imagination and implementation, ensuring nothing is lost in translation.
My work is guided by a simple truth: the best digital experiences are those that users don't have to think about. They just work, seamlessly and beautifully, across every device and platform. This is what I strive for in every project, pushing the boundaries of what's possible while maintaining rock-solid reliability.