I Took Kinetic Typography Apart in CSS
Kinetic typography is a design style where the headline scales with the viewport and words rise into place one after another.
Scroll the assembled one-page mock below before lifting any single piece out of it. Watch for the headline rising word by word, the stat card counting up to its real value, and the sheen that crosses the wordmark once at the very bottom.
The nine sections below are the same pieces cut apart so you can open each on its own. They're ordered the way a visitor actually meets them on a real page — first glance, a skim past the features, a check on the price, a read of who else trusts it, a nudge toward acting, and finally the footer.
Auto-plays · click a tile to jump to its section · all nine in one zip
- 01 Viewport-filling headline
- 02 Oversized wordmark nav
- 03 Oversized-number cards
- 04 One line per feature, big numbers
- 05 Price digits as the hero
- 06 Oversized quote
- 07 Marquee CTA
- 08 Oversized wordmark footer
- 09 Underline-animated link buttons
01Hero
Three headline words climb up from behind a hidden edge one after another instead of appearing all at once, and the whole headline resizes itself with clamp() so it never overflows a narrow screen or looks tiny on a wide one.
<section class="flex flex-col items-center gap-6 py-24 text-center">
<p class="text-xs font-bold uppercase tracking-[0.2em] text-accent">
KINETIC TYPE
</p>
<h1 class="font-display text-6xl font-extrabold leading-[1.02]
sm:text-7xl md:text-8xl">
Built at the speed<br>we build.
</h1>
<button class="rounded-pill bg-ink px-8 py-4 text-base font-bold text-cream">
Get started
</button>
</section>
02Navigation
The wordmark on the left of the nav bar shrinks and grows on its own, in a loop. It's not actually tied to scroll — it's a standalone demo standing in for the shrinking-logo effect a real sticky header gives you, and the only thing that changes is size; letter-spacing stays put.
<header class="sticky top-0 z-20 px-6 pt-6">
<nav class="mx-auto flex max-w-5xl items-center justify-between
rounded-pill bg-ink px-6 py-3 shadow-bento">
<span class="font-display text-xl font-extrabold text-cream">MOTIF</span>
<div class="hidden items-center gap-8 sm:flex">
<span class="text-sm font-semibold text-cream/70">About</span>
<span class="text-sm font-semibold text-cream/70">Features</span>
<span class="text-sm font-semibold text-cream/70">Pricing</span>
</div>
<button class="rounded-pill bg-accent px-5 py-2 text-sm font-bold text-cream">
Get started
</button>
</nav>
</header>
03Card grid
The number inside each card counts up from zero to its real value, digit by digit. Because content generated by CSS counter() is invisible to screen readers, the finished value ships a second time as hidden text ("248 projects") that stays readable no matter how the animation is set.
<section class="py-14">
<h2 class="mb-8 font-display text-3xl font-extrabold">The numbers</h2>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div class="rounded-tile bg-ink p-8 text-center shadow-bento">
<p class="font-display text-6xl font-extrabold tabular-nums text-cream">
248
</p>
<p class="mt-2 text-sm font-semibold text-cream/70">Projects</p>
</div>
<div class="rounded-tile bg-ink p-8 text-center shadow-bento">
<p class="font-display text-6xl font-extrabold tabular-nums text-cream">
99.9%
</p>
<p class="mt-2 text-sm font-semibold text-cream/70">Uptime</p>
</div>
</div>
</section>
04Feature list
Three rows nudge to the right one after another, top to bottom, and the underline beneath each label grows on the same beat. The bold number out front reads like a code editor's line number — pure decoration, since the real feature name is always plain text right beside it.
<ul class="divide-y divide-line rounded-tile bg-white shadow-bento">
<li class="grid grid-cols-[auto_1fr] items-baseline gap-6 p-6">
<span class="font-display text-3xl font-extrabold text-accent
tabular-nums">01</span>
<span class="text-lg font-bold">Real-time collaboration</span>
</li>
<li class="grid grid-cols-[auto_1fr] items-baseline gap-6 p-6">
<span class="font-display text-3xl font-extrabold text-accent
tabular-nums">02</span>
<span class="text-lg font-bold">Auto-deploy</span>
</li>
</ul>
05Pricing
The price digits roll back and forth between a monthly figure ($19) and a yearly one ($190) on their own, over and over. It isn't wired to a toggle button — the loop runs by itself in this demo, and wiring a real monthly/yearly switch to it is a one-line addition when you drop it into a project.
<div class="flex flex-col items-end gap-4 sm:flex-row">
<div class="flex-1 rounded-tile bg-white/60 p-8
shadow-[0_6px_12px_rgba(23,20,26,.08)]">
<p class="font-bold">Free</p>
<p class="mt-2 font-display text-4xl font-extrabold tabular-nums">$0</p>
</div>
<div class="relative flex-[1.1] rounded-tile bg-ink p-8 pt-10
shadow-bento-hover">
<span class="absolute -top-3 left-8 rounded-pill bg-accent px-3 py-1
text-xs font-bold text-cream">Recommended</span>
<p class="font-bold text-cream/70">Pro plan</p>
<p class="mt-2 font-display text-5xl font-extrabold tabular-nums
text-cream">
$19<span class="text-lg font-semibold text-cream/60"> / mo</span>
</p>
</div>
</div>
06Testimonials
The quote reveals itself left to right in one pass, like a curtain drawing back, using clip-path rather than the vertical mask that section 01's headline relies on.
<section class="py-14">
<h2 class="mb-8 font-display text-3xl font-extrabold">What people say</h2>
<figure class="rounded-tile bg-white p-10 text-center shadow-bento">
<p class="font-display text-3xl font-extrabold leading-snug sm:text-4xl">
"Speed changed how our team works."
</p>
<figcaption class="mt-4 text-sm text-ink/60">Park Ji-hoon, Growth Lead</figcaption>
</figure>
</section>
07CTA band
The phrase loops across the full-width band without a visible seam. Getting there means duplicating the whole line once, chaining both copies in a single track, and shifting exactly -50% — spacing has to come from margin-right on each item rather than a flex gap, or the loop skips a few pixels every time it comes back around.
<div class="relative overflow-hidden rounded-tile bg-accent py-8">
<div class="flex w-max animate-marquee">
<span class="mr-12 flex-shrink-0 whitespace-nowrap font-display
text-5xl font-extrabold text-cream">Get started now —</span>
<span class="mr-12 flex-shrink-0 whitespace-nowrap font-display
text-5xl font-extrabold text-cream">Get started now —</span>
<span class="mr-12 flex-shrink-0 whitespace-nowrap font-display
text-5xl font-extrabold text-cream" aria-hidden="true">Get started now —</span>
</div>
</div>
08Footer
A diagonal sheen crosses the oversized wordmark at the very bottom of the footer exactly once. background-clip: text traps the gradient inside the letterforms, and once the sweep finishes it holds still for most of the loop before repeating.
<footer class="py-14">
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3">
<div class="rounded-tile bg-ink/[.04] p-5">
<p class="mb-2 text-xs font-bold uppercase tracking-wide text-ink/50">
Product
</p>
<p class="text-sm font-semibold">Features</p>
</div>
<div class="rounded-tile bg-ink/[.04] p-5">
<p class="mb-2 text-xs font-bold uppercase tracking-wide text-ink/50">
Company
</p>
<p class="text-sm font-semibold">Terms</p>
</div>
</div>
<p class="mt-10 text-center font-display text-7xl font-extrabold
text-ink/[.06] sm:text-8xl">MOTIF</p>
</footer>
09Buttons & inputs
The underline beneath a link-style button grows outward from its own center on a loop rather than in response to a real hover trigger, and a separate focus outline shows up whenever a keyboard user tabs in, so the button never relies only on the underline to say where the focus currently sits.
<div class="flex flex-col items-center gap-6 rounded-tile bg-ink p-10
sm:flex-row sm:justify-center">
<button class="relative border-0 bg-transparent font-display text-2xl
font-extrabold text-cream after:absolute after:-bottom-1
after:left-0 after:right-0 after:h-[2px] after:bg-accent">
Learn more
</button>
<label class="relative w-full max-w-xs rounded-[12px] bg-cream/5">
<input class="w-full rounded-[12px] border border-cream/30 bg-transparent
px-4 py-3 text-sm text-cream outline-none" type="email"
placeholder="Email address" readonly>
</label>
</div>
Custom tailwind.config
Every section above, plus the one-page mock, pulls from this single config. Move a color, radius, or shadow value here and the whole page follows without touching a single section.
theme: {
extend: {
colors: {
cream: "#fff7e6",
ink: "#17141a",
accent: "#2f6df6",
line: "rgba(23,20,26,.08)",
},
fontFamily: {
display: ["'Space Grotesk'", "system-ui", "sans-serif"],
body: ["'Inter'", "system-ui", "sans-serif"],
},
borderRadius: { tile: "20px", pill: "999px" },
boxShadow: {
bento: "0 14px 24px rgba(23,20,26,.10)",
"bento-hover": "0 20px 32px rgba(23,20,26,.16)",
},
spacing: { gutter: "14px" },
},
},
Vibe-coding prompt
Drop the config above into a project first; handing a handful of rules to Claude Code or Cursor is enough to keep the whole page on-brand. The unabridged instruction ships as-is in the zip's prompt.md.
Background is cream (#fff7e6) only. Cards sit on ink (#17141a) with cream text on top,
never on pure white or gray.
Headlines and big numbers always use font-display (Space Grotesk); body copy uses
font-body (Inter). Don't mix the two.
Reserve accent (#2f6df6) for buttons, the recommended badge, and the CTA band —
never add a second accent color.
Radius comes from only two values: rounded-tile and rounded-pill.
Running that prompt against page/index.html produced the screenshot below, a full-page capture at a 1280px viewport.

Where it breaks (the trap)
The most common way this style breaks is by setting color and background through a theme() call written inside a <style> tag. The Tailwind Play CDN resolves theme() fine inside compiled utility classes like bg-cream, but it silently ignores the same call when it's written directly inside <style>, and the page falls back to the browser's own defaults — a transparent background, black text. This project actually shipped that bug on the first pass: <body>'s background and text color, plus the font on every <h2>, were all set through theme() in a <style> block, and rendering it for real showed a transparent body (computed background rgba(0,0,0,0)) with plain black text instead of ink, and headings losing the display font entirely. Switching <body> to compiled utility classes (bg-cream text-ink font-body) and adding font-display directly on each <h2> is what actually fixed it. The second trap sits in section 02 — its wordmark looks like it's tied to scroll, but the loop runs on a timer with no scroll listener at all, and only size changes, not letter-spacing, so if you copy the CSS expecting a tracking animation to come along with it, you'll come up short. The zip bundling all nine sections plus the one-page source is locked, and the password is y9my63zg — unlock it with the button just below.
A third trap sits in sections 03 and 05. Skip declaring @property --num with syntax: '<integer>' and the browser interpolates through fractional values like 0.5 or 1.7 on the way up, so the digits flicker instead of landing cleanly on each whole number.
Accessibility
All nine demos sit inside the media query MDN documents for prefers-reduced-motion, so a visitor who's turned on reduced motion sees each one settled at its end state instead of rising, counting, or sweeping. The count-up numbers in sections 03 and 05 ship a hidden, always-readable copy of the final value because CSS counter() content is invisible to screen readers, and half of the duplicated copies in section 07's marquee are marked aria-hidden so the same words aren't read out twice. Section 09 keeps a :focus-visible outline running independently of the underline animation, so keyboard focus stays visible even with motion turned off.
More trend write-ups like this one live in the design trends category, and what this project is (and isn't) is explained on the about page.
FAQ
Can I use names like MOTIF or KINETIC in a real product?
Yes. Neither name copies an existing brand — they're fictional placeholders invented for this write-up, inspired by the general look of kinetic-typography landing pages seen on Pinterest, so swapping in your own product name is expected, not a trademark risk.
Can I swap out Space Grotesk and Inter for other fonts?
Yes. Changing fontFamily.display and fontFamily.body in tailwind.config.js updates every section at once. Keep the split itself, though — a bold display face for headlines and big numbers, paired with a quieter body face for everything else, is what makes the oversized type read as intentional rather than just loud.
Does any of this work without Tailwind, in plain CSS?
Yes. The zip includes tokens.css, which carries the same values as tailwind.config.js written out as CSS custom properties (--color-cream, --radius-tile, and so on), so referencing var(--radius-tile) directly keeps every corner and color in sync without a single Tailwind class.