GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

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.

Open the full page

The nine sections below are the same pieces cut apart into a typography hero section set you can open one at a time. 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. Setting the mood with dark ground and glow instead of moving type is what the dark SaaS linear design system does, and turning the same screen soft and puffy gives the claymorphism landing kit.

Auto-plays · click a tile to jump to its section · all nine in one zip

01Hero

Kinetic typography css, word by word

Three headline words climb up from behind a hidden edge one after another instead of appearing all at once.

Viewport-filling headline
Open in a new tab

01 · Viewport-filling headline — Measured area 12.381% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · See the password

clamp() 가변폭단어 단위 staggeroverflow: hidden 마스크

Type that resizes itself

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

A wordmark that shrinks on a loop

The wordmark on the left of the nav bar shrinks and grows on its own, in a loop.

Oversized wordmark nav
Open in a new tab

02 · Oversized wordmark nav — Measured area 1.86% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

transform-origin: left centerletter-spacing 보간sticky 헤더

Not actually tied to scroll

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

Counting up digit by digit

The number inside each card counts up from zero to its real value, digit by digit.

Oversized-number cards
Open in a new tab

03 · Oversized-number cards — Measured area 9.127% · moving frames 16/23Original by GODRICH · SCSS and React ship in the zip · See the password

@property 숫자 보간counter-resettabular-nums

Hidden text for screen readers

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

Rows nudging in sequence

Three rows nudge to the right one after another, top to bottom, and the underline beneath each label grows on the same beat.

One line per feature, big numbers
Open in a new tab

04 · One line per feature, big numbers — Measured area 7.512% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

grid 번호 정렬호버 translateX@keyframes underline-grow

The number is decoration

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

Digits rolling between two prices

The price digits roll back and forth between a monthly figure ($19) and a yearly one ($190) on their own, over and over.

Price digits as the hero
Open in a new tab

05 · Price digits as the hero — Measured area 4.107% · moving frames 18/23Original by GODRICH · SCSS and React ship in the zip · See the password

숫자 롤 애니메이션@keyframes price-rolltabular-nums

Wiring a real toggle

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

A curtain drawn left to right

The quote reveals itself left to right in one pass, like a curtain drawing back.

Oversized quote
Open in a new tab

06 · Oversized quote — Measured area 7.105% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · See the password

clip-path: inset()단어 단위 stagger@keyframes wipe-reveal

Why clip-path, not a mask

It uses 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

A seamless loop across the band

The phrase loops across the full-width band without a visible seam.

Marquee CTA
Open in a new tab

07 · Marquee CTA — Measured area 8.303% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateX(-50%) 루프margin 간격(gap 금지)hover 일시정지

Why margin-right beats gap

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 sheen that crosses once

A diagonal sheen crosses the oversized wordmark at the very bottom of the footer exactly once.

Oversized wordmark footer
Open in a new tab

08 · Oversized wordmark footer — Measured area 4.284% · moving frames 8/23Original by GODRICH · SCSS and React ship in the zip · See the password

background-clip: textgradient sweepclamp() 초대형 폰트

Holding still between passes

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

An underline growing from center

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.

Underline-animated link buttons
Open in a new tab

09 · Underline-animated link buttons — Measured area 2.883% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

transform: scaleXtransform-origin: centerfocus-visible 링

A focus outline of its own

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

One config behind every section

Every section above, plus the one-page mock, pulls from this single config.

Move a value, move the page

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

A handful of rules is enough

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.

What the prompt produced

Running that prompt against page/index.html produced the screenshot below, a full-page capture at a 1280px viewport.

Result

Where it breaks (the trap)

Why theme() falls back to defaults

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 place where splitting type into moving pieces is blocked outright is the inbox, and how to leave an impression without animation is what the marketing email templates demonstrate. 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.

Why the digits flicker

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

What reduced motion settles to

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.

Counters, hidden text, and marquees

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.

Focus that never disappears

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 reuse the MOTIF and KINETIC names from these kinetic typography examples 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. When moving letters would get in the way of explaining a product, the calmer arrangement of the trustworthy SaaS landing kit fits better.

Enter the archive password

The password is inside this article. You will find it as you read.