--- name: nexus-ui description: Install and compose Nexus UI components for AI chat UIs — prompt input, model selector, suggestions, attachments, message, thread, citation, reasoning, text shimmer, image, feedback bar, toaster, chain of thought, tool, questions, and AI SDK patterns. Activates for @nexus-ui registry usage or Nexus UI source under components/nexus-ui. user-invocable: false --- # 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](https://ui.shadcn.com/docs/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`: ```json { "registries": { "@nexus-ui": "https://nexus-ui.dev/r/{name}.json" } } ``` ### 2. Add components ```bash 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): ```bash 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 | Component | Registry name | Description | |-----------|---------------|-------------| | Prompt Input | `prompt-input` | Composable chat input with auto-resizing textarea and action slots | | Model Selector | `model-selector` | Dropdown for selecting AI models with radio groups, sub-menus, and custom items | | Suggestions | `suggestions` | Prompt suggestion chips for guiding user input | | Attachments | `attachments` | Composable file attachments for chat inputs and messages with preview, variants, and upload wiring | | Message | `message` | Chat message layout with markdown, optional avatar, actions, and attachments | | Thread | `thread` | Scrollable chat thread with stick-to-bottom scrolling and jump-to-bottom control | | Citation | `citation` | Inline source references with hover preview and multi-source carousel | | Reasoning | `reasoning` | Collapsible model reasoning trace with streaming-aware labels and markdown body | | Text Shimmer | `text-shimmer` | Animated shimmer text for loading, tool runs, and other in-progress UI | | Image | `image` | Image renderer for URLs, base64, and byte payloads with preview, loader, lightbox, and action slots | | Feedback Bar | `feedback-bar` | Feedback prompt bar for per-message or thread ratings with action and close slots | | Toaster | `toaster` | Headless toast notifications powered by Sonner, with variant-aware styling and custom action/cancel controls | | Chain of Thought | `chain-of-thought` | Structured multi-step thought timeline with step status, optional expandable output, and auto-close when steps finish | | Tool | `tool` | State-aware tool call UI with JSON input/output codeblocks (`pending`, `ready`, `running`, `completed`, `error`) | | Questions | `questions` | Follow-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:** ```tsx import { PromptInput, PromptInputTextarea, PromptInputActions, PromptInputActionGroup, PromptInputAction, } from "@/components/nexus-ui/prompt-input"; ``` **Parts:** | Part | Purpose | |------|---------| | `PromptInput` | Root layout: bordered card wrapping textarea and actions. | | `PromptInputTextarea` | Auto-resizing textarea (max height ~160px) inside shadcn `ScrollArea`. | | `PromptInputActions` | Bottom bar; `justify-between` for left vs right action groups. | | `PromptInputActionGroup` | Horizontal group of actions (`gap-2`). | | `PromptInputAction` | Slot 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:** ```tsx ``` ### Model Selector Dropdown for picking a model (or similar value), built on Radix `DropdownMenu` with optional `items` metadata for labels, descriptions, and icons. **Import:** ```tsx 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:** | Part | Purpose | |------|---------| | `ModelSelector` | Root provider + dropdown root; drives selection context (see **Root props**). | | `ModelSelectorPortal` | Optional portal wrapper for menu content (use when portaling content). | | `ModelSelectorTrigger` | Opens the menu; shows the selected label when `items` is set. | | `ModelSelectorContent` | Menu surface / panel. | | `ModelSelectorGroup` | Groups rows; often paired with `ModelSelectorLabel`. | | `ModelSelectorLabel` | Section heading inside a group. | | `ModelSelectorItem` | Generic menu row. | | `ModelSelectorItemTitle` | Primary text line for an item. | | `ModelSelectorItemDescription` | Secondary text line for an item. | | `ModelSelectorItemIcon` | Leading icon slot for an item. | | `ModelSelectorItemIndicator` | Selection indicator (e.g. check). | | `ModelSelectorCheckboxItem` | Checkbox-style menu item. | | `ModelSelectorRadioGroup` | Single-select group; pass Radix `value` / `onValueChange`. | | `ModelSelectorRadioItem` | Radio row; supports `title`, `description`, `icon`, `indicator`. | | `ModelSelectorSeparator` | Visual divider between sections. | | `ModelSelectorSub` | Nested submenu container. | | `ModelSelectorSubTrigger` | Row that expands a submenu. | | `ModelSelectorSubContent` | Popover 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:** ```tsx // models: { value: string; title: string; description?: string; icon?: ComponentType<{ className?: string }> }[] const [model, setModel] = React.useState(models[0].value); Select model {models.map((m) => ( ))} ``` ### Suggestions Chips for quick prompts, with an optional animated panel for grouped suggestions. **Import:** ```tsx import Suggestions, { SuggestionList, Suggestion, SuggestionPanel, SuggestionPanelHeader, SuggestionPanelTitle, SuggestionPanelClose, SuggestionPanelContent, } from "@/components/nexus-ui/suggestions"; ``` **Parts:** | Part | Purpose | |------|---------| | `Suggestions` | Root; optional `onSelect(value: string)` for chip clicks (via context). | | `SuggestionList` | Flex list of chips; `orientation` `"horizontal"` \| `"vertical"`. | | `Suggestion` | Chip `Button`; `variant` `filled` \| `outline` \| `ghost`; optional `value`, `highlight`. | | `SuggestionPanel` | Animated shell around grouped suggestions. | | `SuggestionPanelHeader` | Top bar inside the panel. | | `SuggestionPanelTitle` | Title row in the header. | | `SuggestionPanelClose` | Close control; supports `asChild`. | | `SuggestionPanelContent` | Body 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:** ```tsx // Often paired with prompt state: onSelect((q) => setInput(q)) setInput(q)}> Summarize this thread Draft a reply ``` ### 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:** ```tsx import Attachments, { AttachmentTrigger, AttachmentList, Attachment, AttachmentPreview, AttachmentRemove, AttachmentInfo, AttachmentProperty, AttachmentProgress, AttachmentsDropOverlay, useAttachments, toAttachmentMeta, filesFromDataTransfer, } from "@/components/nexus-ui/attachments"; ``` **Parts:** | Part | Purpose | |------|---------| | `Attachments` | Root provider: hidden ``, validation, blob URL lifecycle, optional `window` drop listeners. | | `AttachmentTrigger` | Opens the picker (pair with `useAttachments().inputId` / labels when building custom triggers). | | `AttachmentList` | Horizontal scroller / flex row hosting each `Attachment`. | | `Attachment` | One tile; pass `attachment: AttachmentMeta`; `variant` `compact` \| `inline` \| `detailed` \| `pasted`. | | `AttachmentPreview` | Thumbnail or type icon region (context: current attachment). | | `AttachmentInfo` | Text column wrapper for `AttachmentProperty` rows. | | `AttachmentProperty` | Renders `name`, `size`, or file `kind` via `as="name" \| "size" \| "kind"`. | | `AttachmentRemove` | Removes the current item (calls `Attachment`’s `onRemove` when provided). | | `AttachmentProgress` | Thin progress bar; `value` 0–100. | | `AttachmentsDropOverlay` | Dropshadow 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 `` — 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:** ```tsx const [items, setItems] = React.useState([]); {items.map((meta, index) => ( setItems(items.filter((_, i) => i !== index))} /> ))} ``` ### Message User vs assistant rows with optional avatar, markdown body (via [Streamdown](https://github.com/vercel/streamdown)), and trailing actions. **Import:** ```tsx import { Message, MessageStack, MessageContent, MessageMarkdown, MessageActions, MessageActionGroup, MessageAction, MessageAvatar, } from "@/components/nexus-ui/message"; ``` **Parts:** | Part | Purpose | |------|---------| | `Message` | Root; required `from`: `"user"` \| `"assistant"` (layout + `role="article"`). | | `MessageStack` | Vertical stack for multiple blocks inside one message. | | `MessageContent` | Bubble / content wrapper (user vs assistant styling). | | `MessageMarkdown` | Renders markdown with Streamdown plugins (code, math, mermaid, CJK). | | `MessageActions` | Action row container. | | `MessageActionGroup` | Groups action buttons. | | `MessageAction` | Single action; supports `asChild` and optional built-in tooltip via `tooltip` (string or object). | | `MessageAvatar` | Avatar column; `src`, optional `fallback`, `alt`, `size`, `delayMs`. | **Root props:** On `Message`, required `from`: `"user"` \| `"assistant"`. Standard `HTMLAttributes` 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](https://github.com/vercel/streamdown) props (`ComponentProps`): 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:** ```tsx Here is **markdown** and `inline code`. Short user reply ``` ### Thread Wraps [`use-stick-to-bottom`](https://github.com/stackblitz/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:** ```tsx import { Thread, ThreadContent, ThreadScrollToBottom, } from "@/components/nexus-ui/thread"; ``` **Parts:** | Part | Purpose | |------|---------| | `Thread` | Root; forwards `StickToBottom` props (`resize`, `initial`, spring options, etc.). | | `ThreadContent` | Scrollable column for messages (`flex flex-col gap-*`). | | `ThreadScrollToBottom` | Button (or `asChild`) shown when not at bottom; calls `scrollToBottom`. | **Root props:** `Thread` is `StickToBottom` with defaults `resize="smooth"` and `initial="smooth"`. Optional [`StickToBottomOptions`](https://github.com/stackblitz/use-stick-to-bottom): `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 ` ``` ### 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:** ```tsx import { Toaster, toast } from "@/components/nexus-ui/toaster"; ``` **Parts / API:** | Export | Purpose | |--------|---------| | `Toaster` | Renders `` with `toastOptions={{ unstyled: true }}` and theme from `useTheme()`; spread extra `ToasterProps`. | | `toast.custom` | Build fully custom payloads (`title`, optional `description`, `variant`, `icon`, `action`, `cancel`, Sonner timing/options). | | `toast.default` / `success` / `info` / `warning` / `error` / `loading` | Shorthand `(title, options?)` delegating to `toast.custom`. | | `toast.dismiss` | Forward 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 `` 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:** ```tsx // layout.tsx // 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:** ```tsx import { ChainOfThought, ChainOfThoughtTrigger, ChainOfThoughtContent, ChainOfThoughtStep, ChainOfThoughtStepTitle, ChainOfThoughtStepContent, ChainOfThoughtComplete, type ChainOfThoughtStepStatus, } from "@/components/nexus-ui/chain-of-thought"; ``` **Parts:** | Part | Purpose | |------|---------| | `ChainOfThought` | Root **Collapsible** + context registry for steps; props `autoCloseOnAllComplete` (default `true`) closes when every step is `completed`. | | `ChainOfThoughtTrigger` | Header row with optional icon/label and chevron; label shimmers while work is in flight. | | `ChainOfThoughtContent` | Body container for **`ChainOfThoughtStep`** list. | | `ChainOfThoughtStep` | Per-step **Collapsible**; `status` drives registration + shimmer; `hasContent` + `autoCloseOnComplete` auto-manage expansion. | | `ChainOfThoughtStepTitle` | Row with icon + label; becomes **CollapsibleTrigger** when expandable content exists (or force `collapsible`). | | `ChainOfThoughtStepContent` | Expandable output region for a step. | | `ChainOfThoughtComplete` | Optional 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):** ```tsx Chain of thought … ``` ### 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:** ```tsx import { Tool, ToolTrigger, ToolContent, ToolInput, ToolOutput, type ToolStatus, } from "@/components/nexus-ui/tool"; ``` **Parts:** | Part | Purpose | |------|---------| | `Tool` | Root **Collapsible**; required `status` (`pending` \| `ready` \| `running` \| `completed` \| `error`) sets CSS vars + context for badge/icon styling. | | `ToolTrigger` | Header row; required `name` (tool label) plus status badge and chevron. | | `ToolContent` | Body for `ToolInput` / `ToolOutput` sections. | | `ToolInput` | Renders formatted JSON (or string) **Input** block. | | `ToolOutput` | Renders **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:** ```tsx ``` ### 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:** ```tsx 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:** | Part | Purpose | |------|---------| | `Questions` | Root **Card** + provider; required `items: QuestionInput[]`; optional `autoAdvance` (default `true`), `onSubmit`, `onSkip`, `onDismiss`. | | `QuestionsHeader` / `QuestionsTitle` / `QuestionsDismiss` | Top chrome and close control. | | `QuestionsCarousel*` | Shadcn **Carousel** wrapper for multi-question flows (`Prev` / `Next` / `Index` / `Pagination`). | | `Question` | One slide; registers `id`, `type` (`single` \| `multiple`), `prompt`, `required`. | | `QuestionOptions` | Layout wrapper for option rows. | | `QuestionOption` | Selectable row (radio or checkbox styling from question type). | | `QuestionOther` | Free-text “Other” option using `QUESTION_OTHER_VALUE`. | | `QuestionsFooter` / `QuestionsSkip` / `QuestionsSubmit` | Footer 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:** ```tsx console.log(submission)} > Quick questions {items.map((item) => ( {item.options.map((option) => ( {option.label} ))} ))} ``` ## AI SDK integration Use the [Vercel AI SDK](https://sdk.vercel.ai) 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. ```tsx 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 ( setInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); void onSend(); } }} disabled={busy} /> ); } ``` 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 - Website: - Docs: - Components: [prompt-input](https://nexus-ui.dev/docs/components/prompt-input) · [model-selector](https://nexus-ui.dev/docs/components/model-selector) · [suggestions](https://nexus-ui.dev/docs/components/suggestions) · [attachments](https://nexus-ui.dev/docs/components/attachments) · [message](https://nexus-ui.dev/docs/components/message) · [thread](https://nexus-ui.dev/docs/components/thread) · [citation](https://nexus-ui.dev/docs/components/citation) · [reasoning](https://nexus-ui.dev/docs/components/reasoning) · [text-shimmer](https://nexus-ui.dev/docs/components/text-shimmer) · [image](https://nexus-ui.dev/docs/components/image) · [feedback-bar](https://nexus-ui.dev/docs/components/feedback-bar) · [toaster](https://nexus-ui.dev/docs/components/toaster) · [chain-of-thought](https://nexus-ui.dev/docs/components/chain-of-thought) · [tool](https://nexus-ui.dev/docs/components/tool) · [questions](https://nexus-ui.dev/docs/components/questions) - GitHub: - LLM context: