Types a phrase, selects it with a highlight that sweeps back right to left, deletes it and types the next one, forever. Both phrases share one set of keyframes, offset by a delay.
Also known as: typing loop with highlight, text selection animation css, retype text effect.
<div class="txt-select-and-retype-loop" aria-label="Write faster. Build better."> <span class="txt-select-and-retype-loop-phrase" aria-hidden="true"><span style="--i: 0">W</span><span style="--i: 1">r</span><span style="--i: 2">i</span><span style="--i: 3">t</span><span style="--i: 4">e</span><span style="--i: 5"> </span><span style="--i: 6">f</span><span style="--i: 7">a</span><span style="--i: 8">s</span><span style="--i: 9">t</span><span style="--i: 10">e</span><span style="--i: 11">r</span><span style="--i: 12">.</span></span> <span class="txt-select-and-retype-loop-phrase" aria-hidden="true"><span style="--i: 0">B</span><span style="--i: 1">u</span><span style="--i: 2">i</span><span style="--i: 3">l</span><span style="--i: 4">d</span><span style="--i: 5"> </span><span style="--i: 6">b</span><span style="--i: 7">e</span><span style="--i: 8">t</span><span style="--i: 9">t</span><span style="--i: 10">e</span><span style="--i: 11">r</span><span style="--i: 12">.</span></span> </div>
.txt-select-and-retype-loop {
--c: var(--txt-color, #3b82f6);
font-size: var(--txt-size, 32px);
font-family: system-ui, sans-serif;
font-weight: 700;
line-height: 1.2;
display: inline-grid;
justify-items: start;
white-space: nowrap;}
/* each phrase owns 3s of a 6s loop */
.txt-select-and-retype-loop-phrase {
position: relative;
isolation: isolate;
grid-area: 1 / 1;
animation: txt-select-and-retype-loop-life 6s step-end infinite;
}
/* delays are negative (slot + beat - 6s) so every element starts mid-cycle and
the base styles are the readable state that reduced motion falls back to */
.txt-select-and-retype-loop-phrase + .txt-select-and-retype-loop-phrase {
--slot: 3s;
visibility: hidden;
animation-delay: -3s;
}
/* the selection highlight sweeps from the end back to the start */
.txt-select-and-retype-loop-phrase::before {
content: "";
position: absolute;
inset: 0.05em -0.04em;
z-index: -1;
border-radius: 0.1em;
background: color-mix(in srgb, var(--c) 30%, transparent);
transform: scaleX(0);
transform-origin: right;
animation: txt-select-and-retype-loop-select 6s var(--slot, 0s) infinite;
}
.txt-select-and-retype-loop-phrase span {
position: relative;
animation: txt-select-and-retype-loop-key 6s step-end calc(var(--slot, 0s) + 0.2s + var(--i) * 80ms - 6s) infinite;
}
.txt-select-and-retype-loop-phrase span::after {
content: "";
position: absolute;
top: 0.12em;
bottom: 0.08em;
right: -0.05em;
width: 0.07em;
background: var(--c);
opacity: 0;
animation: txt-select-and-retype-loop-caret 6s step-end calc(var(--slot, 0s) + 0.2s + var(--i) * 80ms - 6s) infinite;
}
.txt-select-and-retype-loop-phrase span:last-child::after {
animation-name: txt-select-and-retype-loop-wait;
}
@keyframes txt-select-and-retype-loop-life {
0% { visibility: visible; }
43.33% { visibility: hidden; }
}
@keyframes txt-select-and-retype-loop-key {
0% { opacity: 1; }
40% { opacity: 0; }
}
@keyframes txt-select-and-retype-loop-caret {
0% { opacity: 1; }
1.33% { opacity: 0; }
}
/* the last letter keeps the caret, blinking, until the selection starts */
@keyframes txt-select-and-retype-loop-wait {
0% { opacity: 1; }
6% { opacity: 0; }
9% { opacity: 1; }
12.33% { opacity: 0; }
}
@keyframes txt-select-and-retype-loop-select {
0%, 31.67% { transform: scaleX(0); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); }
38.33%, 43.33% { transform: scaleX(1); }
43.34%, 100% { transform: scaleX(0); }
}
@media (prefers-reduced-motion: reduce) {
.txt-select-and-retype-loop, .txt-select-and-retype-loop *, .txt-select-and-retype-loop::before, .txt-select-and-retype-loop::after, .txt-select-and-retype-loop *::before, .txt-select-and-retype-loop *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--txt-color on the element or a parent.
Currently previewing #3b82f6.--txt-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 40px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.