# Gantry — Animated App UI Block for React

> App shell block: a workspace switcher, sectioned sidebar nav with height-animated nested groups, an active pill that slides between items, Tally badges that roll on change, a pinned footer card and a user menu; the sidebar springs between full width and an icon rail (toggle or `[`) with label flyouts and nested flyout menus; the topbar carries breadcrumbs, a ⌘K manifest command palette and a tocsin notifications bell; under 720px of container width the sidebar becomes a swipe-to-close drawer with a focus trap. Controlled collapsed / activeId, skip link and a <main> landmark.

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

## Install

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

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

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

## Props (`GantryProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `brand?` | `string` | `"Lumen"` | Product name — shown under the workspace name when the workspace has no `plan`. |
| `logo?` | `React.ReactNode` | — | Square mark used for workspaces without their own `logo`. |
| `workspaces?` | `GantryWorkspace[]` | `three sample workspaces` | Workspaces in the switcher. With one workspace (and no `onCreateWorkspace`) the switcher is a static header. |
| `workspaceId?` | `string` | — | Controlled current workspace id. |
| `defaultWorkspaceId?` | `string` | `the first workspace` | Initial workspace when uncontrolled. |
| `onWorkspaceChange?` | `(id: string) => void` | — | Fires when a workspace is picked from the switcher. |
| `onCreateWorkspace?` | `() => void` | `a no-op in the sample` | Shows "Create workspace" in the switcher. |
| `sections?` | `GantrySection[]` | `a sample product nav` | Sidebar nav, grouped into sections. |
| `activeId?` | `string` | — | Controlled active nav item id. |
| `defaultActiveId?` | `string` | `"relaunch" (sample) or the first item` | Initial active id when uncontrolled. |
| `onNavigate?` | `(id: string, item: GantryNavItem) => void` | — | Fires when a nav item (or a "Go to" command) is chosen. |
| `collapsed?` | `boolean` | — | Controlled rail mode. Pair with `onCollapsedChange`. |
| `defaultCollapsed?` | `boolean` | `false` | Initial rail mode when uncontrolled. |
| `onCollapsedChange?` | `(collapsed: boolean) => void` | — | Fires whenever rail mode changes (toggle button, `[` key, handle). |
| `user?` | `GantryUser` | `a sample user` | Signed-in user (bottom chip). |
| `userMenu?` | `GantryMenuItem[]` | `Profile / Settings / Sign out` | User menu entries. |
| `onUserMenuSelect?` | `(id: string) => void` | — | Fires when a user-menu entry is chosen. |
| `breadcrumbs?` | `GantryBreadcrumb[]` | `derived: workspace / parent / active item` | Topbar breadcrumbs. |
| `footer?` | `React.ReactNode` | `a sample usage card` | Pinned card above the user chip (usage meter, upgrade CTA…). `null` hides it. |
| `actions?` | `React.ReactNode` | — | Extra topbar controls, rendered before the notifications bell. |
| `notifications?` | `Omit<TocsinProps, "ref" \| "inline"> \| false` | — | Props forwarded to the `tocsin` notifications bell, or `false` to hide it. Its accent defaults to `accentColor`. |
| `commands?` | `ManifestItem[]` | — | Extra ⌘K palette commands (forwarded to `manifest`). "Go to" commands for every nav item are always included. |
| `searchLabel?` | `string` | `"Search…"` | Search trigger label. |
| `searchPlaceholder?` | `string` | `"Search pages and commands…"` | Placeholder inside the palette. |
| `hotkeys?` | `boolean` | `true` | Global shortcuts: ⌘K opens search, `[` toggles the sidebar (ignored while typing). |
| `children?` | `React.ReactNode` | `a sample placeholder page` | Page content, rendered in the scrollable `<main>` region. |
| `mainClassName?` | `string` | — | Classes for the scrollable `<main>` region. |
| `accentColor?` | `string` | `"#c4b5fd"` | The single accent: active pill bar, badges, focus rings, current workspace tile. Hex gets auto black/white ink. |
| `ref?` | `React.Ref<GantryHandle>` | — | Imperative handle — `collapse()`, `expand()`, `toggle()`, `openSearch()`, `replay()`. |
| `className?` | `string` | — | Classes for the root (give it a height, e.g. `h-[600px]` or `h-dvh`). |
| `style?` | `React.CSSProperties` | — | Inline styles for the root. |

## Usage example

```tsx
"use client";

import { Gantry } from "@/components/crucible/gantry";

export default function GantryDemo() {
  return (
    <div className="relative isolate flex h-full min-h-[660px] w-full flex-col gap-2.5 overflow-hidden rounded-xl bg-neutral-950 p-2.5 sm:p-4">
      <div
        aria-hidden
        className="pointer-events-none absolute inset-0 -z-10"
        style={{ background: "radial-gradient(60% 50% at 20% 0%, rgba(196,181,253,0.07), transparent 70%)" }}
      />
      <p className="relative z-10 px-1 text-xs text-neutral-500">
        Collapse the sidebar — press{" "}
        <kbd className="rounded border border-white/10 bg-white/[0.04] px-1 font-sans text-[11px] text-neutral-400">⌘K</kbd>.
      </p>
      <Gantry className="relative z-10 h-[600px] flex-none rounded-xl border border-white/[0.08]" />
    </div>
  );
}
```

## Accessibility & performance

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