Hero

Hero sections for clear, confident first impressions.

Hero layouts for setting the tone and getting to the point. Pick one, then make the words and visuals yours.

Simple

A headline, short supporting line, primary action, and ghost action. Left-aligned on mobile, centered on larger screens.

Ship interfaces that feel finished

Ready made blocks you can restyle with your tokens.

tsx
"use client";import { Button } from "@/components/ui/button";import { cn } from "@/lib/utils";type Hero01Props = {  className?: string;};export function Hero01({ className }: Hero01Props) {  return (    <section className={cn("@container w-full bg-background", className)}>      <div className="mx-auto flex h-full w-full max-w-2xl flex-col items-start justify-center px-6 py-16 text-left @[32rem]:items-center @[32rem]:text-center">        <h1 className="text-balance text-2xl font-medium tracking-wide text-foreground @[32rem]:text-3xl">          Ship interfaces that feel finished        </h1>        <p className="mt-3 max-w-md text-pretty text-sm tracking-wide text-muted-foreground">          Ready made blocks you can restyle with your tokens.        </p>        <div className="mt-6 flex items-center justify-start gap-2 @[32rem]:justify-center">          <Button type="button" size="sm">            Get started          </Button>          <Button type="button" size="sm" variant="ghost">            Browse blocks          </Button>        </div>      </div>    </section>  );}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add hero-01

Usage

import { Hero01 } from "@/components/blocks/hero/hero-01"
<Hero01 />

Horizon glow

A headline and single action over a dark half-circle horizon, with soft glow and subtle noise.

Build once, reuse everywhere

tsx
"use client";import { useId } from "react";import { Button } from "@/components/ui/button";import { cn } from "@/lib/utils";type Hero02Props = {  className?: string;};export function Hero02({ className }: Hero02Props) {  const noiseId = useId().replace(/:/g, "");  return (    <section      className={cn(        "@container relative w-full overflow-hidden bg-black text-white",        className,      )}    >      <div        aria-hidden="true"        className="absolute inset-0"        style={{          backgroundImage: [            "radial-gradient(ellipse 125% 48% at 50% 78%, rgb(255 255 255 / 0.55) 0%, rgb(210 170 255 / 0.42) 8%, rgb(150 70 210 / 0.32) 22%, rgb(90 30 140 / 0.16) 40%, transparent 62%)",            "radial-gradient(ellipse 90% 35% at 50% 100%, rgb(40 10 60 / 0.9) 0%, transparent 55%)",          ].join(", "),        }}      />      <div        aria-hidden="true"        className="absolute left-1/2 bottom-[-48%] h-[95%] w-[165%] -translate-x-1/2 rounded-[100%] bg-black"        style={{          boxShadow:            "0 -1px 0 0 rgb(255 255 255 / 0.75), 0 -8px 28px 2px rgb(255 255 255 / 0.2), 0 -28px 90px 24px rgb(170 90 230 / 0.28)",        }}      />      <svg        aria-hidden="true"        className="pointer-events-none absolute inset-0 size-full opacity-[0.7] mix-blend-soft-light"      >        <filter id={noiseId}>          <feTurbulence            type="fractalNoise"            baseFrequency="0.95"            numOctaves="4"            stitchTiles="stitch"          />        </filter>        <rect width="100%" height="100%" filter={`url(#${noiseId})`} />      </svg>      <div className="relative z-10 mx-auto flex h-full min-h-[28rem] w-full max-w-2xl -translate-y-14 @[32rem]:-translate-y-20 flex-col items-center justify-center px-6 py-16 text-center">        <h1 className="text-balance text-xl font-medium tracking-wide text-white @[32rem]:text-2xl">          Build once, reuse everywhere        </h1>        <div className="mt-6 flex items-center justify-center">          <Button            type="button"            size="sm"            className="bg-white text-black hover:opacity-85"          >            Get started          </Button>        </div>      </div>    </section>  );}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add hero-02

Usage

import { Hero02 } from "@/components/blocks/hero/hero-02"
<Hero02 />

Inset image

A full-bleed image with just enough space around the edges to let the page breathe.

Ship interfaces that feel finished

Ready made blocks you can restyle with your tokens.

