mcpField
N
nexus-ui

nexus-ui

github.com/victorcodessDesign & UI13254 installsUpdated 19w ago
Repository

About nexus-ui

Nexus UI is an open-source React component library for developers building AI interfaces with chat, streaming, and multimodal experiences. Its components are installed as source through the shadcn registry and are built on Tailwind CSS v4 and Radix UI.

Key features

  1. 01Chat and prompt-input components
  2. 02Model selection and prompt suggestions
  3. 03Attachment previews and controls
  4. 04Message, thread, and citation components
  5. 05Tool-call and reasoning status displays

Use cases

  1. 01Build AI chat input and message interfaces
  2. 02Show attachments and source citations in conversations
  3. 03Display tool runs, reasoning, and loading states

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/victorcodess/nexus-ui --skill nexus-ui

After installing, type/nexus-uiin Claude Code to confirm it loaded.

Details

From the project README

Nexus UI

An open-source component library for building AI-powered interfaces. Composable, copy-paste primitives for chat, streaming, and multimodal experiences — built on React, Tailwind CSS v4, and Radix UI.

Components are distributed via the shadcn registry and installed as source code into the user's project.

IMPORTANT: Run CLI commands using the project's package runner: npx shadcn@latest, pnpm dlx shadcn@latest, or bunx --bun shadcn@latest — based on the project's packageManager.

Installation

1. Add the registry

Add the Nexus UI registry to the project's components.json:

{
  "registries": {
    "@nexus-ui": "https://nexus-ui.dev/r/{name}.json"
  }
}
2. Add components
npx shadcn@latest add @nexus-ui/prompt-input
npx shadcn@latest add @nexus-ui/model-selector
npx shadcn@latest add @nexus-ui/suggestions
npx shadcn@latest add @nexus-ui/attachments
npx shadcn@latest add @nexus-ui/message
npx shadcn@latest add @nexus-ui/thread
npx shadcn@latest add @nexus-ui/citation
npx shadcn@latest add @nexus-ui/reasoning
npx shadcn@latest add @nexus-ui/text-shimmer
npx shadcn@latest add @nexus-ui/image
npx shadcn@latest add @nexus-ui/feedback-bar
npx shadcn@latest add @nexus-ui/toaster
npx shadcn@latest add @nexus-ui/chain-of-thought
npx shadcn@latest add @nexus-ui/tool
npx shadcn@latest add @nexus-ui/questions

Or install directly via URL (no registry config needed):

npx shadcn@latest add https://nexus-ui.dev/r/prompt-input.json

Components are installed to components/nexus-ui/ by default.

Prerequisites
  • React 19+
  • Tailwind CSS v4
  • TypeScript
  • shadcn/ui initialized (npx shadcn@latest init)

Available Components

ComponentRegistry nameDescription
Prompt Inputprompt-inputComposable chat input with auto-resizing textarea and action slots
Model Selectormodel-selectorDropdown for selecting AI models with radio groups, sub-menus, and custom items
SuggestionssuggestionsPrompt suggestion chips for guiding user input
AttachmentsattachmentsComposable file attachments for chat inputs and messages with preview, variants, and upload wiring
MessagemessageChat message layout with markdown, optional avatar, actions, and attachments
ThreadthreadScrollable chat thread with stick-to-bottom scrolling and jump-to-bottom control
CitationcitationInline source references with hover preview and multi-source carousel
ReasoningreasoningCollapsible model reasoning trace with streaming-aware labels and markdown body
Text Shimmertext-shimmerAnimated shimmer text for loading, tool runs, and other in-progress UI
ImageimageImage renderer for URLs, base64, and byte payloads with preview, loader, lightbox, and action slots
Feedback Barfeedback-barFeedback prompt bar for per-message or thread ratings with action and close slots
ToastertoasterHeadless toast notifications powered by Sonner, with variant-aware styling and custom action/cancel controls
Chain of Thoughtchain-of-thoughtStructured multi-step thought timeline with step status, optional expandable output, and auto-close when steps finish
TooltoolState-aware tool call UI with JSON input/output codeblocks (pending, ready, running, completed, error)
QuestionsquestionsFollow-up clarification questions with single or multiple choice, carousel navigation, and batch submission

Component APIs

Each section uses the same outline: Import → Parts → Root props → Props & hooks → Usage notes → Example (when a short snippet helps).

Prompt Input

Composable chat shell: auto-growing textarea plus a bottom action row for attach/send and similar controls.

Import:

import {
  PromptInput,
  PromptInputTextarea,
  PromptInputActions,
  PromptInputActionGroup,
  PromptInputAction,
} from "@/components/nexus-ui/prompt-input";

Parts:

PartPurpose
PromptInputRoot layout: bordered card wrapping textarea and actions.
PromptInputTextareaAuto-resizing textarea (max height ~160px) inside shadcn ScrollArea.
PromptInputActionsBottom bar; justify-between for left vs right action groups.
PromptInputActionGroupHorizontal group of actions (gap-2).
PromptInputActionSlot for one control; supports asChild and optional built-in tooltip via tooltip (string or object).

Root props: Optional onSubmit(value: string) on PromptInput for Enter-to-submit from PromptInputTextarea (Shift+Enter adds newline), plus normal DOM attributes / className.

Props & hooks: Every part accepts className. PromptInputTextarea accepts standard textarea props. PromptInputAction supports asChild and tooltip?: string | { content?: string; side?: "top" | "right" | "bottom" | "left"; shortcut?: string } (no tooltip renders when content is missing). There is no context hook.

