defuss-shadcn / Primitives / text-rotate
Text RotateATM
Shows one line at a time from a short list and rolls on to the next - an infinite loop that pauses while hovered. Put it inline in a sentence or use it as a headline; it takes the font, size, color and line height of the text around it. CSS only, the same markup as daisyUI's text-rotate.
On this page (8)
§Default
Three lines in a big headline: each holds for its share of the 10s round, then rolls up to the next; after the last it rolls back to the first. Hover to pause.
§In a sentence
Inline in running text - the window takes the sentence's font and sits on its baseline; each line can have its own color. The window is as wide as its longest line, so the rotating word reads best at the end of a sentence.
§Centered headline
data-align='center' centers shorter lines in the window; emojis are just text.
§Duration
--text-rotate-duration is one full round through all lines (default 10s): here 4s and 16s.
§Direction and fade
data-direction='down' drops each next line in from above (same reading order); data-effect='fade' crossfades in place with a small drift.
§Line height
The window is 1lh - one line of whatever line-height the text has, so a loose line-height gives each line more room. Keep it at 1.2 or more: tighter than the glyphs themselves, the edges of the neighbouring lines peek into the window.
§Six lines, never pausing
Up to six lines; data-pause='none' keeps it rolling under the pointer (a ticker).
§CSS view file
/* -- Text Rotate component ------------------------------------------ */@layer components { /* daisyUI's text-rotate, CSS only: a .text-rotate holds one <span> of 2-6 lines (each a child element); one line shows at a time and the stack rolls on to the next - an infinite loop that pauses while hovered. The window is one line tall (1lh), so it works at any font-size and line-height, inline in a sentence or as a headline. The keyframes are picked from the line count (:has()); --text-rotate-duration sets one full round (default 10s). data-direction="down" rolls the other way, data-effect="fade" crossfades in place, data-pause="none" keeps it running under the pointer. Reduced motion shows the first line only. */ .text-rotate { --_n: 1; --_dur: var(--text-rotate-duration, 10s); display: inline-grid; height: 1lh; overflow: hidden; vertical-align: bottom; & > span { display: flex; flex-direction: column; animation: var(--_name, none) var(--_dur) cubic-bezier(0.65, 0, 0.35, 1) infinite; } & > span > * { display: block; height: 1lh; white-space: nowrap; } &[data-align="center"] > span > * { text-align: center; } &[data-align="end"] > span > * { text-align: end; } &:hover > span, &:focus-within > span { animation-play-state: paused; } &[data-pause="none"]:hover > span, &[data-pause="none"]:focus-within > span { animation-play-state: running; } &[data-direction="down"] > span { flex-direction: column-reverse; animation-name: var(--_down, none); } /* fade: every line in the same cell, each fades through its slot */ &[data-effect="fade"] > span { display: grid; animation: none; } &[data-effect="fade"] > span > * { grid-area: 1 / 1; opacity: 0; animation: var(--_fade, none) var(--_dur) ease infinite both; } &[data-effect="fade"]:hover > span > *, &[data-effect="fade"]:focus-within > span > * { animation-play-state: paused; } &[data-effect="fade"][data-pause="none"]:hover > span > * { animation-play-state: running; } } /* the line count picks the keyframes */ .text-rotate:has(> span > :nth-child(2):last-child) { --_n: 2; --_name: text-rotate-2; --_down: text-rotate-2-down; --_fade: text-rotate-fade-2; } .text-rotate:has(> span > :nth-child(3):last-child) { --_n: 3; --_name: text-rotate-3; --_down: text-rotate-3-down; --_fade: text-rotate-fade-3; } .text-rotate:has(> span > :nth-child(4):last-child) { --_n: 4; --_name: text-rotate-4; --_down: text-rotate-4-down; --_fade: text-rotate-fade-4; } .text-rotate:has(> span > :nth-child(5):last-child) { --_n: 5; --_name: text-rotate-5; --_down: text-rotate-5-down; --_fade: text-rotate-fade-5; } .text-rotate:has(> span > :nth-child(6):last-child) { --_n: 6; --_name: text-rotate-6; --_down: text-rotate-6-down; --_fade: text-rotate-fade-6; } /* fade: line k starts its slot k-1 slots later */ .text-rotate[data-effect="fade"] > span > :nth-child(2) { animation-delay: calc(var(--_dur) * 1 / var(--_n)); } .text-rotate[data-effect="fade"] > span > :nth-child(3) { animation-delay: calc(var(--_dur) * 2 / var(--_n)); } .text-rotate[data-effect="fade"] > span > :nth-child(4) { animation-delay: calc(var(--_dur) * 3 / var(--_n)); } .text-rotate[data-effect="fade"] > span > :nth-child(5) { animation-delay: calc(var(--_dur) * 4 / var(--_n)); } .text-rotate[data-effect="fade"] > span > :nth-child(6) { animation-delay: calc(var(--_dur) * 5 / var(--_n)); } @keyframes text-rotate-2 { 0%, 46% { translate: 0 0; } 50%, 96% { translate: 0 -50%; } 100% { translate: 0 0; } } @keyframes text-rotate-2-down { 0%, 46% { translate: 0 -50%; } 50%, 96% { translate: 0 0; } 100% { translate: 0 -50%; } } @keyframes text-rotate-fade-2 { 0% { opacity: 0; translate: 0 0.25lh; } 4%, 46% { opacity: 1; translate: 0 0; } 50%, 100% { opacity: 0; translate: 0 -0.25lh; } } @keyframes text-rotate-3 { 0%, 30.667% { translate: 0 0; } 33.333%, 64% { translate: 0 -33.3333%; } 66.667%, 97.333% { translate: 0 -66.6667%; } 100% { translate: 0 0; } } @keyframes text-rotate-3-down { 0%, 30.667% { translate: 0 -66.6667%; } 33.333%, 64% { translate: 0 -33.3333%; } 66.667%, 97.333% { translate: 0 0; } 100% { translate: 0 -66.6667%; } } @keyframes text-rotate-fade-3 { 0% { opacity: 0; translate: 0 0.25lh; } 2.667%, 30.667% { opacity: 1; translate: 0 0; } 33.333%, 100% { opacity: 0; translate: 0 -0.25lh; } } @keyframes text-rotate-4 { 0%, 23% { translate: 0 0; } 25%, 48% { translate: 0 -25%; } 50%, 73% { translate: 0 -50%; } 75%, 98% { translate: 0 -75%; } 100% { translate: 0 0; } } @keyframes text-rotate-4-down { 0%, 23% { translate: 0 -75%; } 25%, 48% { translate: 0 -50%; } 50%, 73% { translate: 0 -25%; } 75%, 98% { translate: 0 0; } 100% { translate: 0 -75%; } } @keyframes text-rotate-fade-4 { 0% { opacity: 0; translate: 0 0.25lh; } 2%, 23% { opacity: 1; translate: 0 0; } 25%, 100% { opacity: 0; translate: 0 -0.25lh; } } @keyframes text-rotate-5 { 0%, 18.4% { translate: 0 0; } 20%, 38.4% { translate: 0 -20%; } 40%, 58.4% { translate: 0 -40%; } 60%, 78.4% { translate: 0 -60%; } 80%, 98.4% { translate: 0 -80%; } 100% { translate: 0 0; } } @keyframes text-rotate-5-down { 0%, 18.4% { translate: 0 -80%; } 20%, 38.4% { translate: 0 -60%; } 40%, 58.4% { translate: 0 -40%; } 60%, 78.4% { translate: 0 -20%; } 80%, 98.4% { translate: 0 0; } 100% { translate: 0 -80%; } } @keyframes text-rotate-fade-5 { 0% { opacity: 0; translate: 0 0.25lh; } 1.6%, 18.4% { opacity: 1; translate: 0 0; } 20%, 100% { opacity: 0; translate: 0 -0.25lh; } } @keyframes text-rotate-6 { 0%, 15.333% { translate: 0 0; } 16.667%, 32% { translate: 0 -16.6667%; } 33.333%, 48.667% { translate: 0 -33.3333%; } 50%, 65.333% { translate: 0 -50%; } 66.667%, 82% { translate: 0 -66.6667%; } 83.333%, 98.667% { translate: 0 -83.3333%; } 100% { translate: 0 0; } } @keyframes text-rotate-6-down { 0%, 15.333% { translate: 0 -83.3333%; } 16.667%, 32% { translate: 0 -66.6667%; } 33.333%, 48.667% { translate: 0 -50%; } 50%, 65.333% { translate: 0 -33.3333%; } 66.667%, 82% { translate: 0 -16.6667%; } 83.333%, 98.667% { translate: 0 0; } 100% { translate: 0 -83.3333%; } } @keyframes text-rotate-fade-6 { 0% { opacity: 0; translate: 0 0.25lh; } 1.333%, 15.333% { opacity: 1; translate: 0 0; } 16.667%, 100% { opacity: 0; translate: 0 -0.25lh; } } /* -- Accessibility ---------------------------------------------------- Reduced motion: no rolling - the first line stays. All lines stay in the DOM, so assistive tech reads the full list. */ @media (prefers-reduced-motion: reduce) { .text-rotate > span, .text-rotate[data-effect="fade"] > span > * { animation: none; } .text-rotate[data-effect="fade"] > span > :first-child { opacity: 1; } }}Comments, ideas or improvements? Edit this page's source on GitHub