Button with CSS Character Stagger
JavaScript splits the button label into individual character spans with incrementing transition-delay values. On hover each character slides up via translateY, revealing a text-shadow duplicate below it. The background panel simultaneously shrinks its inset.
Code
<a href="#" aria-label="Staggering button" class="btn-animate-chars">
<div class="btn-animate-chars__bg"></div>
<span data-button-animate-chars="" class="btn-animate-chars__text">Staggering Button</span>
</a>.btn-animate-chars {
color: #131313;
cursor: pointer;
border-radius: .25em;
flex-grow: 1;
justify-content: center;
align-items: center;
max-width: 12em;
padding: 1em;
font-size: 1em;
line-height: 1;
text-decoration: none;
display: flex;
position: relative;
}
.btn-animate-chars__text {
white-space: nowrap;
line-height: 1.3;
}
/* Characters */
.btn-animate-chars [data-button-animate-chars] {
overflow: hidden;
position: relative;
display: inline-block;
}
.btn-animate-chars [data-button-animate-chars] span {
display: inline-block;
position: relative;
text-shadow: 0px 1.3em currentColor;
transform: translateY(0em) rotate(0.001deg);
transition: transform 0.6s cubic-bezier(0.625, 0.05, 0, 1);
}
.btn-animate-chars:hover [data-button-animate-chars] span {
transform: translateY(-1.3em) rotate(0.001deg);
}
/* Background */
.btn-animate-chars__bg {
background-color: #efeeec;
border-radius: .25em;
position: absolute;
inset: 0;
transition: inset 0.6s cubic-bezier(0.625, 0.05, 0, 1);
}
.btn-animate-chars:hover .btn-animate-chars__bg {
inset: 0.125em;
}function initButtonCharacterStagger() {
const offsetIncrement = 0.01; // Transition offset increment in seconds
const buttons = document.querySelectorAll('[data-button-animate-chars]');
buttons.forEach(button => {
const text = button.textContent; // Get the button's text content
button.innerHTML = ''; // Clear the original content
[...text].forEach((char, index) => {
const span = document.createElement('span');
span.textContent = char;
span.style.transitionDelay = `${index * offsetIncrement}s`;
// Handle spaces explicitly
if (char === ' ') {
span.style.whiteSpace = 'pre'; // Preserve space width
}
button.appendChild(span);
});
});
}
// Initialize Button Character Stagger Animation
document.addEventListener('DOMContentLoaded', () => {
initButtonCharacterStagger();
});Guide
How It Works
The script reads the text content of each [data-button-animate-chars] element, clears it, and re-inserts each character as an individual span with an incrementing transition-delay. CSS then handles the hover animation with translateY and text-shadow.
Stagger Speed
Adjust offsetIncrement in the script to change the delay between each character. The default is 0.01s. Increase it for a more pronounced stagger, decrease it for near-simultaneous movement.
Text Duplicate
Each span uses text-shadow: 0px 1.3em currentColor as an invisible duplicate sitting 1.3em below. The container clips overflow, so the shadow is hidden at rest. On hover translateY(-1.3em) pulls the shadow into view while the original exits above.
Background Shrink
The .btn-animate-chars__bg div transitions its inset from 0 to 0.125em on hover, creating a subtle shrink effect that adds depth to the interaction.
GSAP SplitText
If your project already uses GSAP SplitText, you can replace the custom splitting script with it and apply transition-delay values via GSAP instead.