Usage notes: Put attach/tools in the first PromptInputActionGroup and send in the second so justify-between separates them. Prefer PromptInputAction asChild around Button rather than using PromptInputAction as the clickable element itself.

Example:

<PromptInput>
  <PromptInputTextarea placeholder="Ask anything..." />
  <PromptInputActions>
    <PromptInputActionGroup>
      <PromptInputAction asChild>
        <Button variant="ghost" size="icon" className="rounded-full">
          <Paperclip />
        </Button>
      </PromptInputAction>
    </PromptInputActionGroup>
    <PromptInputActionGroup>
      <PromptInputAction asChild>
        <Button size="icon" className="rounded-full">
          <ArrowUp />
        </Button>
      </PromptInputAction>
    </PromptInputActionGroup>
  </PromptInputActions>
</PromptInput>
Model Selector

Dropdown for picking a model (or similar value), built on Radix DropdownMenu with optional items metadata for labels, descriptions, and icons.

Import:

import {
  ModelSelector,
  ModelSelectorPortal,
  ModelSelectorTrigger,
  ModelSelectorContent,
  ModelSelectorGroup,
  ModelSelectorLabel,
  ModelSelectorItemTitle,
  ModelSelectorItemDescription,
  ModelSelectorItemIcon,
  ModelSelectorItemIndicator,
  ModelSelectorItem,
  ModelSelectorCheckboxItem,
  ModelSelectorRadioGroup,
  ModelSelectorRadioItem,
  ModelSelectorSeparator,
  ModelSelectorSub,
  ModelSelectorSubTrigger,
  ModelSelectorSubContent,
} from "@/components/nexus-ui/model-selector";

Parts:

PartPurpose
ModelSelectorRoot provider + dropdown root; drives selection context (see Root props).
ModelSelectorPortalOptional portal wrapper for menu content (use when portaling content).
ModelSelectorTriggerOpens the menu; shows the selected label when items is set.
ModelSelectorContentMenu surface / panel.
ModelSelectorGroupGroups rows; often paired with ModelSelectorLabel.
ModelSelectorLabelSection heading inside a group.
ModelSelectorItemGeneric menu row.
ModelSelectorItemTitlePrimary text line for an item.
ModelSelectorItemDescriptionSecondary text line for an item.
ModelSelectorItemIconLeading icon slot for an item.
ModelSelectorItemIndicatorSelection indicator (e.g. check).
ModelSelectorCheckboxItemCheckbox-style menu item.
ModelSelectorRadioGroupSingle-select group; pass Radix value / onValueChange.
ModelSelectorRadioItemRadio row; supports title, description, icon, indicator.
ModelSelectorSeparatorVisual divider between sections.
ModelSelectorSubNested submenu container.
ModelSelectorSubTriggerRow that expands a submenu.
ModelSelectorSubContentPopover body for nested items.

Root props: On ModelSelector, value and onValueChange are required. Optional items: { value, title, description?, icon? }[] to populate trigger copy and internal metadata. Other Radix menu-root props apply, but value / onValueChange on the root are reserved for this API.

Props & hooks: ModelSelectorTrigger adds variant: filled | outline | ghost (default filled). Selection state lives in your React state (value / onValueChange); there is no extra hook beyond Radix behavior.

Usage notes: Prefer ModelSelectorRadioGroup + ModelSelectorRadioItem for exclusive choices; use ModelSelectorCheckboxItem when multiple toggles are needed. Nest advanced trees with ModelSelectorSub / SubTrigger / SubContent. Long menus benefit from ModelSelectorGroup, ModelSelectorLabel, and ModelSelectorSeparator.

Example:

// models: { value: string; title: string; description?: string; icon?: ComponentType<{ className?: string }> }[]
const [model, setModel] = React.useState(models[0].value);

<ModelSelector value={model} onValueChange={setModel} items={models}>
  <ModelSelectorTrigger />
  <ModelSelectorContent align="start">
    <ModelSelectorGroup>
      <ModelSelectorLabel>Select model</ModelSelectorLabel>
      <ModelSelectorRadioGroup value={model} onValueChange={setModel}>
        {models.map((m) => (
          <ModelSelectorRadioItem
            key={m.value}
            value={m.value}
            icon={m.icon}
            title={m.title}
            description={m.description}
          />
        ))}
      </ModelSelectorRadioGroup>
    </ModelSelectorGroup>
  </ModelSelectorContent>
</ModelSelector>
Suggestions

Chips for quick prompts, with an optional animated panel for grouped suggestions.

Import:

import Suggestions, {
  SuggestionList,
  Suggestion,
  SuggestionPanel,
  SuggestionPanelHeader,
  SuggestionPanelTitle,
  SuggestionPanelClose,
  SuggestionPanelContent,
} from "@/components/nexus-ui/suggestions";

Parts:

PartPurpose
SuggestionsRoot; optional onSelect(value: string) for chip clicks (via context).
SuggestionListFlex list of chips; orientation "horizontal" | "vertical".
SuggestionChip Button; variant filled | outline | ghost; optional value, highlight.
SuggestionPanelAnimated shell around grouped suggestions.
SuggestionPanelHeaderTop bar inside the panel.
SuggestionPanelTitleTitle row in the header.
SuggestionPanelCloseClose control; supports asChild.
SuggestionPanelContentBody region; supports asChild.

