# Casebook — Animated Section Block for React

> B2B customer-stories section block: a strip of original customer wordmarks acts as a tablist with a sliding accent underline and auto-advance progress line; the featured story crossfades in with a word-staggered pull quote, seeded avatar attribution and a metric rail rolling on Tally digit drums; below, an industry-filtered (Detent) story grid re-flows with layout springs, each card carrying seeded procedural cover art, cursor tilt and a hover-revealed read link.

- Page: https://crucibleui.com/components/sections/casebook
- Category: [sections](https://crucibleui.com/components/sections)
- Tier: pro (one-time purchase, see https://crucibleui.com/pricing)
- Engine: motion
- npm dependencies: motion

## Install

```bash
npx shadcn@latest add @crucible/casebook
```

Pro install requires adding the token-bearing `@crucible` registry to components.json (issued at https://crucibleui.com/account after purchase).

Import from `@/components/crucible/casebook`.

## Props (`CasebookProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `featured?` | `CasebookStory[]` | `six seeded sample stories` | Stories in the featured strip (4–6 reads best). Pass `[]` to hide the featured strip. |
| `stories?` | `CasebookStory[]` | `eight seeded sample stories` | Stories in the filterable grid below. Pass `[]` to hide the grid. |
| `industries?` | `string[]` | — | Industry filter tabs, in order. Omit to derive from `stories`. Pass `[]` to hide the filter. |
| `autoplay?` | `boolean` | `true` | Auto-advance the featured story (pauses on hover/focus, offscreen, hidden tab; off under reduced motion). |
| `interval?` | `number` | `7000` | Milliseconds each featured story is shown while autoplaying. |
| `index?` | `number` | — | Controlled featured index. |
| `defaultIndex?` | `number` | `0` | Initial featured index when uncontrolled. |
| `onIndexChange?` | `(index: number) => void` | — | Fires whenever the featured index changes (click, keyboard, autoplay, `goTo`). |
| `onSelect?` | `(story: CasebookStory, source: "featured" \| "grid") => void` | — | Fires when a "Read story" link/button or grid card is activated. |
| `eyebrow?` | `React.ReactNode` | `"Customer stories"` | Small label above the headline. `null` hides it. |
| `title?` | `React.ReactNode` | `"Results our customers can measure"` | Section headline (`<h2>`). `null` hides the header. |
| `description?` | `React.ReactNode` | — | Supporting copy under the headline. `null` hides it. |
| `gridTitle?` | `React.ReactNode` | `"More stories"` | Heading above the story grid. `null` hides it. |
| `readLabel?` | `string` | `"Read the story"` | Label of the featured "read" link. |
| `allLabel?` | `string` | `"All"` | Label of the "show everything" filter tab. |
| `accentColor?` | `string` | `"#f5d0a9"` | Signature accent hex: active underline, progress line, metric rules, focus rings. |
| `coverTone?` | `"seeded" \| "accent"` | `"seeded"` | Grid cover-art palette. `"seeded"` gives every card its own hue from the company name; `"accent"` keeps the seeded composition but draws both glows from `accentColor`'s hue family so covers match the brand. |
| `ref?` | `React.Ref<CasebookHandle>` | — | Imperative handle (`replay()`, `goTo(i)`). React 19 ref-as-prop. |
| `className?` | `string` | — | Extra classes for the `<section>` root. |

Also accepts: `Omit<React.ComponentPropsWithoutRef<"section">, "children" | "title" | "onSelect">`.

## Usage example

```tsx
"use client";

import * as React from "react";

import { Casebook, type CasebookHandle } from "@/components/crucible/casebook";

export default function CasebookDemo() {
  const ref = React.useRef<CasebookHandle>(null);

  return (
    <div className="relative isolate flex h-full min-h-[360px] w-full items-start justify-center overflow-hidden rounded-xl bg-neutral-950">
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10"
        style={{
          background:
            "radial-gradient(55% 40% at 15% 0%, rgba(245,208,169,0.07), transparent 70%), radial-gradient(40% 30% at 100% 100%, rgba(255,255,255,0.03), transparent 70%)",
        }}
      />
      <div className="relative z-10 h-full max-h-[760px] w-full overflow-y-auto px-5 py-10 sm:px-10 sm:py-14">
        <div className="mx-auto w-full max-w-6xl">
          <div className="mb-8 flex items-center justify-between gap-3 text-xs text-white/45">
            <span>Pick a customer — filter by industry.</span>
            <button
              type="button"
              onClick={() => ref.current?.replay()}
              className="rounded underline underline-offset-4 transition-colors hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
            >
              Replay
            </button>
          </div>
          <Casebook ref={ref} />
        </div>
      </div>
    </div>
  );
}
```

## Accessibility & performance

Honors `prefers-reduced-motion` with a designed static frame or fade (never a blank box) and passes `className` through.
