# Hero Section Block

> A Hero Section Block component for displaying a hero section.

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

- Page, with a live preview: https://shadcn.exchange/r/devsloka/hero-section
- Screenshot: https://shadcn.exchange/shot/devsloka/hero-section/light.png
- Registry item: https://ui.devsloka.in/r/hero-section.json
- Listed: 2026-10-28, source last changed 2026-10-28

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/devsloka/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`
- Registry items: `button`

## Files

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

```tsx
import { useId, type ComponentProps } from "react";
import { ArrowRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

type Action = { label: string; href: string };

type CalendarEvent = {
  /** 0 = Monday. */
  day: number;
  /** Start and end in hours, e.g. 9.5 for 09:30. */
  start: number;
  end: number;
  title: string;
  /** Protected focus time is drawn in the accent color. */
  focus?: boolean;
};

export type HeroSectionBlockProps = {
  eyebrow?: string;
  title?: string;
  description?: string;
  primaryAction?: Action;
  secondaryAction?: Action;
  note?: string;
  events?: CalendarEvent[];
} & ComponentProps<"section">;

const DAYS = ["Mon 12", "Tue 13", "Wed 14", "Thu 15", "Fri 16"];
const FIRST_HOUR = 8;
const LAST_HOUR = 18;

const DEFAULT_EVENTS: CalendarEvent[] = [
  { day: 0, start: 9, end: 12, title: "Deep work", focus: true },
  { day: 0, start: 14, end: 14.5, title: "1:1 with Priya" },
  { day: 1, start: 9, end: 12, title: "Deep work", focus: true },
  { day: 1, start: 15, end: 16, title: "Design review" },
  { day: 2, start: 10, end: 10.5, title: "Standup, moved" },
  { day: 2, start: 13, end: 17, title: "No meetings Wednesday", focus: true },
  { day: 3, start: 9, end: 12, title: "Deep work", focus: true },
  { day: 3, start: 16, end: 16.5, title: "Hiring sync" },
  { day: 4, start: 9, end: 11, title: "Writing", focus: true },
  { day: 4, start: 15.5, end: 16.5, title: "Demo" },
];

const DEFAULTS = {
  eyebrow: "Margin 3 · Released 6 Oct 2026",
  title: "Fewer meetings. Longer afternoons.",
  description:
    "Margin is a calendar that defends your focus time. It books meetings into the gaps you choose and quietly declines the rest.",
  primaryAction: { label: "Start free, no card", href: "#" },
  secondaryAction: { label: "Read the changelog", href: "#" },
  note: "Used by 18,000 people who guard their mornings.",
};

const pad = (n: number) => String(n).padStart(2, "0");
const formatHour = (h: number) => `${pad(Math.floor(h))}:${pad(Math.round((h % 1) * 60))}`;

export default function HeroSectionBlock({
  eyebrow = DEFAULTS.eyebrow,
  title = DEFAULTS.title,
  description = DEFAULTS.description,
  primaryAction = DEFAULTS.primaryAction,
  secondaryAction = DEFAULTS.secondaryAction,
  note = DEFAULTS.note,
  events = DEFAULT_EVENTS,
  className,
  ...props
}: HeroSectionBlockProps) {
  const headingId = useId();
  const span = LAST_HOUR - FIRST_HOUR;

  return (
    <section
      data-slot="hero-section"
      aria-labelledby={headingId}
      className={cn("w-full bg-background text-foreground", className)}
      {...props}
    >
      <div className="mx-auto max-w-6xl px-6 pt-20 pb-16 md:pt-28">
        <p className="font-mono text-xs uppercase tracking-wider text-muted-foreground">
          {eyebrow}
        </p>
        <h1
          id={headingId}
          className="mt-8 max-w-5xl text-5xl font-semibold leading-[0.95] tracking-[-0.045em] text-balance sm:text-6xl md:text-8xl"
        >
          {title}
        </h1>

        <div className="mt-12 grid gap-8 border-t pt-6 md:grid-cols-12 md:gap-10">
          <p className="max-w-md text-base leading-relaxed text-muted-foreground md:col-span-5">
            {description}
          </p>
          <div className="flex flex-wrap items-start gap-x-6 gap-y-3 md:col-span-4">
            <Button asChild size="lg" className="rounded-none">
              <a href={primaryAction.href}>
                {primaryAction.label}
                <ArrowRight aria-hidden />
              </a>
            </Button>
            <a
              href={secondaryAction.href}
              className="py-2 text-sm font-medium underline decoration-1 underline-offset-[6px] hover:text-muted-foreground"
            >
              {secondaryAction.label}
            </a>
          </div>
          <p className="font-mono text-xs leading-relaxed text-muted-foreground md:col-span-3 md:text-right">
            {note}
          </p>
        </div>

        {/* A plain week view: the product, not an illustration of it. */}
        <figure className="mt-16 border">
          <figcaption className="flex items-center justify-between border-b px-4 py-2.5 font-mono text-xs text-muted-foreground">
            <span>Week 42 · October</span>
            <span className="flex items-center gap-2">
              <span aria-hidden className="size-2 bg-primary" />
              Protected focus time
            </span>
          </figcaption>
          <div className="overflow-x-auto">
            <div className="grid min-w-[640px] grid-cols-[3.5rem_repeat(5,1fr)]">
              <div className="border-r" />
              {DAYS.map((day) => (
                <div
                  key={day}
                  className="border-r px-3 py-2 font-mono text-xs uppercase tracking-wider text-muted-foreground last:border-r-0"
                >
                  {day}
                </div>
              ))}

              <div className="relative border-t border-r" style={{ height: `${span * 2.25}rem` }}>
                {Array.from({ length: span }, (_, i) => (
                  <span
                    key={i}
                    className="absolute right-2 -translate-y-1/2 font-mono text-[10px] text-muted-foreground"
                    style={{ top: `${(i / span) * 100}%` }}
                  >
                    {i > 0 && formatHour(FIRST_HOUR + i)}
                  </span>
                ))}
              </div>
              {DAYS.map((day, dayIndex) => (
                <div
                  key={day}
                  className="relative border-t border-r last:border-r-0"
                  style={{
                    backgroundImage:
                      "linear-gradient(to bottom, var(--border) 1px, transparent 1px)",
                    backgroundSize: `100% ${100 / span}%`,
                  }}
                >
                  {events
                    .filter((event) => event.day === dayIndex)
                    .map((event) => {
                      // Half-hour events don't fit two lines, so they get one compact line.
                      const compact = event.end - event.start < 1;
                      const time = `${formatHour(event.start)}–${formatHour(event.end)}`;
                      return (
                        <div
                          key={`${event.title}-${event.start}`}
                          title={`${event.title}, ${time}`}
                          className={cn(
                            "absolute inset-x-1 overflow-hidden border px-2 text-xs",
                            compact ? "flex items-center leading-none" : "py-1",
                            event.focus
                              ? "border-primary bg-primary text-primary-foreground"
                              : "bg-background"
                          )}
                          style={{
                            top: `${((event.start - FIRST_HOUR) / span) * 100}%`,
                            height: `${((event.end - event.start) / span) * 100}%`,
                          }}
                        >
                          <p className="truncate font-medium">
                            {event.title}
                            {compact && <span className="sr-only">, {time}</span>}
                          </p>
                          {!compact && (
                            <p
                              className={cn(
                                "font-mono text-[10px]",
                                event.focus ? "text-primary-foreground/70" : "text-muted-foreground"
                              )}
                            >
                              {time}
                            </p>
                          )}
                        </div>
                      );
                    })}
                </div>
              ))}
            </div>
          </div>
        </figure>
      </div>
    </section>
  );
}
```