Root props: Suggestions — optional onSelect(value: string). SuggestionPanel — optional open (default true), onOpenChange, onClose (runs after the exit animation).

Props & hooks: No selector hook; wire behavior through onSelect and Suggestion’s Button props. Suggestion omits outer variant in favor of the Nexus variant union above.

Usage notes: Combine multiple SuggestionList blocks or place lists inside SuggestionPanel for progressive disclosure. highlight dims matching substrings inside chip labels. Use SuggestionPanelClose in the header for dismiss affordances.

Example:

// Often paired with prompt state: onSelect((q) => setInput(q))
<Suggestions onSelect={(q) => setInput(q)}>
  <SuggestionList>
    <Suggestion value="Summarize this thread">Summarize this thread</Suggestion>
    <Suggestion value="Draft a reply">Draft a reply</Suggestion>
  </SuggestionList>
</Suggestions>
Attachments

Controlled file previews for a prompt or message row: hidden file input, optional window-level drag/drop, and per-item remove/progress UI.

Import:

import Attachments, {
  AttachmentTrigger,
  AttachmentList,
  Attachment,
  AttachmentPreview,
  AttachmentRemove,
  AttachmentInfo,
  AttachmentProperty,
  AttachmentProgress,
  AttachmentsDropOverlay,
  useAttachments,
  toAttachmentMeta,
  filesFromDataTransfer,
} from "@/components/nexus-ui/attachments";

Parts:

PartPurpose
AttachmentsRoot provider: hidden <input type="file">, validation, blob URL lifecycle, optional window drop listeners.
AttachmentTriggerOpens the picker (pair with useAttachments().inputId / labels when building custom triggers).
AttachmentListHorizontal scroller / flex row hosting each Attachment.
AttachmentOne tile; pass attachment: AttachmentMeta; variant compact | inline | detailed | pasted.
AttachmentPreviewThumbnail or type icon region (context: current attachment).
AttachmentInfoText column wrapper for AttachmentProperty rows.
AttachmentPropertyRenders name, size, or file kind via as="name" | "size" | "kind".
AttachmentRemoveRemoves the current item (calls Attachment’s onRemove when provided).
AttachmentProgressThin progress bar; value 0–100.
AttachmentsDropOverlayDropshadow overlay during drag; variant fullscreen | contained.

Root props: On Attachments, required attachments and onAttachmentsChange. Optional: accept, multiple (default true), maxFiles, maxSize (per file, bytes), disabled, onFileInputChange, onFilesRejected, windowDrop.

Props & hooks: useAttachments() — only under <Attachments> — exposes inputRef, inputId, openPicker, appendFiles, isDraggingFile, current attachments, onAttachmentsChange, limits, and disabled. Exported types: AttachmentMeta, AttachmentsRejectedFiles, AppendFilesOptions. Helpers: toAttachmentMeta(file, options?), filesFromDataTransfer for paste/drop.

Usage notes: Call appendFiles from custom drop/paste handlers to reuse the same limits as the native picker. Enable windowDrop plus AttachmentsDropOverlay for app-wide file drag hints. Object URLs are revoked when items disappear from the controlled attachments array.

Example:

const [items, setItems] = React.useState<AttachmentMeta[]>([]);

<Attachments attachments={items} onAttachmentsChange={setItems} accept="image/*" maxFiles={5}>
  <AttachmentTrigger />
  <AttachmentList>
    {items.map((meta, index) => (
      <Attachment
        key={meta.url ?? `${meta.name}-${index}`}
        attachment={meta}
        variant="compact"
        onRemove={() => setItems(items.filter((_, i) => i !== index))}
      />
    ))}
  </AttachmentList>
</Attachments>
Message

User vs assistant rows with optional avatar, markdown body (via Streamdown), and trailing actions.

Import:

import {
  Message,
  MessageStack,
  MessageContent,
  MessageMarkdown,
  MessageActions,
  MessageActionGroup,
  MessageAction,
  MessageAvatar,
} from "@/components/nexus-ui/message";

Parts:

PartPurpose
MessageRoot; required from: "user" | "assistant" (layout + role="article").
MessageStackVertical stack for multiple blocks inside one message.
MessageContentBubble / content wrapper (user vs assistant styling).
MessageMarkdownRenders markdown with Streamdown plugins (code, math, mermaid, CJK).
MessageActionsAction row container.
MessageActionGroupGroups action buttons.
MessageActionSingle action; supports asChild and optional built-in tooltip via tooltip (string or object).
MessageAvatarAvatar column; src, optional fallback, alt, size, delayMs.

Root props: On Message, required from: "user" | "assistant". Standard HTMLAttributes<HTMLDivElement> apply to the article wrapper. Default aria-label is "User message" or "Assistant message" unless you pass aria-label or aria-labelledby.

Props & hooks: No exported context hook. Descendants (MessageStack, MessageContent, MessageActions, etc.) read from from internal context (fallback assistant if used outside Message). MessageMarkdown accepts Streamdown props (ComponentProps<typeof Streamdown>): e.g. markdown via children, optional components merged over built-in code / table / inlineCode defaults, plus className. MessageAction supports asChild (Radix Slot) and tooltip?: string | { content?: string; side?: "top" | "right" | "bottom" | "left"; shortcut?: string } (no tooltip renders when content is missing). MessageAvatar: required src; optional alt, fallback, delayMs, size, className.

Usage notes: Registry install adds codeblock.tsx next to message.tsx. Merge Streamdown-related CSS from the registry item when installing manually. Prefer MessageAction asChild around Button for actions.

