UI Library

Centered hero

A centered landing hero with a status pill, a gradient wash, and two calls to action. The wash is pointer-events-none, so it never intercepts a click on the buttons beneath it.

Herolandinggradientcentered
Preview
A small studio building focused, private tools

Focused tools that respect your data.

No dark patterns, no data selling, open-source where it counts. Everything here is built to keep working — not to keep your attention.

apps/site/blocks/components/hero-centered.tsx
import { ArrowRight } from 'lucide-react';

interface HeroCenteredProps {
  eyebrow?: string;
  title?: string;
  body?: string;
  primaryCta?: { label: string; href: string };
  secondaryCta?: { label: string; href: string };
}

export function HeroCentered({
  eyebrow = 'A small studio building focused, private tools',
  title = 'Focused tools that respect your data.',
  body = 'No dark patterns, no data selling, open-source where it counts. Everything here is built to keep working — not to keep your attention.',
  primaryCta = { label: 'Explore the tools', href: '#' },
  secondaryCta = { label: 'Read our principles', href: '#' },
}: HeroCenteredProps) {
  return (
    <section className="relative overflow-hidden border-b border-border/60">
      {/* Decorative wash. `pointer-events-none` so it never eats a click on the
          CTAs beneath it, and aria-hidden so it is not announced. */}
      <div
        aria-hidden
        className="pointer-events-none absolute inset-x-0 -top-32 h-72 bg-gradient-to-b from-primary/10 via-primary/5 to-transparent blur-2xl"
      />
      <div className="relative mx-auto max-w-3xl px-4 py-20 text-center sm:px-6 sm:py-28">
        <div className="inline-flex items-center gap-2 rounded-full border border-border/80 bg-background px-3 py-1 text-xs text-muted-foreground">
          <span className="h-1.5 w-1.5 rounded-full bg-primary" />
          {eyebrow}
        </div>

        <h1 className="mt-6 text-4xl font-semibold tracking-tight sm:text-5xl lg:text-6xl">
          {title}
        </h1>

        <p className="mx-auto mt-6 max-w-2xl text-base text-muted-foreground sm:text-lg">
          {body}
        </p>

        <div className="mt-8 flex flex-wrap items-center justify-center gap-3">
          <a
            href={primaryCta.href}
            className="inline-flex h-10 items-center gap-2 rounded-md bg-primary px-5 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90"
          >
            {primaryCta.label}
            <ArrowRight className="h-4 w-4" />
          </a>
          <a
            href={secondaryCta.href}
            className="inline-flex h-10 items-center gap-2 rounded-md border border-border bg-background px-5 text-sm font-medium transition-colors hover:bg-accent"
          >
            {secondaryCta.label}
          </a>
        </div>
      </div>
    </section>
  );
}

Dependencies

  • lucide-react — npm package

Colours come from CSS variables (--background, --primary, --border) using the shadcn/ui token names, so the block adopts your palette rather than importing ours.