Skip to content
All Taste components

Animated heroes

Hero — minimal editorial (motion)

The hairline rules draw across the page and each line of the huge headline slides up out of its own mask.

rules draw · masked line-by-line reveal · meta fades · arrow nudges on the CTA

Fernbill — InvoicingFor people who make thingsEst. 2026

Do the work.We'll handlethe money.

01 / Invoicing

Invoices, reminders and payouts for freelancers and small studios — quiet, quick and out of your way.

Start free
import { ArrowRight } from "lucide-react";

/* Add once to globals.css:
@keyframes rule-draw { from { transform: scaleX(0); } }
@keyframes line-up { from { transform: translateY(135%); } }
@keyframes nudge { 0%, 55%, 100% { translate: 0 0; } 25% { translate: 4px 0; } }
*/

const meta = ["Fernbill — Invoicing", "For people who make things", "Est. 2026"];
const lines = [
  { text: "Do the work.", muted: false },
  { text: "We’ll handle", muted: true },
  { text: "the money.", muted: false },
];
const rule = "absolute inset-x-0 h-px origin-left animate-[rule-draw_1.2s_cubic-bezier(.2,.7,.1,1)_both] motion-reduce:animate-fade";

export function HeroEditorialMotion() {
  return (
    <section aria-labelledby="hero-title" className="bg-bg font-sans">
      <div className="mx-auto max-w-6xl px-4 pb-16 pt-10 sm:px-6">
        <div className="relative flex flex-wrap justify-between gap-x-6 gap-y-2 pb-3.5 text-[12.5px] text-fg-muted">
          {meta.map((m, i) => <span key={m} className="animate-fade motion-reduce:animate-fade" style={{ animationDelay: 300 + i * 120 + "ms", animationDuration: ".8s" }}>{m}</span>)}
          <span aria-hidden className={rule + " -bottom-px bg-fg [animation-delay:100ms]"} />
        </div>

        <h1 id="hero-title" className="my-10 text-[clamp(3.25rem,11vw,9.5rem)] font-medium leading-[0.9] tracking-[-0.06em] text-fg sm:my-14">
          {lines.map((l, i) => (
            // Each line rises out of its own mask; the padding keeps descenders from being clipped.
            <span key={l.text} className="-mb-[.3em] -mt-[.1em] block overflow-hidden pb-[.3em] pt-[.1em]">
              <span
                className={"block animate-[line-up_1.1s_cubic-bezier(.16,1,.3,1)_both] motion-reduce:animate-fade " + (l.muted ? "text-fg-subtle" : "")}
                style={{ animationDelay: 350 + i * 140 + "ms" }}
              >
                {l.text}
              </span>
            </span>
          ))}
        </h1>

        <div className="relative grid items-end gap-6 pt-4 sm:grid-cols-3">
          <span aria-hidden className={rule + " -top-px bg-border [animation-delay:850ms]"} />
          <span className="hidden text-[13px] text-fg-subtle animate-rise [animation-delay:890ms] motion-reduce:animate-fade sm:block">01 / Invoicing</span>
          <p className="text-pretty text-[14.5px] leading-relaxed text-fg-muted animate-rise [animation-delay:980ms] motion-reduce:animate-fade">
            Invoices, reminders and payouts for freelancers and small studios — quiet, quick and out of your way.
          </p>
          <a href="/signup" className="inline-flex w-fit items-center gap-2 border-b border-current pb-0.5 text-sm font-medium text-fg transition animate-rise [animation-delay:1070ms] hover:text-primary motion-reduce:animate-fade sm:justify-self-end">
            Start free <ArrowRight className="size-4 animate-[nudge_2.6s_ease-in-out_2.2s_infinite] motion-reduce:animate-none" aria-hidden />
          </a>
        </div>
      </div>
    </section>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.