Example:

<Message from="assistant">
  <MessageAvatar src="https://github.com/shadcn.png" fallback="AI" />
  <MessageStack>
    <MessageContent>
      <MessageMarkdown>Here is **markdown** and `inline code`.</MessageMarkdown>
    </MessageContent>
    <MessageActions>
      <MessageActionGroup>
        <MessageAction asChild>
          <Button type="button" variant="ghost" size="sm">
            Copy
          </Button>
        </MessageAction>
      </MessageActionGroup>
    </MessageActions>
  </MessageStack>
</Message>

<Message from="user">
  <MessageContent>
    <MessageMarkdown>Short user reply</MessageMarkdown>
  </MessageContent>
</Message>
Thread

Wraps use-stick-to-bottom for a scrollable message list that stays pinned to new content, with a floating control when the user scrolls up.

Import:

import {
  Thread,
  ThreadContent,
  ThreadScrollToBottom,
} from "@/components/nexus-ui/thread";

Parts:

PartPurpose
ThreadRoot; forwards StickToBottom props (resize, initial, spring options, etc.).
ThreadContentScrollable column for messages (flex flex-col gap-*).
ThreadScrollToBottomButton (or asChild) shown when not at bottom; calls scrollToBottom.

Root props: Thread is StickToBottom with defaults resize="smooth" and initial="smooth". Optional StickToBottomOptions: resize, initial, mass, damping, stiffness, targetScrollTop. Also instance (pre-created stick-to-bottom instance), contextRef, and normal HTMLAttributes on the outer div.

Props & hooks: Inside Thread, use useStickToBottomContext() from use-stick-to-bottom for isAtBottom, scrollToBottom, state, etc. ThreadScrollToBottom uses that context internally and returns null when already at the bottom. It accepts asChild plus standard button HTML attributes (when not asChild, renders a styled <button>).

Usage notes: Place ThreadContent and ThreadScrollToBottom as siblings inside Thread. Give Thread a bounded height (e.g. h-full / min-h-*) so the scroll region is defined.

Example:

<Thread className="min-h-[480px]">
  <ThreadContent>
    {messages.map((m) => (
      <Message key={m.id} from={m.role === "user" ? "user" : "assistant"}>
        <MessageContent>
          <MessageMarkdown>{m.text}</MessageMarkdown>
        </MessageContent>
      </Message>
    ))}
  </ThreadContent>
  <ThreadScrollToBottom />
</Thread>
Citation

Composable inline citation chip(s) with Radix HoverCard preview. Multiple URLs use a carousel inside the panel (CitationCarousel*).

Import:

import {
  Citation,
  CitationTrigger,
  CitationContent,
  CitationSource,
  CitationSourcesBadge,
  CitationItem,
  CitationFavicon,
  CitationSiteName,
  CitationCarousel,
  CitationCarouselContent,
  CitationCarouselHeader,
  CitationCarouselItem,
  CitationCarouselPrev,
  CitationCarouselNext,
  CitationCarouselPagination,
  CitationCarouselIndex,
} from "@/components/nexus-ui/citation";

Helpers: resolveCitationSources, resolveCitationSource, parseCitationUrl, rootDomainSiteName, types CitationSourceInput, ResolvedCitation.

Parts:

PartPurpose
CitationRoot HoverCard; owns resolved sources + carousel index state.
CitationTriggerChip trigger; single-source renders as link, multi-source as span + “+N”.
CitationContentHover panel surface (default align="center", sideOffset={4}).
CitationCarouselShadcn Carousel; wires setApi into citation context.
CitationCarouselContent / ItemSlides; each CitationCarouselItem requires index.
CitationCarouselPrev / Next / Index / Pagination / HeaderNavigation chrome.
CitationItemDefault preview body (title, description, CitationSource) or custom children; anchor to href.
CitationSourceFooter row: favicon + site name.
CitationFavicon / CitationSiteNameResolve from active carousel item or CitationItem scope.
CitationSourcesBadgeStacked favicons + “N sources” (or custom label); for toolbars, not the hover chip.

Root props: On Citation, required citations: CitationSourceInput[] (url, optional title, description). All other props omit children from HoverCard and pass through; optional children compose trigger + content. Renders null if citations is empty. openDelay / closeDelay are set to 50 on the root card.

Props & hooks: No exported hooks; CitationItem, CitationFavicon, etc. require an ancestor Citation and (for items inside the carousel) a matching CitationCarouselItem with index. CitationTrigger: optional label, showFavicon (default true), showSiteName (default true). CitationCarouselItem: required index: number. CitationItem: optional showTitle, showDescription, showSource (defaults true); href defaults to resolved URL. CitationSourcesBadge: optional showFavicons, label.

Usage notes: Build citations from your RAG / search pipeline; use resolveCitationSources if you normalize URLs yourself. For two or more URLs, put CitationCarousel + one CitationCarouselItem per index inside CitationContent. CitationSourcesBadge is optional chrome for message headers or actions, still under Citation.

Example (single source):

<Citation
  citations={[
    {
      url: "https://vercel.com/blog",
      title: "Vercel Blog",
      description: "Product and platform updates.",
    },
  ]}
>
  <CitationTrigger />
  <CitationContent>
    <CitationItem />
  </CitationContent>
</Citation>

Example (multiple sources + carousel):

const sources = [
  { url: "https://example.com/a", title: "Source A" },
  { url: "https://example.com/b", title: "Source B" },
];

