Typewriter Retype
Text erases and retypes itself character by character on hover — like a live terminal cursor.
Typewriter Retype the 12th of 21 designs in the 21 CSS Button Hover Effects collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
The code
<button class="bhe-12__btn"> <span class="bhe-12__text">console.log()</span> <span class="bhe-12__cursor"></span> </button>
.bhe-12__btn {
padding: 12px 32px;
font-size: 13.5px;
font-weight: 500;
border-radius: 8px;
cursor: pointer;
letter-spacing: 0.02em;
color: inherit;
background: transparent;
}
.bhe-12__btn {
font-family: monospace;
}
.bhe-12__cursor {
display: inline-block;
width: 2px;
height: 13px;
background: currentColor;
margin-left: 2px;
vertical-align: middle;
opacity: 0;
}
.bhe-12__btn:hover .bhe-12__cursor {
animation: blink 0.6s step-end infinite;
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.bhe-12__btn,
.bhe-12__btn * {
animation: none !important;
}
}
document.querySelectorAll(".bhe-12__btn").forEach(function (btn) {
const txt = btn.querySelector(".bhe-12__text");
if (!txt) return;
const full = txt.textContent;
btn.addEventListener("mouseenter", function () {
let i = full.length;
const erase = setInterval(function () {
txt.textContent = full.slice(0, --i);
if (i === 0) {
clearInterval(erase);
retype();
}
}, 55);
function retype() {
let j = 0;
const write = setInterval(function () {
txt.textContent = full.slice(0, ++j);
if (j === full.length) clearInterval(write);
}, 55);
}
});
});