tsx
"use client";import { Button } from "@/components/ui/button";import { cn } from "@/lib/utils";type Hero03Props = {  className?: string;};export function Hero03({ className }: Hero03Props) {  return (    <section      className={cn("@container w-full bg-background p-3 sm:p-4", className)}    >      <div className="relative min-h-[28rem] w-full flex-1 overflow-hidden rounded-md">        <img          src="/assets/brand/demos/card-cover.png"          alt=""          className="absolute inset-0 size-full object-cover"        />        <div          aria-hidden="true"          className="absolute inset-0 bg-black/45"        />        <div className="absolute inset-0 z-10 flex flex-col items-start justify-end p-6 text-left sm:p-8">          <h1 className="max-w-xl text-balance text-2xl font-medium tracking-wide text-white @[32rem]:text-3xl">            Ship interfaces that feel finished          </h1>          <p className="mt-3 max-w-md text-pretty text-sm tracking-wide text-white/75">            Ready made blocks you can restyle with your tokens.          </p>          <Button type="button" size="sm" className="mt-6">            Get started          </Button>        </div>      </div>    </section>  );}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add hero-03

Usage

import { Hero03 } from "@/components/blocks/hero/hero-03"
<Hero03 />

Split

Copy on the left and a cover image on the right. The layout stacks on narrow widths.

Ship interfaces that feel finished

Ready made blocks you can restyle with your tokens.

tsx
"use client";import { Button } from "@/components/ui/button";import { cn } from "@/lib/utils";type Hero04Props = {  className?: string;};export function Hero04({ className }: Hero04Props) {  return (    <section className={cn("w-full bg-background", className)}>      <div        data-slot="block-split"        className="flex h-full min-h-[28rem] w-full flex-col gap-4 p-4 lg:flex-row lg:items-stretch lg:gap-6 lg:p-6"      >        <div className="flex flex-1 flex-col items-start justify-center py-6 text-left lg:py-10">          <h1 className="max-w-md text-balance text-2xl font-medium tracking-wide text-foreground lg:text-3xl">            Ship interfaces that feel finished          </h1>          <p className="mt-3 max-w-sm text-pretty text-sm tracking-wide text-muted-foreground">            Ready made blocks you can restyle with your tokens.          </p>          <Button type="button" size="sm" className="mt-6">            Get started          </Button>        </div>        <div className="relative min-h-[16rem] w-full flex-1 overflow-hidden rounded-md lg:min-h-0">          <img            src="/assets/brand/demos/card-cover.png"            alt=""            className="absolute inset-0 size-full object-cover"          />        </div>      </div>    </section>  );}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add hero-04

Usage

import { Hero04 } from "@/components/blocks/hero/hero-04"
<Hero04 />

Logo cloud

Centered copy surrounded by floating logos. On narrow widths, the logos become two marquees.

Plays nice with your whole stack

Drop in blocks that already speak the tools your team uses every day.