<Citation citations={sources}>
  <CitationTrigger />
  <CitationContent>
    <CitationCarousel>
      <CitationCarouselHeader>
        <CitationSourcesBadge />
        <CitationCarouselPagination>
          <CitationCarouselPrev />
          <CitationCarouselIndex />
          <CitationCarouselNext />
        </CitationCarouselPagination>
      </CitationCarouselHeader>
      <CitationCarouselContent>
        {sources.map((_, index) => (
          <CitationCarouselItem key={index} index={index}>
            <CitationItem />
          </CitationCarouselItem>
        ))}
      </CitationCarouselContent>
    </CitationCarousel>
  </CitationContent>
</Citation>
Reasoning

Collapsible “thinking” block built on shadcn Collapsible: the trigger label shifts from streaming copy to a duration summary, and the body renders markdown via Streamdown.

Import:

import {
  Reasoning,
  ReasoningTrigger,
  ReasoningContent,
} from "@/components/nexus-ui/reasoning";

Parts:

PartPurpose
ReasoningRoot provider + collapsible root; manages open state vs isStreaming and builds the contextual trigger label (Thinking… → Thought for N seconds).
ReasoningTriggerRow with brain icon, label text (streaming uses tw-shimmer utilities on the span), and chevron that rotates when open.
ReasoningContentExpands markdown in Streamdown with muted styling and a leading border strip. Requires children: string (markdown source).

Root props: On Reasoning, optional isStreaming (default false) drives auto-open while streaming and auto-close plus duration bookkeeping when streaming ends; optional controlled open / defaultOpen / onOpenChange (inherits from collapsible semantics but Reasoning omits Radix open/defaultOpen/onOpenChange from its type in favor of the listed props). Accepts remaining Collapsible-compatible props plus className.

Props & hooks: No exported hook (internal context only). ReasoningTrigger/ReasoningContent must be descendants of Reasoning. Registry install pulls collapsible, streamdown, Hugeicons packages, @import "tw-shimmer", and keyframes for collapsible height animation.

Usage notes: Toggle isStreaming from your chat transport / part stream. Omit ReasoningTrigger children to use the computed label; pass children only when you override the visible label while keeping accessibility in mind.

Example:

<Reasoning isStreaming={status === "streaming"}>
  <ReasoningTrigger />
  <ReasoningContent>{reasoningMarkdownString}</ReasoningContent>
</Reasoning>
Text Shimmer

Single primitive that animates a moving highlight across text (bg-clip-text + injected @keyframes). No extra UI dependencies beyond @/lib/utils.

Import:

import { TextShimmer } from "@/components/nexus-ui/text-shimmer";

Parts: Single component — TextShimmer. Optional polymorphic root via as (default "span").

Root props: duration — active sweep duration in seconds (default 2.5); repeatDelay — pause before the next sweep (default 0); spread — highlight width in percentage points around center, clamped ~5–45 (default 20); angle — degrees added to the gradient axis (default 0); color — override beam stop color; invert — flip default light/dark beam contrast; plus standard HTML attributes/className/style. Children are the label text/node tree.

Props & hooks: No context. Injects a unique @keyframes name per instance via useId.

Usage notes: Prefer on short status strings (“Running tools…”). Combine with muted text-muted-foreground on the wrapper if the edge color should inherit from typography.

Example:

<TextShimmer repeatDelay={0.5}>Running tool calls</TextShimmer>
Image

Composable image tile for multimodal models: accepts src, base64, or uint8Array (+ mediaType), wraps content in a Radix Dialog root for lightbox viewing, and exposes loader + absolute action regions.

Import:

import {
  Image,
  ImagePreview,
  ImageLightbox,
  ImageLightboxOverlay,
  ImageLightboxPreview,
  ImageLightboxClose,
  ImageLoader,
  ImageActions,
  ImageActionGroup,
  ImageAction,
} from "@/components/nexus-ui/image";

Parts:

PartPurpose
ImageRoot div inside Dialog; resolves src / data URL from base64 / object URL from uint8Array; provides context; default child is ImagePreview.
ImagePreviewThumbnail; wraps an <img> in Dialog Trigger when src resolves; shows ImageLoader while empty.
ImageLightboxDialog.Portal wrapper.
ImageLightboxOverlayModal backdrop.
ImageLightboxPreviewDialog.Content with large <img> (object-contain) and screen-reader title.
ImageLightboxCloseVisually hidden close control (sr-only).
ImageLoaderPulse skeleton; reflects error state via context.
ImageActionsAbsolutely positioned action strip; align: inline-start | inline-end | block-start | block-end.
ImageActionGroup / ImageActionGrouped controls; ImageAction supports asChild + Slot and optional built-in tooltip via tooltip (string or object).

Root props: On Image, pass one of src, base64, or uint8Array; optional mediaType, alt; plus Radix dialog root props (open, defaultOpen, onOpenChange, modal) and normal div HTML attributes / className.

Props & hooks: Internal context only (no exported hook). Nested parts must sit under Image.

Usage notes: Omit children on Image to get the default preview. Compose ImageLightbox* as siblings of the root card (typical pattern from docs) so clicking the preview opens the modal. Use ImageActions + ImageAction asChild around Button for download or share; add tooltip to ImageAction for labels, side placement, and optional shortcuts.

Example (minimal):

<Image base64={modelImageBase64} alt="Generated scene">
  <ImagePreview />
  <ImageLightbox>
    <ImageLightboxOverlay />
    <ImageLightboxPreview />
    <ImageLightboxClose />
  </ImageLightbox>
