Steward
In-app support messenger block: a round launcher with a Tally unread badge that morphs (shared layout) into a 380×600 panel — a full-screen sheet on phones — with Home / Messages / Help tabs. Home has a team avatar stack, reply-time line, system-status strip, highlighted article search, recent conversation and a message CTA. Messages pushes from a thread list into a role="log" conversation with grouped bubbles, typing dots, delivered/read ticks, attachment chips, a hand-off card and a Sluice composer with optimistic send + retry. Help pushes categories → article reader with "Was this helpful?" that collapses into a thanks line. ref.receive() types then delivers, bouncing the launcher once when closed. Escape closes and restores focus; crossfades under reduced motion.
The preview is live. The source is Pro.
One purchase, lifetime access: every pro component's source, CLI install with your own registry token, and all future drops.
Installation
CLI
npx shadcn@latest add @crucible/stewardManual — install dependencies, then copy the source
npm install motionProps
| Prop | Type | Default | Description |
|---|---|---|---|
| team | StewardTeam | a seeded three-person team | Support team: avatar stack, greeting and reply time. |
| status | StewardStatus | null | all systems operational | System-status strip on Home. Pass null to hide it. |
| articles | StewardArticle[] | seeded articles | Help-center articles: searchable on Home and Help, grouped by category. |
| conversations | StewardConversation[] | seeded sample | Initial conversations (the widget owns them afterwards). Passing a new array resets them. |
| onSend | (text: string, conversationId: string) => void | Promise<unknown> | Fired when the visitor sends a message. The bubble appears optimistically; return a Promise — resolve marks it delivered, reject marks it failed with Retry. | |
| onFeedback | (articleId: string, helpful: boolean) => void | Promise<unknown> | Fired from "Was this helpful?" on an article. A returned Promise holds the buttons pending; reject shows a retry hint. | |
| inline | boolean | false | Render the panel in place, always open, with no launcher. |
| defaultOpen | boolean | false | Start open (launcher mode). |
| defaultTab | StewardTab | "home" | Tab shown first. |
| onOpenChange | (open: boolean) => void | Fired when the panel opens or closes. | |
| position | "fixed" | "absolute" | "fixed" | "fixed" pins the launcher to the viewport; "absolute" pins it to the nearest positioned ancestor (e.g. a preview frame). |
| launcher | React.ReactNode | Custom launcher icon (replaces the chat glyph). | |
| label | string | "Support" | Accessible name of the panel. |
| accentColor | string | "#38bdf8" | The single accent: launcher, your bubbles, links, focus rings, active tab. Hex gets auto black/white ink. |
| ref | React.Ref<StewardHandle> | Imperative handle — open(tab?), close(), receive(msg), replay(). | |
| className | string | Classes for the root (the panel in inline mode). | |
| panelClassName | string | Classes for the panel, e.g. a width or height. |
Honors prefers-reduced-motion with a designed static fallback, and passes className through.