tsx
"use client";import { useEffect, useRef, type CSSProperties } from "react";import { Button } from "@/components/ui/button";import { cn } from "@/lib/utils";type Hero05Props = {  className?: string;};const LEFT_LOGOS = [  { src: "/assets/icons/logos/slack.svg", x: "6%", y: "14%", s: "3.4rem" },  { src: "/assets/icons/logos/notion.svg", x: "15%", y: "52%", s: "2.9rem" },  { src: "/assets/icons/logos/linear.svg", x: "4%", y: "74%", s: "3.1rem" },  { src: "/assets/icons/logos/github.svg", x: "24%", y: "22%", s: "2.7rem" },  { src: "/assets/icons/logos/figma.svg", x: "21%", y: "78%", s: "2.9rem" },  { src: "/assets/icons/logos/stripe.svg", x: "11%", y: "33%", s: "2.6rem" },  { src: "/assets/icons/logos/intercom.svg", x: "26%", y: "58%", s: "2.5rem" },] as const;const RIGHT_LOGOS = [  { src: "/assets/icons/logos/hubspot.svg", x: "88%", y: "16%", s: "3.3rem" },  { src: "/assets/icons/logos/jira.svg", x: "79%", y: "48%", s: "2.8rem" },  { src: "/assets/icons/logos/zendesk.svg", x: "91%", y: "70%", s: "3rem" },  { src: "/assets/icons/logos/asana.svg", x: "72%", y: "20%", s: "2.6rem" },  { src: "/assets/icons/logos/dropbox.svg", x: "72%", y: "76%", s: "2.9rem" },  { src: "/assets/icons/logos/snowflake.svg", x: "84%", y: "34%", s: "2.5rem" },  { src: "/assets/icons/logos/airtable.svg", x: "74%", y: "58%", s: "2.5rem" },] as const;const MARQUEE_A = [  "slack",  "notion",  "linear",  "github",  "figma",  "stripe",  "intercom",  "airtable",  "hubspot",  "jira",  "zendesk",  "asana",  "dropbox",  "snowflake",] as const;const MARQUEE_B = [  "hubspot",  "jira",  "zendesk",  "asana",  "dropbox",  "snowflake",  "slack",  "notion",  "linear",  "github",  "figma",  "stripe",  "intercom",  "airtable",] as const;function LogoTile({  src,  className,  style,}: {  src: string;  className?: string;  style?: CSSProperties;}) {  return (    <span      data-slot="hero-logo-tile"      className={cn(        "flex items-center justify-center rounded-xl bg-card shadow-sm",        className,      )}      style={style}    >      <img        src={src}        alt=""        className="size-[52%] object-contain brightness-0 dark:invert"      />    </span>  );}export function Hero05({ className }: Hero05Props) {  const stageRef = useRef<HTMLDivElement>(null);  useEffect(() => {    const stage = stageRef.current;    if (!stage) return;    if (!window.matchMedia("(hover: hover) and (pointer: fine)").matches) {      return;    }    const tiles = Array.from(      stage.querySelectorAll<HTMLElement>("[data-slot=hero-logo-tile]"),    ).filter((tile) => tile.offsetParent !== null);    const depths = tiles.map((_, i) => 0.008 + (i % 5) * 0.005);    let targetX = 0;    let targetY = 0;    let currentX = 0;    let currentY = 0;    let raf: number | null = null;    const tick = () => {      currentX += (targetX - currentX) * 0.08;      currentY += (targetY - currentY) * 0.08;      tiles.forEach((tile, i) => {        const d = depths[i];        const dir = i % 2 === 0 ? 1 : -1;        tile.style.transform = `translate(${currentX * d * dir}px, ${currentY * d * dir}px)`;      });      if (        Math.abs(targetX - currentX) > 0.1 ||        Math.abs(targetY - currentY) > 0.1      ) {        raf = requestAnimationFrame(tick);      } else {        raf = null;      }    };    const schedule = () => {      if (!raf) raf = requestAnimationFrame(tick);    };    const onMove = (event: MouseEvent) => {      const rect = stage.getBoundingClientRect();      targetX = event.clientX - (rect.left + rect.width / 2);      targetY = event.clientY - (rect.top + rect.height / 2);      schedule();    };    const onLeave = () => {      targetX = 0;      targetY = 0;      schedule();    };    stage.addEventListener("mousemove", onMove);    stage.addEventListener("mouseleave", onLeave);    return () => {      stage.removeEventListener("mousemove", onMove);      stage.removeEventListener("mouseleave", onLeave);      if (raf) cancelAnimationFrame(raf);    };  }, []);  return (    <section      className={cn(        "@container flex w-full items-center justify-center overflow-hidden bg-background",        className,      )}    >      <style>{`        @keyframes hero-05-scroll {          from { transform: translateX(0); }          to { transform: translateX(-33.333%); }        }      `}</style>      <div        ref={stageRef}        className="relative flex h-full min-h-[28rem] w-full flex-col items-center justify-center gap-10 px-0 py-16 @[40rem]:min-h-[34rem] @[40rem]:gap-0 @[40rem]:px-6"      >        <div          aria-hidden="true"          className="pointer-events-none absolute inset-0 hidden @[40rem]:block"        >          {LEFT_LOGOS.map((logo) => (            <LogoTile              key={`${logo.src}-${logo.x}`}              src={logo.src}              className="absolute will-change-transform"              style={{                top: logo.y,                left: logo.x,                width: logo.s,                height: logo.s,              }}            />          ))}          {RIGHT_LOGOS.map((logo) => (            <LogoTile              key={`${logo.src}-${logo.x}`}              src={logo.src}              className="absolute will-change-transform"              style={{                top: logo.y,                left: logo.x,                width: logo.s,                height: logo.s,              }}            />          ))}        </div>        <div className="relative z-10 flex max-w-md flex-col items-center px-6 text-center">          <h1 className="text-balance text-2xl font-medium tracking-wide text-foreground @[40rem]:text-3xl">            Plays nice with your whole stack          </h1>          <p className="mt-3 max-w-sm text-pretty text-sm tracking-wide text-muted-foreground">            Drop in blocks that already speak the tools your team uses every day.          </p>          <Button type="button" size="sm" className="mt-6">            Get started          </Button>        </div>        <div          aria-hidden="true"          className="flex w-full flex-col gap-3 overflow-hidden @[40rem]:hidden"        >          <div className="flex w-max" style={{ animation: "hero-05-scroll 32s linear infinite" }}>            {[...MARQUEE_A, ...MARQUEE_A, ...MARQUEE_A].map((name, i) => (              <LogoTile                key={`a-${name}-${i}`}                src={`/assets/icons/logos/${name}.svg`}                className="mr-3 size-11 shrink-0"              />            ))}          </div>          <div            className="flex w-max"            style={{              animation: "hero-05-scroll 32s linear infinite reverse",            }}          >            {[...MARQUEE_B, ...MARQUEE_B, ...MARQUEE_B].map((name, i) => (              <LogoTile                key={`b-${name}-${i}`}                src={`/assets/icons/logos/${name}.svg`}                className="mr-3 size-11 shrink-0"              />            ))}          </div>        </div>      </div>    </section>  );}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add hero-05