</Image>
Feedback Bar

Compact bar for “Was this helpful?”-style flows on a message or thread: compose a prompt (label) on the left, actions (e.g. thumbs) on the right, and an optional close column. FeedbackBarAction can wrap real buttons via asChild and show a tooltip with optional keyboard shortcut chips (shadcn Tooltip + Kbd).

Import:

import {
  FeedbackBar,
  FeedbackBarContent,
  FeedbackBarPrompt,
  FeedbackBarLabel,
  FeedbackBarActions,
  FeedbackBarAction,
  FeedbackBarClose,
} from "@/components/nexus-ui/feedback-bar";

Parts:

PartPurpose
FeedbackBarRoot role="group" strip; default h-12, full width (max ~28rem), bordered rounded-[12px] surface.
FeedbackBarContentjustify-between flex row filling the bar.
FeedbackBarPromptLeft cluster (default pl-4) for copy + leading content.
FeedbackBarLabelPrimary prompt text (font-[350]).
FeedbackBarActionsRight cluster for control groups (gap-1.5).
FeedbackBarActionSingle control shell; asChild + Slot; optional tooltip: string or { content?, side?, shortcut? }.
FeedbackBarCloseNarrow right column with leading border; uses FeedbackBarAction + asChild around a host div; pass close Button/IconButton as children.

Root props: Plain HTMLAttributes on each part; slot parts accept className. FeedbackBarAction / FeedbackBarClose: see tooltips above.

Props & hooks: No context. Tooltips render a local TooltipProvider per action that has tooltip.content.

Usage notes: Put icon buttons inside FeedbackBarAction asChild for hit targets. Prefer shortcut in tooltip when mirroring real keyboard affordances.

Example:

<FeedbackBar>
  <FeedbackBarContent>
    <FeedbackBarPrompt>
      <FeedbackBarLabel>Was this response helpful?</FeedbackBarLabel>
    </FeedbackBarPrompt>
    <FeedbackBarActions>
      <FeedbackBarAction asChild tooltip={{ content: "Helpful", shortcut: "⌘Y" }}>
        <button type="button" aria-label="Helpful">…</button>
      </FeedbackBarAction>
    </FeedbackBarActions>
    <FeedbackBarClose>
      <button type="button" aria-label="Dismiss">…</button>
    </FeedbackBarClose>
  </FeedbackBarContent>
</FeedbackBar>
Toaster

Headless toast notifications powered by Sonner, with variant-aware styling and custom action/cancel controls. Uses next-themes for light/dark/system. Export Toaster (mount once in the root layout) and a toast object with ergonomic helpers plus toast.dismiss.

Import:

import { Toaster, toast } from "@/components/nexus-ui/toaster";

Parts / API:

ExportPurpose
ToasterRenders <Sonner /> with toastOptions={{ unstyled: true }} and theme from useTheme(); spread extra ToasterProps.
toast.customBuild fully custom payloads (title, optional description, variant, icon, action, cancel, Sonner timing/options).
toast.default / success / info / warning / error / loadingShorthand (title, options?) delegating to toast.custom.
toast.dismissForward to Sonner dismiss API.

Root props: On Toaster, standard Sonner props (position, duration, expand, etc.) plus className merge behavior from Sonner.

Props & hooks: Requires ThemeProvider from next-themes above the layout that renders Toaster so useTheme() resolves. Toast bodies use shadcn Button for action/cancel/close.

Usage notes: Place <Toaster /> beside app chrome (e.g. layout.tsx). Prefer toast.success("Copied") for quick feedback; use toast.custom when you need cancel buttons or custom icons (icon: null to suppress defaults).

Example:

// layout.tsx
<Toaster position="bottom-right" />

// anywhere client-side
toast.success("Saved");
toast.error("Something went wrong", { description: "Try again.", duration: 5000 });
Chain of Thought

Structured timeline for tool / reasoning steps: a root collapsible tracks all child step statuses, optional shimmer on active labels (via tw-shimmer), and useOnChange drives auto-open/close when steps transition.

Import:

import {
  ChainOfThought,
  ChainOfThoughtTrigger,
  ChainOfThoughtContent,
  ChainOfThoughtStep,
  ChainOfThoughtStepTitle,
  ChainOfThoughtStepContent,
  ChainOfThoughtComplete,
  type ChainOfThoughtStepStatus,
} from "@/components/nexus-ui/chain-of-thought";

Parts:

PartPurpose
ChainOfThoughtRoot Collapsible + context registry for steps; props autoCloseOnAllComplete (default true) closes when every step is completed.
ChainOfThoughtTriggerHeader row with optional icon/label and chevron; label shimmers while work is in flight.
ChainOfThoughtContentBody container for ChainOfThoughtStep list.
ChainOfThoughtStepPer-step Collapsible; status drives registration + shimmer; hasContent + autoCloseOnComplete auto-manage expansion.
ChainOfThoughtStepTitleRow with icon + label; becomes CollapsibleTrigger when expandable content exists (or force collapsible).
ChainOfThoughtStepContentExpandable output region for a step.
ChainOfThoughtCompleteOptional footer row (“All steps complete”) after steps.

Root props: See source types: controlled open / defaultOpen / onOpenChange on root and steps; ChainOfThoughtStepStatus is "pending" | "active" | "completed" | "error".

Props & hooks: Internal contexts only. Install includes lib/use-on-change.ts when pulled from the registry.

