# Muster — Animated Section Block for React

> Waitlist section block: an email capsule that spring-morphs into a confirmation card with your queue number rolling on Tally digit drums, a copyable referral link, and a seeded avatar stack the joiner drops into. Async onJoin with pending/error states, shake-on-invalid validation, and a contact variant.

- Page: https://crucibleui.com/components/sections/muster
- 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/muster
```

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

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

## Props (`MusterProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title?` | `React.ReactNode` | `"Get early access" (waitlist) / "Get in touch" (contact)` | Section headline (`<h2>`). |
| `description?` | `React.ReactNode` | `depend on `variant`` | Supporting copy under the headline. |
| `count?` | `number` | `2481` | People already waiting. The joiner becomes `#count + 1`, and the stack counter ticks +1 on success. |
| `onJoin?` | `(email: string, details?: MusterDetails) => void \| Promise<unknown>` | — | Called with the submitted email (plus name/message in the contact variant). Return a Promise to hold the button in its pending state — resolve morphs to the confirmation card, reject returns to the form with the rejection's message (or a generic one) as the inline error. |
| `referralBase?` | `string` | `"https://example.com/?ref="` | URL base for the referral link; a short code seeded from the email is appended. |
| `variant?` | `MusterVariant` | `"waitlist"` | `"waitlist"` — email capsule → "You're #N" card with referral link. `"contact"` — name/email/message panel → "Message sent" card (no position). |
| `avatars?` | `string[]` | — | Image URLs for the avatar stack (first 5 used). Omit for seeded gradient avatars. The joiner's own avatar is always a seeded gradient. |
| `accentColor?` | `string` | `"#7dd3fc"` | Accent hex for the submit control, focus rings and card highlights. |
| `ref?` | `React.Ref<MusterHandle>` | — | Imperative handle (`reset()`, `focus()`). React 19 ref-as-prop. |
| `className?` | `string` | — | Extra classes for the `<section>` root. |

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

## Usage example

```tsx
"use client";

import * as React from "react";

import { Muster, type MusterHandle } from "@/components/crucible/muster";

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

  return (
    <div className="relative isolate flex h-full min-h-[360px] w-full items-center justify-center overflow-hidden rounded-xl bg-neutral-950">
      {/* Subtle radial backdrop */}
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10"
        style={{
          background:
            "radial-gradient(60% 55% at 50% 38%, rgba(125,211,252,0.10), transparent 70%), radial-gradient(40% 40% at 50% 100%, rgba(255,255,255,0.04), transparent 70%)",
        }}
      />
      <div className="relative z-10 flex w-full flex-col items-center pb-6">
        <Muster
          ref={ref}
          onJoin={() => new Promise<void>((resolve) => window.setTimeout(resolve, 900))}
        />
        <div className="relative -mt-8 flex items-center gap-3 text-xs text-white/45">
          <span>Enter any email to join.</span>
          <button
            type="button"
            onClick={() => ref.current?.reset()}
            className="rounded underline underline-offset-4 transition-colors hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
          >
            Reset
          </button>
        </div>
      </div>
    </div>
  );
}
```

## Accessibility & performance

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