# Hero (Caseroom block)

The opening section: h1, one sentence, two actions and an install command; compact for an inner page. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

48% of the 2,062 sites in The UI Index have a hero (982 sites).

## Install

- React: `npx shadcn@latest add https://www.themes.dev/caseroom/r/hero.json`
- Astro: `npx shadcn@latest add https://www.themes.dev/caseroom/r/astro/hero.json`
- HTML: https://www.themes.dev/caseroom/r/html/hero.html (with https://www.themes.dev/caseroom/r/html/caseroom.css)

Page: https://www.themes.dev/caseroom/blocks/hero/ · Rules for agents: https://www.themes.dev/caseroom/AGENT.md

## Code

### hero.astro

```astro
---
import Badge from '@/components/ui/badge.astro';
import Button from '@/components/ui/button.astro';
import { cn } from '@/lib/utils';
import { commandParts, hero, partClasses, type HeroContent } from './hero.classes';

type Props = HeroContent & { class?: string };
const { eyebrow, title, description, primary, secondary, command, align = 'center', size = 'default', class: className } = Astro.props;
---

<section class={cn(hero.root[size], className)}>
  <div class={hero.inner[align]}>
    {eyebrow && <Badge variant="outline">{eyebrow}</Badge>}
    <h1 class={hero.title[size]}>{title}</h1>
    {description && <p class={hero.description[size]}>{description}</p>}
    {
      (primary || secondary) && (
        <div class={cn(hero.actions[align], hero.actionsGap[size])}>
          {primary && <Button href={primary.href} size="lg">{primary.label}</Button>}
          {secondary && <Button href={secondary.href} size="lg" variant="outline">{secondary.label}</Button>}
        </div>
      )
    }
    {
      command && (
        <p class={hero.command}>
          <span aria-hidden="true">$</span>
          <code>{commandParts(command).map((word, w) => <>{w > 0 && ' '}{word.map((part, k) => <>{k > 0 && <wbr />}<span class={partClasses}>{part}</span></>)}</>)}</code>
        </p>
      )
    }
  </div>
</section>
```

### hero.classes.ts

```tsx
export const hero = {
  // default: the opening of a home or product page. compact: the head of an inner page (pricing, blog, docs), a
  // smaller h1 and less air, so the content it introduces starts above the fold.
  root: { default: 'bg-background px-6 py-20 sm:py-28', compact: 'bg-background px-6 py-12 sm:py-16' },
  // center: centred from 640px up and left-aligned on phones, where a centred lede would run to five lines.
  // start: on the page's left edge at every width, for a page whose next sections are left-aligned (one axis).
  inner: {
    center: 'mx-auto flex max-w-3xl flex-col items-start text-left sm:items-center sm:text-center',
    start: 'flex max-w-3xl flex-col items-start text-left',
  },
  title: {
    default: 'mt-5 text-4xl leading-[1.05] font-semibold tracking-[-0.035em] text-balance sm:text-6xl',
    compact: 'mt-4 text-3xl leading-[1.1] font-semibold tracking-[-0.03em] text-balance sm:text-5xl',
  },
  description: {
    default: 'mt-5 max-w-xl text-base text-pretty text-muted-foreground sm:text-lg',
    compact: 'mt-4 max-w-xl text-base text-pretty text-muted-foreground',
  },
  actions: { center: 'flex flex-wrap gap-3 sm:justify-center', start: 'flex flex-wrap gap-3' },
  actionsGap: { default: 'mt-8', compact: 'mt-6' },
  // A long command wraps instead of pushing the page wider than a phone (320px).
  command: 'mt-6 inline-flex max-w-full items-start gap-2 rounded-md border bg-muted px-3 py-1.5 text-left font-mono text-[13px] text-muted-foreground [&_code]:min-w-0 [&_code]:[overflow-wrap:anywhere]',
};

/** A command split so it breaks only at spaces and after / or @ (not inside //), never inside a name (button.json). */
export const commandParts = (cmd: string) => cmd.split(' ').map((word) => word.split(/(?<=[/@])(?!\/)/));
export const partClasses = 'whitespace-nowrap';

export type HeroAction = { label: string; href: string };
export type HeroContent = {
  eyebrow?: string;
  title: string;
  description?: string;
  primary?: HeroAction;
  secondary?: HeroAction;
  /** An install command shown under the buttons, e.g. "npx shadcn add @caseroom/hero". */
  command?: string;
  /** center (default): centred from 640px up. start: left-aligned, when the sections after it are. */
  align?: 'center' | 'start';
  /** default: a page's opening. compact: the head of an inner page, a smaller h1 and less air. */
  size?: 'default' | 'compact';
};
```

### hero.tsx

```tsx
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { Fragment } from 'react';
import { commandParts, hero, partClasses, type HeroContent } from './hero.classes';

export type HeroProps = HeroContent & { className?: string };

/** The opening section of a page: the h1, one sentence and at most two actions. */
export function Hero({ eyebrow, title, description, primary, secondary, command, align = 'center', size = 'default', className }: HeroProps) {
  return (
    <section className={cn(hero.root[size], className)}>
      <div className={hero.inner[align]}>
        {eyebrow && <Badge variant="outline">{eyebrow}</Badge>}
        <h1 className={hero.title[size]}>{title}</h1>
        {description && <p className={hero.description[size]}>{description}</p>}
        {(primary || secondary) && (
          <div className={cn(hero.actions[align], hero.actionsGap[size])}>
            {primary && <Button href={primary.href} size="lg">{primary.label}</Button>}
            {secondary && <Button href={secondary.href} size="lg" variant="outline">{secondary.label}</Button>}
          </div>
        )}
        {command && (
          <p className={hero.command}>
            <span aria-hidden="true">$</span>
            <code>
              {commandParts(command).map((word, w) => (
                <Fragment key={w}>
                  {w > 0 && ' '}
                  {word.map((part, k) => (
                    <Fragment key={k}>
                      {k > 0 && <wbr />}
                      <span className={partClasses}>{part}</span>
                    </Fragment>
                  ))}
                </Fragment>
              ))}
            </code>
          </p>
        )}
      </div>
    </section>
  );
}
```