Usage notes: Give each step stable visual content for hasContent when you want expand/collapse. Pair with streaming/tool events by updating status on each step.

Example (shape):

<ChainOfThought>
  <ChainOfThoughtTrigger>Chain of thought</ChainOfThoughtTrigger>
  <ChainOfThoughtContent>
    <ChainOfThoughtStep status="completed">
      <ChainOfThoughtStepTitle icon={…} label="Search the web" />
    </ChainOfThoughtStep>
    <ChainOfThoughtStep status="active" hasContent>
      <ChainOfThoughtStepTitle icon={…} label="Read results" />
      <ChainOfThoughtStepContent>…</ChainOfThoughtStepContent>
    </ChainOfThoughtStep>
  </ChainOfThoughtContent>
</ChainOfThought>
Tool

State-aware collapsible UI for a single tool invocation: status-driven header (icon + badge), JSON input, and optional output via Shiki codeblocks. Maps cleanly to Vercel AI SDK tool part states.

Import:

import {
  Tool,
  ToolTrigger,
  ToolContent,
  ToolInput,
  ToolOutput,
  type ToolStatus,
} from "@/components/nexus-ui/tool";

Parts:

PartPurpose
ToolRoot Collapsible; required status (pending | ready | running | completed | error) sets CSS vars + context for badge/icon styling.
ToolTriggerHeader row; required name (tool label) plus status badge and chevron.
ToolContentBody for ToolInput / ToolOutput sections.
ToolInputRenders formatted JSON (or string) Input block.
ToolOutputRenders Output when status is in showWhen (default ["completed"]); on error shows errorText plus optional payload codeblock.

Root props: Tool forwards Collapsible props; ToolOutput accepts showWhen?: ToolStatus[] and errorText?: string.

Props & hooks: Internal context only. Registry install adds codeblock-new.tsx and lib/shiki/highlighter.ts alongside tool.tsx.

Usage notes: Set status from your tool lifecycle (e.g. map AI SDK tool-invocation states). Use ToolOutput with showWhen={["completed", "error"]} when you need output on failure.

Example:

<Tool status="running">
  <ToolTrigger name="search_web" />
  <ToolContent>
    <ToolInput payload={{ query: "nexus ui" }} />
    <ToolOutput payload={result} showWhen={["completed", "error"]} errorText="Search failed" />
  </ToolContent>
</Tool>
Questions

Card UI for follow-up clarification in chat: pass items metadata, compose Question slides with QuestionOption / QuestionOther, navigate with QuestionsCarousel*, and collect answers via onSubmit.

Import:

import {
  Questions,
  Question,
  QuestionOptions,
  QuestionOption,
  QuestionOther,
  QuestionsTitle,
  QuestionsHeader,
  QuestionsFooter,
  QuestionsSkip,
  QuestionsSubmit,
  QuestionsDismiss,
  QuestionsCarousel,
  QuestionsCarouselContent,
  QuestionsCarouselItem,
  QuestionsCarouselPagination,
  QuestionsCarouselPrev,
  QuestionsCarouselNext,
  QuestionsCarouselIndex,
  type QuestionsSubmission,
  type QuestionInput,
} from "@/components/nexus-ui/questions";

Parts:

PartPurpose
QuestionsRoot Card + provider; required items: QuestionInput[]; optional autoAdvance (default true), onSubmit, onSkip, onDismiss.
QuestionsHeader / QuestionsTitle / QuestionsDismissTop chrome and close control.
QuestionsCarousel*Shadcn Carousel wrapper for multi-question flows (Prev / Next / Index / Pagination).
QuestionOne slide; registers id, type (single | multiple), prompt, required.
QuestionOptionsLayout wrapper for option rows.
QuestionOptionSelectable row (radio or checkbox styling from question type).
QuestionOtherFree-text “Other” option using QUESTION_OTHER_VALUE.
QuestionsFooter / QuestionsSkip / QuestionsSubmitFooter actions; submit disabled until required questions answered.

Root props: Questions — items, autoAdvance, onSubmit(submission: QuestionsSubmission), onSkip(questionId), onDismiss(). Skipped optional questions submit as [No Preference].

Props & hooks: Internal context only. Constants: QUESTION_OTHER_VALUE, QUESTION_NO_PREFERENCE_VALUE, QUESTION_NO_PREFERENCE_LABEL.

Usage notes: Single choice auto-advances when autoAdvance is true. Hide or disable Submit until the last carousel slide in multi-question flows.

Example:

<Questions
  items={[
    {
      id: "scope",
      type: "single",
      prompt: "What should we focus on?",
      required: true,
      options: [
        { value: "perf", label: "Performance" },
        { value: "ux", label: "UX" },
      ],
    },
  ]}
  onSubmit={(submission) => console.log(submission)}
>
  <QuestionsHeader>
    <QuestionsTitle>Quick questions</QuestionsTitle>
    <QuestionsDismiss />
  </QuestionsHeader>
  <QuestionsCarousel>
    <QuestionsCarouselContent>
      {items.map((item) => (
        <QuestionsCarouselItem key={item.id}>
          <Question {...item}>
            <QuestionOptions>
              {item.options.map((option) => (
                <QuestionOption key={option.value} value={option.value}>
                  {option.label}
                </QuestionOption>
              ))}
              <QuestionOther />
            </QuestionOptions>
          </Question>
        </QuestionsCarouselItem>
      ))}
    </QuestionsCarouselContent>
  </QuestionsCarousel>
  <QuestionsFooter>
    <QuestionsSkip />
    <QuestionsSubmit />
  </QuestionsFooter>
