# Hero

> Marketing hero with a pulsing announcement pill, a word-by-word headline with a volt-marked word, two CTAs, a copyable install command and a floating product shot of animated UI cards.

A shadcn/ui block from the [@geturui](https://geturui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/geturui/hero-section).

- Page, with a live preview: https://shadcn.exchange/r/geturui/hero-section
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/geturui/hero-section-light-2f4573d628fe2f44.png
- Registry item: https://geturui.com/r/hero-section.json
- Categories: blocks, hero, landing, marketing, headline, cta, install, product shot
- Listed: 2026-10-23, source last changed 2026-10-23

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/geturui/hero-section.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`, `motion`
- Registry items: `@geturui/badge`, `@geturui/button`, `@geturui/copy-button`, `@geturui/number-ticker`, `@geturui/text-animation`

## Files

### `registry/geturui/blocks/hero-section.tsx`

```tsx
"use client";

import * as React from "react";
import { motion, useReducedMotion } from "motion/react";
import { ArrowRight, Check, GitBranch, Search, Sparkles } from "lucide-react";
import { cn } from "@/lib/utils";
import { Badge } from "@/components/ui/badge";
import { Button, buttonClasses, type ButtonVariant } from "@/components/ui/button";
import { CopyButton } from "@/components/ui/copy-button";
import { NumberTicker } from "@/components/ui/number-ticker";
import { RevealText } from "@/components/ui/text-animation";

export interface HeroAction {
  label: string;
  /** Renders an anchor when set, otherwise a button. */
  href?: string;
  onClick?: () => void;
}

export interface HeroSectionProps {
  /** "centered" stacks copy over the product shot; "split" puts them side by side on large screens. */
  variant?: "centered" | "split";
  /** Text inside the announcement pill. Pass null to hide the pill. */
  eyebrow?: string | null;
  /** Short label on the pulsing badge inside the pill. */
  eyebrowBadge?: string;
  eyebrowHref?: string;
  headline?: string;
  /** One word of the headline to underline with a volt swash. */
  mark?: string;
  subcopy?: string;
  primaryAction?: HeroAction;
  secondaryAction?: HeroAction;
  /** Shell command shown with a copy button. Pass null to hide the row. */
  installCommand?: string | null;
  /** Show the floating product shot made of animated UI cards. */
  showProductShot?: boolean;
  className?: string;
}

function Cta({ action, variant }: { action: HeroAction; variant: ButtonVariant }) {
  const arrow = variant === "primary";
  if (action.href) {
    return (
      <a href={action.href} onClick={action.onClick} className={buttonClasses({ variant, size: "lg" })}>
        <span className="inline-flex items-center gap-[inherit]">
          {action.label}
          {arrow && (
            <ArrowRight aria-hidden className="size-4 transition-transform duration-300 ease-[var(--ease-spring)] group-hover/btn:translate-x-[3px]" />
          )}
        </span>
      </a>
    );
  }
  return (
    <Button variant={variant} size="lg" trailing={arrow ? "arrow" : undefined} onClick={action.onClick}>
      {action.label}
    </Button>
  );
}

/** Entrance + idle float for a product-shot card. */
function Floating({
  children,
  className,
  delay = 0,
  drift = 8,
  reduce,
}: {
  children: React.ReactNode;
  className?: string;
  delay?: number;
  drift?: number;
  reduce: boolean;
}) {
  return (
    <motion.div
      className={className}
      initial={reduce ? { opacity: 0 } : { opacity: 0, y: 24, scale: 0.96, filter: "blur(6px)" }}
      animate={{ opacity: 1, y: 0, scale: 1, filter: "blur(0px)" }}
      transition={{ type: "spring", stiffness: 260, damping: 30, delay }}
    >
      <motion.div
        animate={reduce ? undefined : { y: [0, -drift, 0] }}
        transition={{ duration: 6 + delay * 2, repeat: Infinity, ease: "easeInOut", delay: delay + 0.6 }}
      >
        {children}
      </motion.div>
    </motion.div>
  );
}

function MiniSwitch({ reduce }: { reduce: boolean }) {
  return (
    <span aria-hidden className="relative flex h-5 w-9 items-center rounded-full border border-border-strong bg-surface-sunken p-[2px]">
      <motion.span
        className="absolute inset-0 rounded-full bg-volt"
        animate={reduce ? { opacity: 1 } : { opacity: [0, 0, 1, 1, 0] }}
        transition={{ duration: 4, repeat: Infinity, times: [0, 0.3, 0.4, 0.8, 0.9] }}
      />
      <motion.span
        className="relative block size-[14px] rounded-full bg-ink"
        animate={reduce ? { x: 16 } : { x: [0, 0, 16, 16, 0] }}
        transition={{ duration: 4, repeat: Infinity, times: [0, 0.3, 0.4, 0.8, 0.9], ease: "easeInOut" }}
      />
    </span>
  );
}

function ProductShot({ reduce, className }: { reduce: boolean; className?: string }) {
  return (
    <div aria-hidden className={cn("relative mx-auto w-full max-w-[640px] select-none", className)}>
      <div className="pointer-events-none absolute -inset-10 -z-10 rounded-full bg-volt-soft opacity-60 blur-3xl" />
      <Floating reduce={reduce} drift={4} className="relative">
        <div className="overflow-hidden rounded-lg border border-border bg-surface shadow-lg">
          <div className="flex items-center gap-2 border-b border-border px-4 py-3">
            <span className="size-2.5 rounded-full bg-surface-hover ring-1 ring-border" />
            <span className="size-2.5 rounded-full bg-surface-hover ring-1 ring-border" />
            <span className="size-2.5 rounded-full bg-surface-hover ring-1 ring-border" />
            <span className="ml-3 inline-flex h-6 flex-1 items-center gap-1.5 rounded-full bg-surface-sunken px-3 font-mono text-[11px] text-ink-subtle">
              <Search className="size-3" /> geturui.com/components
            </span>
          </div>
          <div className="grid grid-cols-[120px_1fr] gap-0 @xl:grid-cols-[150px_1fr]">
            <div className="hidden flex-col gap-2 border-r border-border p-4 @md:flex">
              {[64, 80, 52, 72, 60, 44].map((w, i) => (
                <span key={i} className={cn("h-2 rounded-full", i === 1 ? "bg-volt" : "bg-surface-raised")} style={{ width: `${w}%` }} />
              ))}
            </div>
            <div className="col-span-2 grid gap-3 p-4 @md:col-span-1">
              <div className="flex items-center justify-between">
                <span className="h-3 w-28 rounded-full bg-surface-raised" />
                <span className="inline-flex h-6 items-center rounded-full bg-volt px-3 text-[11px] font-semibold text-on-volt">Install</span>
              </div>
              <div className="grid grid-cols-2 gap-3">
                <div className="grid gap-2 rounded-md border border-border bg-bg p-3">
                  <span className="text-[11px] font-medium text-ink-muted">Reduced motion</span>
                  <MiniSwitch reduce={reduce} />
                </div>
                <div className="grid gap-2 rounded-md border border-border bg-bg p-3">
                  <span className="text-[11px] font-medium text-ink-muted">Build</span>
                  <span className="h-1.5 overflow-hidden rounded-full bg-surface-sunken">
                    <motion.span
                      className="block h-full origin-left rounded-full bg-volt"
                      animate={reduce ? { scaleX: 0.8 } : { scaleX: [0.1, 1, 1] }}
                      transition={{ duration: 3.2, repeat: Infinity, times: [0, 0.75, 1], ease: "easeInOut" }}
                    />
                  </span>
                </div>
              </div>
              <div className="grid gap-1.5 rounded-md bg-surface-sunken p-3 font-mono text-[11px] leading-4">
                <span>
                  <span className="text-ember-ink">import</span> <span className="text-ink">{"{ Button }"}</span>{" "}
                  <span className="text-ember-ink">from</span> <span className="text-volt-ink">&quot;@/ui/button&quot;</span>
                </span>
                <span className="text-ink-subtle">{"// spring press, nudging arrow"}</span>
                <span className="text-ink">
                  {"<Button trailing=\"arrow\">Ship it</Button>"}
                  {!reduce && (
                    <motion.span
                      className="ml-0.5 inline-block h-3 w-[6px] translate-y-0.5 bg-volt"
                      animate={{ opacity: [1, 0, 1] }}
                      transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
                    />
                  )}
                </span>
              </div>
            </div>
          </div>
        </div>
      </Floating>

      <Floating reduce={reduce} delay={0.25} className="absolute -left-6 top-20 hidden w-56 @xl:block">
        <div className="flex items-start gap-3 rounded-md bg-surface-raised p-3 shadow-md">
          <span className="grid size-7 shrink-0 place-items-center rounded-full bg-volt-soft text-volt-ink">
            <Check className="size-3.5" strokeWidth={2.5} />
          </span>
          <div className="min-w-0">
            <div className="text-[13px] font-semibold text-ink">Deployed to production</div>
            <div className="text-[11.5px] text-ink-muted">main · 41s ago</div>
          </div>
        </div>
      </Floating>

      <Floating reduce={reduce} delay={0.4} drift={10} className="absolute -right-6 -top-6 hidden w-44 @xl:block">
        <div className="grid gap-1 rounded-md bg-surface-raised p-3 shadow-md">
          <span className="text-[11px] font-medium text-ink-muted">Components shipped</span>
          <NumberTicker value={214} className="font-display text-2xl font-semibold text-ink" />
          <span className="inline-flex items-center gap-1 text-[11px] font-medium text-success">
            <Sparkles className="size-3" /> 12 this week
          </span>
        </div>
      </Floating>

      <Floating reduce={reduce} delay={0.55} drift={6} className="absolute -bottom-6 right-10 hidden @xl:block">
        <div className="inline-flex items-center gap-2 rounded-full border border-border bg-glass px-3 py-2 text-[12px] font-medium text-ink shadow-md glass">
          <GitBranch className="size-3.5 text-volt-ink" /> feat/hero-section
          <span className="rounded-full bg-success/15 px-1.5 text-[10px] font-semibold uppercase text-success">Merged</span>
        </div>
      </Floating>
    </div>
  );
}

/** Marketing hero: pulsing announcement pill, word-by-word headline with a marked word, two CTAs, a copyable install command and a floating product shot. */
export function HeroSection({
  variant = "centered",
  eyebrow = "New: 14 page-section blocks",
  eyebrowBadge = "New",
  eyebrowHref,
  headline = "Animated components your users will feel",
  mark = "feel",
  subcopy = "Copy-paste React components with spring motion, real keyboard support and tokens that match your brand. Built on Tailwind and motion.",
  primaryAction = { label: "Browse components" },
  secondaryAction = { label: "Read the docs" },
  installCommand = "npx shadcn@latest add @geturui/hero-section",
  showProductShot = true,
  className,
}: HeroSectionProps) {
  const reduce = useReducedMotion() ?? false;
  const split = variant === "split";

  const pillInner = (
    <>
      <Badge tone="new" pulse>
        {eyebrowBadge}
      </Badge>
      <span className="truncate">{eyebrow}</span>
      <ArrowRight aria-hidden className="size-3.5 shrink-0 text-ink-subtle transition-transform duration-300 ease-[var(--ease-spring)] group-hover/pill:translate-x-0.5" />
    </>
  );
  const pillClass =
    "group/pill inline-flex max-w-full items-center gap-2 rounded-full border border-border bg-surface py-1 pl-1 pr-3 text-[13px] font-medium text-ink-muted outline-none transition-colors hover:border-border-strong hover:text-ink focus-visible:shadow-[var(--focus-ring)]";

  return (
    <section className={cn("@container relative w-full overflow-hidden px-4 py-16 sm:px-6 sm:py-24", className)}>
      <div
        className={cn(
          "mx-auto grid max-w-6xl gap-14",
          split ? "items-center @4xl:grid-cols-[1.05fr_1fr] @4xl:gap-10" : "justify-items-center text-center",
        )}
      >
        <div className={cn("flex flex-col gap-6", split ? "items-start" : "items-center")}>
          {eyebrow &&
            (eyebrowHref ? (
              <motion.a
                href={eyebrowHref}
                className={pillClass}
                initial={{ opacity: 0, y: 8 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ type: "spring", stiffness: 300, damping: 30 }}
              >
                {pillInner}
              </motion.a>
            ) : (
              <motion.span
                className={pillClass}
                initial={{ opacity: 0, y: 8 }}
                animate={{ opacity: 1, y: 0 }}
                transition={{ type: "spring", stiffness: 300, damping: 30 }}
              >
                {pillInner}
              </motion.span>
            ))}

          <RevealText
            as="h1"
            text={headline}
            mark={mark}
            delay={0.1}
            className={cn(
              "text-balance font-display text-[40px] font-semibold leading-[1.05] tracking-[-1.2px] text-ink @xl:text-[56px] @xl:tracking-[-1.8px]",
              split ? "max-w-xl" : "max-w-3xl @4xl:text-[68px] @4xl:tracking-[-2.2px]",
            )}
          />

          <motion.p
            className={cn("text-balance text-base leading-7 text-ink-muted @xl:text-lg", split ? "max-w-lg" : "max-w-xl")}
            initial={{ opacity: 0, y: 10, filter: "blur(6px)" }}
            animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
            transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1], delay: 0.45 }}
          >
            {subcopy}
          </motion.p>

          <motion.div
            className={cn("flex flex-wrap items-center gap-3", split ? "justify-start" : "justify-center")}
            initial={{ opacity: 0, y: 10 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ type: "spring", stiffness: 300, damping: 30, delay: 0.55 }}
          >
            <Cta action={primaryAction} variant="primary" />
            <Cta action={secondaryAction} variant="secondary" />
          </motion.div>

          {installCommand && (
            <motion.div
              className="inline-flex h-10 max-w-full items-center gap-2 rounded-full border border-border bg-surface-sunken pl-4 pr-1 font-mono text-[12.5px] text-ink-muted"
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              transition={{ duration: 0.4, delay: 0.7 }}
            >
              <span aria-hidden className="text-volt-ink">
                $
              </span>
              <code className="truncate">{installCommand}</code>
              <CopyButton value={installCommand} label="Copy install command" className="rounded-full" />
            </motion.div>
          )}
        </div>

        {showProductShot && <ProductShot reduce={reduce} className={split ? "@4xl:max-w-none" : "mt-4"} />}
      </div>
    </section>
  );
}
```

### `LICENSE-geturui.md`

```md
MIT License

Copyright (c) 2026 geturui.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
