# Tailstock — Animated Section Block for React

> Big site footer: link columns that collapse to 2-up then <details> accordions via container queries, a labelled async newsletter form, social row, live status pill and legal row — signed by an oversized wordmark that rises through a clip-path curtain with a letter stagger and one sheen sweep. Compact variant drops the wordmark.

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

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

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

## Props (`TailstockProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `wordmark?` | `string` | `"Studio"` | Oversized wordmark text that rises through a curtain reveal at the foot of the footer. Ignored when `compact`. |
| `tagline?` | `React.ReactNode` | — | Short line under the small brand name at the top-left. |
| `columns?` | `TailstockColumn[]` | — | Link columns. Omit for a sample Product / Resources / Company set. |
| `onSubscribe?` | `(email: string) => void \| Promise<unknown>` | — | Called with the submitted email. Return a Promise to hold the button in its pending state — resolve shows the success state, reject shows the rejection's message (or a generic one) inline. The newsletter form is always rendered; without a handler it succeeds immediately. |
| `newsletterTitle?` | `React.ReactNode` | `"Stay in the loop"` | Heading above the newsletter field. |
| `status?` | `TailstockStatus \| false` | `{ label: "All systems normal", tone: "operational" }` | Status pill config, or `false` to hide it. |
| `socials?` | `TailstockSocial[]` | — | Social icon links. Omit for a generic sample row; pass `[]` to hide. |
| `legal?` | `TailstockLink[]` | `Privacy / Terms / Cookies` | Legal-row links. |
| `copyright?` | `React.ReactNode` | `"© 2026 {wordmark}. All rights reserved."` | Copyright line in the legal row. |
| `compact?` | `boolean` | `false` | Drop the oversized wordmark for a tighter, utility footer. |
| `accentColor?` | `string` | `"#c4b5fd"` | Accent hex for the subscribe button, focus rings, hovers and the wordmark sheen. |
| `ref?` | `React.Ref<TailstockHandle>` | — | Imperative handle (`replay()`, `focusNewsletter()`). React 19 ref-as-prop. |
| `className?` | `string` | — | Extra classes for the `<footer>` root. |

Also accepts: `Omit<React.ComponentPropsWithoutRef<"footer">, "children">`.

## Usage example

```tsx
"use client";

import * as React from "react";

import { Tailstock, type TailstockHandle } from "@/components/crucible/tailstock";

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

  return (
    <div className="relative isolate h-[560px] w-full overflow-y-auto overscroll-contain rounded-xl bg-neutral-950">
      {/* Sample page content above the footer. */}
      <div className="relative z-10 mx-auto flex min-h-[560px] max-w-3xl flex-col items-center justify-center gap-6 px-6 text-center">
        <p className="text-2xl font-semibold tracking-tight text-white sm:text-3xl">Scroll to the footer.</p>
        <p className="max-w-sm text-sm text-white/50">The wordmark rises in the first time it enters view.</p>
        <svg aria-hidden viewBox="0 0 24 24" className="size-5 text-white/40 motion-safe:animate-bounce" fill="none">
          <path d="M12 5v14m0 0-5-5m5 5 5-5" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
        </svg>
      </div>
      <div aria-hidden className="relative z-10 mx-auto grid max-w-3xl gap-3 px-6 pb-20 sm:grid-cols-3">
        {[0, 1, 2].map((i) => (
          <div key={i} className="rounded-xl border border-white/[0.06] bg-white/[0.02] p-5">
            <div className="h-2.5 w-1/2 rounded-full bg-white/10" />
            <div className="mt-4 h-2 w-full rounded-full bg-white/[0.06]" />
            <div className="mt-2 h-2 w-4/5 rounded-full bg-white/[0.06]" />
          </div>
        ))}
      </div>

      <Tailstock
        ref={ref}
        className="relative z-10"
        onSubscribe={() => new Promise<void>((resolve) => window.setTimeout(resolve, 900))}
        status={{ label: "All systems normal", tone: "operational", href: "#" }}
      />
      <div className="relative z-10 flex justify-center bg-neutral-950 pb-4">
        <button
          type="button"
          onClick={() => ref.current?.replay()}
          className="rounded text-xs text-white/40 underline underline-offset-4 transition-colors hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
        >
          Replay reveal
        </button>
      </div>
    </div>
  );
}
```

## Accessibility & performance

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