</Questions>

AI SDK integration

Use the Vercel AI SDK from @ai-sdk/react. The chat hook uses a transport (for example DefaultChatTransport pointing at your API route). Wire the textarea with local state (or your form library) and call sendMessage on submit.

import * as React from "react";
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import {
  PromptInput,
  PromptInputTextarea,
  PromptInputActions,
  PromptInputActionGroup,
  PromptInputAction,
} from "@/components/nexus-ui/prompt-input";
import { Button } from "@/components/ui/button";
import { ArrowUp } from "lucide-react";

function ChatInput() {
  const [input, setInput] = React.useState("");
  const { sendMessage, status } = useChat({
    transport: new DefaultChatTransport({ api: "/api/chat" }),
  });

  const busy = status === "streaming" || status === "submitted";

  async function onSend() {
    const text = input.trim();
    if (!text || busy) return;
    await sendMessage({ text });
    setInput("");
  }

  return (
    <PromptInput>
      <PromptInputTextarea
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === "Enter" && !e.shiftKey) {
            e.preventDefault();
            void onSend();
          }
        }}
        disabled={busy}
      />
      <PromptInputActions>
        <PromptInputActionGroup>
          <PromptInputAction asChild>
            <Button
              type="button"
              size="icon"
              className="rounded-full"
              disabled={!input.trim() || busy}
              onClick={() => void onSend()}
            >
              <ArrowUp />
            </Button>
          </PromptInputAction>
        </PromptInputActionGroup>
      </PromptInputActions>
    </PromptInput>
  );
}

Attachments can be passed in the same turn via sendMessage({ text, files }) when using file inputs compatible with the SDK (see current AI SDK docs for FileList / file parts).

Composition Principles

  1. Compose, don't configure. Prefer small primitives over monolithic config objects.
  2. asChild for prompt actions. Wrap Button in PromptInputAction asChild (or SuggestionPanelClose asChild where applicable).
  3. Group prompt actions. Left groups (attach, tools) vs right (send) using PromptInputActionGroup inside PromptInputActions.
  4. Own the source. Components are copied into the project; customize them directly.

Styling

  • Tailwind CSS v4 utility classes throughout.
  • Override via className on any part.
  • Dark mode: dark: variants align with typical shadcn-style tokens.

Project Structure

components/
├── nexus-ui/
│   ├── prompt-input.tsx
│   ├── model-selector.tsx
│   ├── suggestions.tsx
│   ├── attachments.tsx
│   ├── message.tsx
│   ├── codeblock.tsx
│   ├── thread.tsx
│   ├── citation.tsx
│   ├── reasoning.tsx
│   ├── text-shimmer.tsx
│   ├── image.tsx
│   ├── feedback-bar.tsx
│   ├── toaster.tsx
│   ├── chain-of-thought.tsx
│   ├── codeblock-new.tsx
│   ├── tool.tsx
│   └── questions.tsx
├── ui/
│   ├── button.tsx
│   ├── textarea.tsx
│   ├── scroll-area.tsx
│   └── ...
└── ...

Dependencies

Registry items pull these in as needed (shadcn CLI installs registry dependencies):

  • Prompt Input: @radix-ui/react-slot, shadcn textarea, scroll-area, tooltip, kbd, @/lib/utils
  • Suggestions: @radix-ui/react-presence, @radix-ui/react-slot, class-variance-authority, shadcn button
  • Model Selector: radix-ui (DropdownMenu), class-variance-authority, @hugeicons/react, @hugeicons/core-free-icons
  • Attachments: @radix-ui/react-slot, class-variance-authority, @hugeicons/react, @hugeicons/core-free-icons
  • Message: streamdown + @streamdown/* plugins, radix-ui, @radix-ui/react-slot, @hugeicons/react, @hugeicons/core-free-icons, shadcn button, avatar, tooltip, kbd
  • Thread: @radix-ui/react-slot, use-stick-to-bottom, @hugeicons/react, @hugeicons/core-free-icons
  • Citation: radix-ui, @hugeicons/react, @hugeicons/core-free-icons, shadcn carousel, hover-card
  • Reasoning: streamdown, @hugeicons/react, @hugeicons/core-free-icons, tw-shimmer, shadcn collapsible, registry CSS for shimmer + collapsible height keyframes
  • Text Shimmer: @/lib/utils only (cn)
  • Image: @radix-ui/react-dialog, @radix-ui/react-slot, shadcn tooltip, kbd, @/lib/utils
  • Feedback Bar: @radix-ui/react-slot, shadcn tooltip, kbd, @/lib/utils
  • Toaster: sonner, next-themes, @hugeicons/react, @hugeicons/core-free-icons, shadcn button, @/lib/utils
  • Chain of Thought: @hugeicons/react, @hugeicons/core-free-icons, shadcn collapsible, tw-shimmer (registry CSS), @/lib/use-on-change, @/lib/utils
  • Tool: @hugeicons/react, @hugeicons/core-free-icons, shiki, @react-symbols/icons, shadcn badge, collapsible, registry codeblock-new.tsx, lib/shiki/highlighter.ts, @/lib/utils
  • Questions: @hugeicons/react, @hugeicons/core-free-icons, shadcn carousel, button, card, checkbox, @/lib/utils

Documentation

Comments

Markdown supported