Gradient Sweep
Kinetic display type: an iridescent gradient sweeps through the letters while a shimmer band passes over them and the variable-font weight gently breathes. A second line tints each word a staggered hue.
- background-clip: text
- animated background-position
- font-variation-settings
- staggered delays
Source
A single self-contained .astro component. It uses Prism's design tokens (--spectrum-*, --ink…) with sensible fallbacks, so it renders even outside this site.
---
export const meta = {
title: "Gradient Sweep",
tags: ["text", "kinetic", "gradient", "css-only"],
description:
"Kinetic display type: an iridescent gradient sweeps through the letters while a shimmer band passes over them and the variable-font weight gently breathes. A second line tints each word a staggered hue.",
tech: ["background-clip: text", "animated background-position", "font-variation-settings", "staggered delays"],
height: 380,
};
const words = ["colour", "motion", "type", "light"];
const headline = "Designed in motion";
---
<section class="gsweep">
<div class="gsweep__stack">
<p class="gsweep__kicker">Kinetic typography</p>
<h1 class="gsweep__title" data-text={headline}>{headline}</h1>
<p class="gsweep__words" aria-label="colour, motion, type, light">
{words.map((w, i) => (
<span class="gsweep__word" style={`--i:${i}`} aria-hidden="true">{w}</span>
))}
</p>
</div>
</section>
<style>
.gsweep {
position: relative; min-height: 380px; display: grid; place-items: center;
padding: 3rem 1.5rem; overflow: hidden; isolation: isolate; text-align: center;
background:
radial-gradient(120% 80% at 50% 120%, color-mix(in oklab, var(--spectrum-1, #8b5cf6) 16%, transparent), transparent 70%),
var(--bg, #0e0f1a);
color: var(--ink, #f2f2f7);
}
.gsweep__stack { display: grid; gap: 0.9rem; justify-items: center; max-width: 46rem; }
.gsweep__kicker {
margin: 0; font-size: var(--step--1, 0.85rem); font-weight: 600;
letter-spacing: 0.24em; text-transform: uppercase; color: var(--ink-muted, #b7b9c9);
}
/* --- The sweeping iridescent headline ----------------------------------
Base element carries the iridescent ramp (animates background-position)
and the weight breathe. A ::after clone carries the shimmer band on its
OWN background-position, so the two never fight over one property. */
.gsweep__title {
position: relative; margin: 0; font-family: var(--font-display, system-ui, sans-serif);
font-size: clamp(2.4rem, 1.4rem + 5vw, 5.4rem); line-height: 1.04;
letter-spacing: -0.03em; font-weight: 800; text-wrap: balance;
background-image: linear-gradient(100deg,
var(--spectrum-1, #8b5cf6), var(--spectrum-2, #5b8def),
var(--spectrum-3, #45d3e8), var(--spectrum-4, #4ade80),
var(--spectrum-5, #fbbf24), var(--spectrum-6, #f472b6),
var(--spectrum-1, #8b5cf6));
background-size: 300% 100%; background-position: 0% 0; background-repeat: no-repeat;
-webkit-background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent; color: transparent;
font-variation-settings: "wght" 640;
animation: gsweep-ramp 9s linear infinite, gsweep-breathe 6s ease-in-out infinite;
}
.gsweep__title::after {
content: attr(data-text); position: absolute; inset: 0; pointer-events: none;
background-image: linear-gradient(115deg,
transparent 40%, color-mix(in oklab, #fff 94%, transparent) 50%, transparent 60%);
background-size: 220% 100%; background-position: 130% 0; background-repeat: no-repeat;
-webkit-background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent; color: transparent;
animation: gsweep-shine 4.8s var(--ease-in-out, cubic-bezier(0.65,0,0.35,1)) 1.2s infinite;
}
@keyframes gsweep-ramp { to { background-position: -300% 0; } }
@keyframes gsweep-shine { 0% { background-position: 130% 0; } 55%, 100% { background-position: -130% 0; } }
@keyframes gsweep-breathe { 50% { font-variation-settings: "wght" 880; } }
/* --- The staggered-hue word line -------------------------------------- */
.gsweep__words {
margin: 0; display: flex; flex-wrap: wrap; gap: 0.15rem 0.9rem; justify-content: center;
font-weight: 700; font-size: clamp(1.1rem, 0.9rem + 1vw, 1.7rem); letter-spacing: -0.01em;
}
.gsweep__word {
background-image: linear-gradient(100deg,
oklch(78% 0.17 calc(200 + var(--i) * 48)),
oklch(80% 0.17 calc(280 + var(--i) * 48)));
background-size: 200% 100%;
-webkit-background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent; color: transparent;
animation: gsweep-wordshift 5s ease-in-out infinite;
animation-delay: calc(var(--i) * 0.35s);
}
@keyframes gsweep-wordshift { 50% { background-position: 100% 0; } }
@media (prefers-reduced-motion: reduce) {
.gsweep__title { animation: none; background-position: 40% 0; font-variation-settings: "wght" 760; }
.gsweep__title::after { animation: none; opacity: 0; }
.gsweep__word { animation: none; }
}
</style>