# Hero 02

> Centered hero over animated gradient bars, with a release pill, a serif headline with an underlined accent, dual CTA and a wordmark strip.

A shadcn/ui block from the [@hirael](https://hirael.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/hirael/hero-02).

- Page, with a live preview: https://shadcn.exchange/r/hirael/hero-02
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/hirael/hero-02-light-e62912adf13dada9.png
- Registry item: https://hirael.com/r/hero-02.json
- Categories: blocks, hero
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://hirael.com/r/hero-02.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`, `shaders`
- Registry items: `button`, `https://hirael.com/r/use-reduced-motion.json`

## Files

### `registry/hirael/bases/radix/blocks/hero-02/hero-02.tsx`

```tsx
// Hero 02 from Hirael <https://hirael.com/blocks/hero/hero-02>
// MIT · Mohammad Shehadeh · https://github.com/MohammadShehadeh/hirael

'use client';

import * as React from 'react';
import dynamic from 'next/dynamic';
import { ArrowRight, Compass, Dna, Gem, Globe, Play, ShieldCheck, Sun } from 'lucide-react';

import { cn } from '@/lib/utils';
import { Button } from '@/registry/hirael/bases/radix/ui/button';

const Hero02Backdrop = dynamic(() => import('./hero-02-backdrop'), {
  ssr: false,
  loading: () => <div className="size-full bg-muted/20" />,
});

const ENTER =
  'animate-in fade-in slide-in-from-bottom-4 duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] fill-mode-both motion-reduce:animate-none';
// Text only slides: starting it at opacity 0 would hold back Largest Contentful Paint.
const ENTER_TEXT =
  'animate-in slide-in-from-bottom-4 duration-500 ease-[cubic-bezier(0.22,1,0.36,1)] fill-mode-both motion-reduce:animate-none';

const stagger = (index: number): React.CSSProperties => ({ animationDelay: `${index * 70}ms` });

const WORDMARKS = [
  { name: 'Helix', icon: Dna },
  { name: 'Northwind', icon: Compass },
  { name: 'Vanta', icon: ShieldCheck },
  { name: 'Quartz', icon: Gem },
  { name: 'Lumen', icon: Sun },
  { name: 'Atlas', icon: Globe },
] as const;

const Hero02 = () => {
  const [active, setActive] = React.useState(false);

  return (
    <section
      data-slot="hero"
      className="relative isolate flex min-h-[640px] flex-col items-center justify-center overflow-hidden bg-background px-6 py-24 text-center text-foreground md:px-10"
      onMouseEnter={() => setActive(true)}
      onMouseLeave={() => setActive(false)}
    >
      <div
        aria-hidden
        data-slot="hero-wash"
        className="pointer-events-none absolute inset-0 bg-[radial-gradient(55%_55%_at_50%_8%,color-mix(in_oklch,var(--primary)_30%,transparent),transparent_64%),radial-gradient(60%_60%_at_10%_92%,color-mix(in_oklch,var(--primary)_28%,transparent),transparent_68%),radial-gradient(60%_60%_at_90%_80%,color-mix(in_oklch,var(--chart-2)_20%,transparent),transparent_68%)]"
      />
      <div
        aria-hidden
        data-slot="hero-backdrop"
        className="pointer-events-none absolute inset-0 [mask-image:radial-gradient(ellipse_at_center,black_15%,transparent_72%)] opacity-50 mix-blend-multiply dark:opacity-40 dark:mix-blend-screen"
      >
        <Hero02Backdrop active={active} />
      </div>

      <a
        href="#"
        data-slot="hero-release"
        className={cn(
          ENTER,
          'inline-flex items-center gap-2.5 rounded-full border border-border bg-card/70 px-3.5 py-1.5 text-xs uppercase backdrop-blur-sm transition-colors duration-150 hover:border-primary/50',
        )}
      >
        <span className="text-primary">New release</span>
        <span aria-hidden className="text-border">
          |
        </span>
        <span dir="ltr" className="text-muted-foreground tabular-nums">
          2026.06
        </span>
      </a>

      <h1
        style={stagger(1)}
        className={cn(
          ENTER_TEXT,
          'mt-8 max-w-4xl font-serif text-5xl leading-[1.04] font-medium tracking-tight sm:text-6xl md:text-7xl',
        )}
      >
        Ship faster with tools that stay{' '}
        <span className="italic underline decoration-border decoration-2 underline-offset-[10px]">out of your way</span>
        .
      </h1>

      <p style={stagger(2)} className={cn(ENTER_TEXT, 'mt-6 max-w-xl text-lg leading-relaxed text-muted-foreground')}>
        A focused toolkit for teams that would rather build than configure. Sensible defaults, no busywork.
      </p>

      <div
        data-slot="hero-actions"
        style={stagger(3)}
        className={cn(ENTER, 'mt-10 flex flex-col items-center gap-3 sm:flex-row')}
      >
        <Button asChild size="lg" className="group">
          <a href="#">
            Get started
            <ArrowRight className="size-4 transition-transform duration-150 group-hover:translate-x-0.5 rtl:rotate-180 rtl:group-hover:-translate-x-0.5" />
          </a>
        </Button>
        <Button asChild size="lg" variant="ghost">
          <a href="#">
            <Play className="size-4" />
            Watch demo
          </a>
        </Button>
      </div>

      <div data-slot="hero-logos" style={stagger(4)} className={cn(ENTER, 'mt-16 flex flex-col items-center gap-5')}>
        <p className="text-xs text-muted-foreground uppercase">Used by product teams at</p>
        <div className="flex flex-wrap items-center justify-center gap-x-8 gap-y-4">
          {WORDMARKS.map((w) => (
            <span
              key={w.name}
              className="inline-flex items-center gap-2 text-sm font-medium text-muted-foreground transition-colors duration-150 hover:text-foreground"
            >
              <w.icon aria-hidden className="size-4" />
              {w.name}
            </span>
          ))}
        </div>
      </div>
    </section>
  );
};

export default Hero02;
```

### `registry/hirael/bases/radix/blocks/hero-02/hero-02-backdrop.tsx`

```tsx
// Hero 02 from Hirael <https://hirael.com/blocks/hero/hero-02>
// MIT · Mohammad Shehadeh · https://github.com/MohammadShehadeh/hirael

'use client';

import * as React from 'react';
import { Shader, Stripes } from 'shaders/react';
import { useReducedMotion } from '@/registry/hirael/hooks/use-reduced-motion';

// The shader can't read `var()`, so tokens are resolved to absolute colors through the DOM.
const resolveColor = (value: string) => {
  const probe = document.createElement('span');
  probe.style.color = value;
  document.body.append(probe);
  const resolved = getComputedStyle(probe).color;
  probe.remove();

  return resolved;
};

const readPalette = () => ({
  stripe: resolveColor('var(--muted-foreground)'),
});

type Palette = ReturnType<typeof readPalette>;

const usePalette = () => {
  const [palette, setPalette] = React.useState<Palette | null>(() =>
    typeof document === 'undefined' ? null : readPalette(),
  );

  React.useEffect(() => {
    const observer = new MutationObserver(() => setPalette(readPalette()));
    observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });

    return () => observer.disconnect();
  }, []);

  return palette;
};

interface Hero02BackdropProps {
  active?: boolean;
}

const Hero02Backdrop = ({ active = false }: Hero02BackdropProps) => {
  const palette = usePalette();
  const reduced = useReducedMotion();
  if (!palette) return null;

  return (
    <Shader style={{ width: '100%', height: '100%' }}>
      <Stripes
        colorA="transparent"
        colorB={palette.stripe}
        angle={90}
        density={9}
        balance={0.5}
        softness={1}
        speed={reduced ? 0 : active ? 0.5 : 0.16}
      />
    </Shader>
  );
};

export default Hero02Backdrop;
```
