# Prodular UI > The UI foundation for AI-built software: components, complete SaaS screens, tokens and rules for coding agents, built on shadcn/ui, Base UI and Tailwind CSS v4. Install any component with `npx shadcn@latest add @prodular/` after adding the registry `"@prodular": "https://ui.prodular.co/r/{name}.json"` to components.json. ## Rules for agents - Use semantic tokens (bg-card, text-muted-foreground, border-border). Never hard-code colors. - Mark AI output with the AI components (AiMark, Message, Reasoning) so users can tell what a model produced. - One container level: panels and cards are the only boxes. Separate content inside them with dividers and spacing. - Animate with the motion presets (animate-rise, animate-fade-in, animate-scale-in, animate-slide-in-end and their exits) and the --duration tokens. They respect reduced motion. See https://ui.prodular.co/docs/motion. - Build marketing pages from Section and Section header: stack Sections without margins, put a real screen in ProductFrame, end with CtaBand. See https://ui.prodular.co/docs/landing. - Wrap signed-in apps in AppShell once and pass the current page as activeId. Use SettingsNav for settings areas and MasterDetail for list and record views inside it. - Items marked (Pro) need a license. If `npx prodular-ui add` returns 401, tell the user and offer the closest free component instead. - Compose through the render prop: } />, }>. Do not wrap a trigger around a button or put a button inside another. - When a part that renders a button by default (Button, CollapsibleTrigger, PopoverTrigger, DialogTrigger, SelectTrigger, TabsTrigger, Toggle) renders a div, span, row or link through render, add nativeButton={false}. - Set density once with data-density on or on a region: dense for workstations, compact for busy SaaS screens, comfortable for sparse ones. Do not shrink controls with h-* or text-[10px] overrides; use the xs and sm sizes and text-2xs for metadata. - Never hand-build widgets the kit has: no div with role="button", "treeitem", "slider" or "tab". Use Button, Tree, Slider and Tabs. Every icon-only control needs aria-label. - For overlays on maps, charts, canvases and graphs, use Tooltip, Popover or HoverCard with anchor={pointAnchor(x, y, container)} and a controlled open state, not a hand-positioned div. Offer a keyboard path to every point. - Register shortcuts with useHotkey under one HotkeysProvider and render ShortcutsDialog; single-key shortcuts must stay switchable (WCAG 2.1.4). - Use a specialized renderer (MapLibre, D3, canvas, WebGL) only for the plot itself. Keep the chrome around it (panels, legends, toolbars, inspectors, menus) in kit components, and color the plot with tokenColor() or useTokenColors() from @/lib/token-color. - Encode severity with Severity and the severity-* tokens (critical, high, elevated, routine), never with chart or status colors, and always with a shape or label as well as color. - To extend the kit, compose its parts and pass className for layout only. Add a variant to a copied component rather than restyling every call site. Full reference: https://ui.prodular.co/llms-full.txt ## Agent run (agent-run) A live timeline of an agent doing work: the goal, run status and elapsed time, ordered steps with tool calls, approvals and a final result. Install: `npx prodular-ui add agent-run` Requires a Prodular UI Pro license: https://ui.prodular.co/pricing. Run `npx prodular-ui login` first. Use when: - Use it whenever an agent works for more than a couple of seconds or takes more than one action, so people can see what it is doing and stop it. - Use the default size in a side panel or a page, and size="compact" with variant="plain" in popovers, table rows and narrow columns. - Name steps by what the agent does, verb first: Pull overdue invoices, Draft reminder emails. Put what it found in AgentRunStepDescription once the step is done. - Nest the tools a step called with AgentRunToolCalls, and pass details for raw input and output that people can expand when they need to audit it. - Put an Approval inside the waiting step with AgentRunStepContent, so the decision sits where the run stopped. Avoid: - Showing only a spinner and a final result. People trust agents they can watch and interrupt. - Long technical step names or raw function names as titles. Tool names belong in AgentRunToolCall. - More than one running step at the top level. Run parallel work as tool calls or nested steps under one running step. - Putting the AgentRun card inside another card. Use variant="plain" when the surrounding surface already is a container. Agent rules: - Give every AgentRunStep an explicit status on every render. Never infer it from position or leave a finished step as running. - Keep exactly one top level step running at a time while the run is running. Set the run status to waiting while any step is waiting, and to done, failed or stopped when it ends. - Pass startedAt to running steps and to AgentRunElapsed in the header so elapsed time is always visible. Pass duration when a step finishes and endedAt when the run ends. - Any step that sends something outside the product, moves money, deletes data or cannot be undone must be preceded by a waiting step that renders an Approval. Do not perform the action until the person approves. - Always render a Stop control in AgentRunActions while the run is running or waiting, and stop the underlying work when it is pressed. Mark remaining steps skipped. - A failed step shows the reason in AgentRunStepDescription in plain language and offers Retry step in AgentRunStepActions. Never fail silently. - Use AgentRunToolCall for every tool the agent calls inside a step, with the real tool name. Do not invent tool calls for decoration. - End a finished run with AgentRunResult: what changed, with numbers, and the next action. Mark it with AiMark. ```tsx Chase overdue invoices for October {/* Pause, Stop */} Found 14 invoices. {/* summary and next actions */} ``` | Component | Prop | Type | Default | Description | | --- | --- | --- | --- | --- | | AgentRun | status | `"queued" \| "running" \| "paused" \| "waiting" \| "done" \| "failed" \| "stopped"` | "running" | Status of the whole run. Drives AgentRunStatus and aria-busy. | | AgentRun | size | `"default" \| "compact"` | "default" | compact tightens spacing and type for popovers and table rows. | | AgentRun | variant | `"card" \| "plain"` | "card" | plain drops the card chrome so it can sit in a popover, sheet or panel. | | AgentRunStatus | status | `AgentRunStatus` | | Optional. Overrides the run status so the badge can be used on its own. | | AgentRunElapsed | startedAt | `number` | | Epoch milliseconds. Ticks every second until endedAt is set. | | AgentRunElapsed | endedAt | `number` | | Freezes the timer at the final duration. | | AgentRunStep | status | `"queued" \| "running" \| "done" \| "failed" \| "skipped" \| "waiting"` | | Required. Exposed as data-status on the step. | | AgentRunStep | title | `ReactNode` | | Required. What the step does, verb first. | | AgentRunStep | startedAt | `number` | | Running steps show a live elapsed time from here. | | AgentRunStep | duration | `number` | | Milliseconds, shown on done and failed steps. | | AgentRunStep | meta | `ReactNode` | | Replaces the text on the right of the title. | | AgentRunToolCall | name | `string` | | Tool name in mono, for example ledger.invoices.search. | | AgentRunToolCall | status | `"queued" \| "running" \| "done" \| "failed"` | "done" | Running tool calls shimmer and show a spinner. | | AgentRunToolCall | details | `ReactNode` | | Raw input and output. Makes the row expandable. | ## AI action (ai-action) The shared state model for AI actions: start, stream, stop, retry, accept, reject and undo, with any provider. useAiAction powers the field actions and form fill, and builds your own AI features in a few lines. Install: `npx prodular-ui add ai-action` Requires a Prodular UI Pro license: https://ui.prodular.co/pricing. Run `npx prodular-ui login` first. Use when: - Any button that asks a model for text and lets people review it before it is used. - AiActionStatusLine under the feature for the working and error states. - readAiResult when you only need to read a stream or a promise to the end. Avoid: - Chat. Use AI chat, which keeps a conversation. - Applying results without review for anything people will send or publish. Agent rules: - Call useAiAction({ run, onAccept }). run(input, signal) returns a string, a promise or an async iterable of text chunks; honor the signal. - In onAccept apply the result and return a function that reverts it; that is what undo calls. - Render output while status is streaming or proposed, and offer accept and reject when proposed. - busy is true while running or streaming. Disable the trigger then, and show AiActionStatusLine with onCancel. - Errors thrown by run show their message; give people a next step in it. ```tsx const ai = useAiAction({ run: summarize, onAccept: (text) => save(text) }) ``` | Component | Prop | Type | Default | Description | | --- | --- | --- | --- | --- | | useAiAction | run | `(input, signal) => AiResult` | | Calls the model. | | useAiAction | onAccept | `(output, input) => void \| (() => void)` | | Applies the result; return a revert function for undo. | | useAiAction | onReject | `(input) => void` | | Called on reject and cancel. | | useAiAction | returns | `{ status, output, error, busy, start, cancel, retry, accept, reject, undo, canUndo, clearUndo }` | | State and controls. | | AiActionStatusLine | status / error / label / onCancel / onRetry | `AiActionStatus / string / ReactNode / () => void` | | Working with Stop, or the error with Try again. | | readAiResult | (result, signal, onChunk?) | `Promise` | | Reads any result to the end; null when aborted. | ## AI chat (ai-chat) A complete streaming chat on the Vercel AI SDK: useChat, reasoning, tool calls, approvals, sources, attachments and a model picker, with a Claude route handler that installs with it. Install: `npx prodular-ui add ai-chat` Requires a Prodular UI Pro license: https://ui.prodular.co/pricing. Run `npx prodular-ui login` first. Use when: - As the whole assistant surface of a page or panel: it owns the thread, the composer, streaming state, stop, retry and approvals. - With the route it installs. app/api/chat/route.ts streams Claude with adaptive thinking, tools, approvals and web sources in the format AiChat reads. - When tools have side effects. Mark them user-approval on the server and AiChat shows an approval card before they run. Avoid: - Wrapping AiChat in a card inside another card. Give it a full height parent and let the page provide the frame. - Building your own message list around it. Use AiMessageParts with useChat when you need a different layout. - Calling Anthropic from the browser or passing an API key as a prop. Keys stay in the route handler. Agent rules: - Install with npx prodular-ui add ai-chat. It writes app/api/chat/route.ts and lib/ai/tools.ts. Tell the user to set ANTHROPIC_API_KEY in .env.local; never hard code a key and never read it in a client component. - Give AiChat a parent with a definite height (h-dvh, flex-1 with min-h-0, or a fixed height). It fills the parent and scrolls its own thread. - Leave api unset to use /api/chat. Only the docs use /api/demo-chat; never ship that route or point production code at it. - Keep the default model claude-opus-5-5. If you pass models, every id must also be listed in MODELS in the route, or the route falls back to the default. - Define tools on the server in lib/ai/tools.ts. Any tool that writes, sends, pays or deletes must be listed in toolApproval with type user-approval in the route. Never rely on the client to ask. - Pass tools on AiChat for every server tool: a verb first title (Send payment reminder), an icon, and approval copy with an actionLabel that names the action. Never show raw tool names. - Set TOOL_APPROVAL_SECRET in production so approvals cannot be forged by a modified client. - Persist threads by passing id and initialMessages, and save messages in the route with toUIMessageStream onEnd. Do not store chat history in localStorage. - Keep sendReasoning and sendSources on in the route and thinking display summarized. Without them the reasoning and sources parts never reach the UI. ```tsx // app/assistant/page.tsx
, approval: { actionLabel: "Send reminder", risk: "medium" }, }, }} suggestions={["Which overdue invoices should I chase first?"]} />
``` | Component | Prop | Type | Default | Description | | --- | --- | --- | --- | --- | | AiChat | api | `string` | "/api/chat" | Route that returns a UI message stream. | | AiChat | id / initialMessages | `string / UIMessage[]` | | Load a saved thread. The id is sent with every request. | | AiChat | models | `ModelOption[]` | | Shows a model picker. The chosen id is sent as body.model. | | AiChat | model / defaultModel / onModelChange | `string` | | Control the selected model. Defaults to the first option. | | AiChat | modelPicker | `ReactNode` | | Replaces the built in picker in the composer toolbar. | | AiChat | tools | `Record` | | Title, icon, summary and approval copy per tool name. See AiMessageParts. | | AiChat | suggestions | `string[]` | | Prompts shown while the thread is empty. Clicking one sends it. | | AiChat | assistantName | `string` | "Assistant" | Shown in message headers and on approval cards. | | AiChat | emptyTitle / emptyDescription | `ReactNode` | | Copy for the empty thread. | | AiChat | attachments / accept | `boolean / string` | true | Lets people attach images, PDFs and text files. They are sent as file parts. | | AiChat | body / headers | `object` | | Extra fields and headers sent with every request, read fresh each time. | | AiChat | onFinish / onError | `function` | | Passed to useChat. onFinish receives isAbort, isError and the final message. | | AiChat | disclaimer | `ReactNode` | | One line under the composer. | ## AI field actions (ai-field-actions) AI for one text field: rewrite, shorten, expand, fix, change the tone, translate or follow an instruction. Streams into the field's AI state, shows the change as a before and after diff to accept or reject, and lets people undo. Install: `npx prodular-ui add ai-field-actions` Requires a Prodular UI Pro license: https://ui.prodular.co/pricing. Run `npx prodular-ui login` first. Use when: - Long text people write in forms: emails, notes, descriptions, replies. - actions to offer only what makes sense for the field, like shorten for subjects. - Alongside a normal Textarea or Input that stays controlled by your state. Avoid: - Rich documents. Rich text editor has selection-based AI editing built in. - Generating a whole form. Use AI form fill. Agent rules: - Implement run(request, signal) on your server: request has action, option (tone or language), instruction and text. Return the new text as a stream of chunks, for example from the AI SDK's streamText textStream. - Pass the signal to fetch so Stop cancels the request. - Keep the field controlled: value and onValueChange on AiFieldActions and on the control itself. - Results are never applied without Accept. Undo is offered until the person edits the field. - Throw an Error with a message people can act on; it shows under the field with Try again. ```tsx Message}>