Usage

import { Hero05 } from "@/components/blocks/hero/hero-05"
<Hero05 />

Social proof

A centered statement, short supporting line, one action, and a little social proof through avatars and a rating.

Ship interfaces that feel finished

Blocks you can drop in and restyle with your tokens.

User 1?User 2?User 3?User 4?User 5?
4.9

from 200+ reviews

tsx
"use client";import { Button } from "@/components/ui/button";import {  Avatar,  AvatarFallback,  AvatarGroup,  AvatarImage,} from "@/components/ui/avatar";import { cn } from "@/lib/utils";type Hero06Props = {  className?: string;};const AVATARS = [  { src: "/assets/brand/demos/avatars/avatar-1.png", alt: "User 1" },  { src: "/assets/brand/demos/avatars/avatar-2.png", alt: "User 2" },  { src: "/assets/brand/demos/avatars/avatar-3.png", alt: "User 3" },  { src: "/assets/brand/demos/avatars/avatar-4.png", alt: "User 4" },  { src: "/assets/brand/demos/avatars/avatar-5.png", alt: "User 5" },] as const;function StarIcon({ className }: { className?: string }) {  return (    <svg      viewBox="0 0 24 24"      fill="currentColor"      aria-hidden="true"      className={className}    >      <path d="M12 2.5l2.9 6.1 6.6.9-4.8 4.6 1.2 6.5L12 17.8 6.1 20.6l1.2-6.5L2.5 9.5l6.6-.9L12 2.5z" />    </svg>  );}export function Hero06({ className }: Hero06Props) {  return (    <section className={cn("@container w-full bg-background", className)}>      <div className="mx-auto flex h-full min-h-[28rem] w-full max-w-2xl flex-col items-center justify-center px-6 py-16 text-center">        <h1 className="text-balance text-2xl font-medium tracking-wide text-foreground @[32rem]:text-3xl">          Ship interfaces that feel finished        </h1>        <p className="mt-3 max-w-sm text-pretty text-sm tracking-wide text-muted-foreground">          Blocks you can drop in and restyle with your tokens.        </p>        <Button type="button" size="sm" className="mt-6">          Get started        </Button>        <div className="mt-10 flex flex-col items-center gap-3 @[24rem]:flex-row @[24rem]:justify-center">          <AvatarGroup overlap="sm">            {AVATARS.map((avatar) => (              <Avatar key={avatar.src} size="sm">                <AvatarImage src={avatar.src} alt={avatar.alt} />                <AvatarFallback>?</AvatarFallback>              </Avatar>            ))}          </AvatarGroup>          <div className="flex flex-col items-center gap-1 @[24rem]:items-start">            <div className="flex items-center gap-1.5">              <div className="flex items-center gap-0.5 text-amber-400" aria-hidden="true">                {Array.from({ length: 5 }).map((_, i) => (                  <StarIcon key={i} className="size-3.5" />                ))}              </div>              <span className="text-xs tracking-wide text-foreground">4.9</span>            </div>            <p className="text-xs tracking-wide text-muted-foreground">              from 200+ reviews            </p>          </div>        </div>      </div>    </section>  );}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add hero-06

Usage

import { Hero06 } from "@/components/blocks/hero/hero-06"
<Hero06 />