@neuronection/assistant-ui
v0.69.0
Published
Shared React component library for the Assistant family (career-, study-, health-, desktop-assistant). Family look by default, app-flavored via CSS-variable tokens.
Readme
assistant-ui
Shared React component library for the Assistant family — four sibling apps that AI-help people with career, study, health, and their desktop. One package, four apps: family look by default, app flavor via CSS-variable design tokens. Works in React 18 & 19 and alongside Tailwind 3 or 4 — the library ships precompiled CSS, so your app's Tailwind never compiles library classes.
neuronection.com — the family hub.
| App | What it is | |---|---| | career-assistant | Job discovery, AI matching and university pathways for students | | study-assistant | Study workspace: notes, materials, chat with math & code rendering | | health-assistant | Universal health data platform — self-hosted, privacy-first, open source | | desktop-assistant | System-tray AI launcher — global hotkey, streaming chat, voice input, local-only history |
The family shares its UI DNA here: 80+ modules, 690+ tests (keyboard-nav
- axe per component), built on Radix, token-themed so every app looks like a sibling without sharing a stack. Patterns the apps actually use — provider & model settings blocks, AI affordances (ask/fill/act), date & clock pickers, async comboboxes, file attachment surfaces, chat surfaces & trace panes — live in the package, not copy-pasted in four repos.
Install
pnpm add @neuronection/assistant-uiThen do exactly two things in your app entry:
import '@neuronection/assistant-ui/styles.css'
import './theme.css' // your app's token overrides, loaded afterimport { Button, ConfirmationModal, Input } from '@neuronection/assistant-ui'Theming
All visuals flow through --as-* CSS variables. The defaults are the family
look (extracted from study-assistant); each app overrides identity tokens in
a small theme.css:
:root {
--as-primary: oklch(0.62 0.15 152);
--as-radius: 0.75rem;
}Dark mode is the same story: remap the tokens under .dark (health-assistant
ships a class-based dark theme this way) — no component rewrites. Overlay
stacking is token-driven too (--as-z-modal, --as-z-popover) so library
dialogs clear your app chrome. Full guide: docs/theming.md.
Components
Every module has its own entry point (import { Menu } from
'@neuronection/assistant-ui/menu') and everything is re-exported from the
package root. Each module links a reference page under
docs/components/ below; the .d.ts files in the
package's dist/ stay the authoritative API for your installed version.
Visual reference: gallery.
| Module | Exports | Gallery |
|---|---|---|
| button / badge / card | Button, buttonVariants · Badge, badgeVariants · Card, CardHeader/Title/Description/Content/Footer | stories |
| modal (Modal + parts incl. ModalBody, PanelModal) | Modal + parts (compound Radix dialog) · PanelModal (header/body/footer shell, full-screen on mobile) | stories |
| confirmation-modal / form-modal | ConfirmationModal · FormModal (form-in-modal shell: headerActions, reject button, busy states) | stories |
| popover / popover-button | compound Radix Popover + parts · PopoverButton (self-contained trigger + panel, hover-open, lazy children, closeSignal) | stories |
| menu / context-menu | Menu, MenuTrigger/Content/Item/Separator/Label, ActionMenu (items-driven) · ContextMenu (coordinate-anchored, {x, y, items, onClose}) | stories |
| combobox | Combobox, ComboboxMulti — async mode (onSearchChange + loading), grouping, capability badges, full keyboard nav | stories |
| tooltip | Tooltip + parts, InfoTooltip | stories |
| wizard | Wizard (steps config + renderStep, per-step validation gates, modal/drawer variants), Stepper (dots/labels) | stories |
| input / textarea / search-input / expandable-search | Input (label/hint/error wiring) · Textarea · SearchInput · ExpandableSearch | stories |
| rich-text-editor | RichTextEditor — controlled tiptap markdown editor: full-override extensions, configurable toolbar groups + per-level headings + toolbarExtra slot, onReady/contentClassName composition props, label/icon props, caret-preserving external sync | stories |
| dictation / ai-text-transform | useDictation + DictationButton/DictationStrip (transport-injected STT) · useAiTextTransform (streaming transform state machine) | stories |
| text-diff-view | TextDiffView — side-by-side diff with word-level highlights, line numbers, change navigation, folds, virtualization (+ exported computeLineDiff/wordSegments) | stories |
| markdown-diff-view | MarkdownDiffView — side-by-side formatted markdown diff (KaTeX/tables/diagrams render; block-level compare, change navigation, folds, + splitMarkdownBlocks) | stories |
| chip-input / chip-list | ChipInput (Enter/comma commit, paste-splits, Backspace-removes) · ChipList (variant pills, clickable, removable) | stories |
| time-picker / time-list | TimePicker (clock face, 24h value, 12h UI, editable fields) · TimeList (time chips with per-chip pickers) | stories |
| date-picker | DatePicker (popover calendar: days/months/years views, min/max, allowClear, arrow-key day grid, unstyled variant) | stories |
| range-bar / scale-slider | RangeBar (low–high band + value dot) · ScaleSlider (+ scaleColorForValue) | stories |
| table | Table (headers/rows, opt-in empty state) | stories |
| check-indicator / selection-bar / view-toggle | CheckIndicator (tri-state) · SelectionBar (bulk-select bar) · ViewToggle (grid/list) | stories |
| segmented-tabs | SegmentedTabs (pill tablist with sliding thumb, arrow-key activation, disabled-skip) | stories |
| error-banner / undo-notice / empty-state / spinner / skeleton | presentational feedback: alert banner with action slot · undo toast · EmptyState · Spinner · Skeleton/SkeletonText shimmer placeholders (reduced-motion static) · rich hover previews: hover-card | stories(https://neuronection.github.io/assistant-ui/?story=feedback--errors) |
| info-button / field-label / copy-button / breadcrumbs | InfoButton · FieldLabel · CopyButton (clipboard + copied state) · Breadcrumbs (SPA links via linkComponent) | stories |
| ai-button / ai-actions-dropdown / ai-magic-fill | AI affordances: ask-with-suggestions (controlled open, icon-only mode) · action menu + custom prompt · describe-in-words fill modal — API calls stay app-side | stories |
| chat-core | useChatStream (transport-injected live-turn state machine), liveTurnReducer, ChatMessageView, family ChatStreamEvent vocabulary, branch-tree utils (buildBranchTree, walkActivePath, variantInfo) | stories |
| chat-markdown | MarkdownSurface (read-only streaming markdown: GFM + KaTeX + lazy mermaid + copyable code blocks; no raw HTML) · MarkdownCodeBlock · MermaidDiagram | stories |
| chat-message / chat-reasoning / chat-tool-card / chat-turn-status | ChatMessage (bubble: actions row, variant switcher, edit-and-resend, error/interrupted states) · ChatReasoning (collapsible thinking block: animated reveal, streaming accent, auto-collapse) · ChatToolCard (tool observation: status, duration, args/result, app-provided result views via renderResult) · ChatTurnStatus (live pre-text turn status: dots, phase label, elapsed timer) | stories |
| chat-pending | ChatPending — first-token latency placeholder: narrated phase row (ChatTurnStatus) over an AI-accented shimmer bubble for the transcript's live tail | stories |
| chat-welcome | ChatWelcome — chat empty-state kit: breathing --as-ai orb, greeting + subtitle, staggered suggestion slots (page / sidebar / bubble densities) | stories |
| chat-suggestion-card | ChatSuggestionCard — the standard suggestion chip for welcome screens and composer strips: icon + title + hint, hover lift | stories |
| chat-hitl | HitlProposalCard / FieldDiff — human-in-the-loop proposal cards: field-level before/after diffs (long text via TextDiffView, structured collection chips), approve/reject with armed destructive confirm, preview slot, pending/approved/rejected/conflict/expired/reverted states | stories |
| chat-composer | ChatComposer — auto-growing IME-safe input with send/stop states, toolbar/attachment/suggestion slots, drag-drop + paste wiring, data-multiline row styling hook | stories |
| chat-transcript / chat-branch-tree / chat-session-list | ChatTranscript (log semantics, polite announcements, stick-to-bottom + jump pill, optional virtualization) · ChatBranchTree (OpenWebUI-style branch rail, keyboard tree nav) · ChatSessionList (fuzzy search, date groups, row actions) | stories |
| chat-panel / chat-drawer / chat-launcher | the three chat surfaces from one assembly — ChatPanel host (page | sidebar | bubble) · ChatDrawer (resizable sidepanel on Radix Dialog, keyboard resize) · ChatLauncher (floating bubble → anchored panel, badge) | stories |
| chat-tools-catalog | ChatToolsCatalog — searchable disclosure catalog of the tools an assistant can use (name, title, scope, arguments, example, response); fetching stays app-side | stories |
| chat-export | buildChatMarkdown / chatExportFileName / downloadChatMarkdown — conversation → Markdown export with role-label styles and app-supplied blockquote annotations | stories |
| chat-history-button | ChatHistoryButton — labelled history popover for a chat header (open-refresh hook, close-on-pick); the session list stays app-side | stories |
| chat-trace-meta | ChatTraceMeta — compact model · duration · N tools trace badges; renders nothing without data | stories |
| chat-trace-timeline |
| detail-value | DetailValue — internal parsed-detail pane (key/value rows, value chips, preformatted fallback) used by chat-tool-card + chat-trace-timeline | stories |
ChatTraceTimeline — collapsible per-turn trace: duration-proportional phase/tool bars, tokens, model, reasoning disclosure | stories |
| flow-status | FlowStatusCard (multi-step AI-flow progress from the family event vocabulary: per-node status, current-step emphasis, progress summary, error + retryable, controlled retry/cancel/resume, detail slot for HITL cards) | stories |
| flow-trace | FlowTraceCard (run ledger over the library-owned FlowTrace schema: outcome badge, stage timeline, per-call LLM table with tokens/latency/prompt version, ops ✓/✗ list, simulated badge for mock rows) + FlowTelemetryStrip (compact calls · tokens in/out · edits live counters) | stories |
| settings-shell / provider-form / connection-test-row | SettingsShell (controlled two-pane nav) · ProviderForm (write-only API-key field) · ConnectionTestRow (idle/testing/ok/fail + latency, inline variant + meta slot) | stories |
| provider-setup-tiles / provider-setup-form / setup-review / setup-assignments / setup-steps / switch-row / provider-logo / setup-first-run-card | The plan-26 one-click setup kit (BYOK + decision models): neutral equal-weight preset tiles (family order, no "recommended", optional per-tile capability chips — chat/vision/decision/local, i18n labels prop-injected) · guided setup form (checklist + copy buttons, write-only new-password key, Advanced disclosure with fixed-base read-only, §15 error enum copy prop-injected) · editable pre-flight review emitting the five-bind options body {curatedIds, bindChat, bindVision, bindStt, bindDecision} (decision-only presets show the decision slot only) · the review's "Default assignments" switch section (aria-current step headers for wizard progress) · labeled role="switch" row with now: sub-line · simple-icons provider mark with monogram fallback (openai/groq gone upstream), currentColor · first-run CTA card with local-probe status | stories |
| provider-card | ProviderCard — provider summary card: mark + name + identity badges + muted subtitle line (endpoint context, nameTitle/tooltip-bearing), display-only ConnectionTestRow status line, management actions in a labelled ⋯ overflow menu | stories |
| web-search-providers | WebSearchProvidersPanel — the ADR-0035 web-search settings surface (plan 28): ordered multi-provider list (enable, reorder, test with probe status, edit, delete) + catalog-driven add/edit form; presentational, every label overridable | stories |
| admin-user-table | AdminUserTable — controlled admin user management table (email + "(you)" marker, activity count, role/status, promote/demote, activate/deactivate, inline password reset with min-length gate, force logout; guard-rail 403s as friendly messages) | stories |
| surface-renderer | SurfaceRenderer — the ADR-0040 clause-16 declarative-surface renderer (plan 32 Phase 4): the charter's closed component vocabulary (stat/table/cards/filter-bar/select/badge/progress/actions/evidence) rendered from a schema + resolved labels with typed action ids out — promoted from desktop's PackReportRenderer, career + desktop are hosts, pinned by the shared fixture wall (dev/contracts/surface-renderer.fixtures.json) | stories |
| integration-contracts / integration-atoms | The plan-33 integrations kit foundation: generated charter vocabulary + view-model mapping target (origin classes, categories, status, tiers, capability ids, risk classes, verbatim consent disclosures — regenerated from the vendored contracts/extension-charter.json, byte-identity walled) · the shared atoms (OriginChip D25 display law, StatusBadge, TierBadge, risk-marked IntegrationCapabilityChips) | stories |
| integration-card / integration-catalog-grid / integration-instances-list / integration-sources-list | The plan-33 registry surfaces: IntegrationCard (identity, verbatim provenance, origin/tier/capability chips, update states, typed actions out, detail + actions slots) · IntegrationCatalogGrid (charter-category groups, controlled search, host-compat results verbatim, loading/error/empty) · IntegrationInstancesList (instances grouped under their extension, conditions + runtime fields, per-instance typed actions) · IntegrationSourcesList (sources with status, pinned-ref short forms, GitOps indicators, candidates → consent handoff) | stories |
| add-integration-source-dialog / consent-dialog / integration-config-form | The plan-33 flow dialogs: AddIntegrationSourceDialog (one flow for git + local sources, sealed credential slot, fetch/validate progress) · ConsentDialog (install + capability-growth re-consent + update — the charter's payload rendered VERBATIM with the D22 law disclosure as data and the G7 change summary) · IntegrationConfigForm (JSON-schema-subset fields, x-secret password fields with hint-only sealed slots, async host validation in/results out) | stories |
| integration-action-result-dialog / integration-docs-dialog | The plan-33 extension-content dialogs: ActionResultDialog (HOSTS SurfaceRenderer for declarative result payloads — compose not reinvent, loading/error/retry, typed actions out, per-product chrome slot) · DocsDialog (bundle docs tree + app-owned content slot, loading/error/empty) | stories |
| instance-mode-control | InstanceModeControl — controlled §4.5 instance access-mode panel (current mode, open→authenticated with owner credential set, authenticated→open with password + explicit ack, blocked states, audit note) | stories |
| login-form / register-form | LoginForm — controlled sign-in form (labelled email + password with visibility toggle, loading/error props, register/forgot link slots; no remember-me — HttpOnly sessions are server-owned) · RegisterForm — controlled registration form (confirm-password mismatch gate, optional full-name field, min-length hint, login link slot) | stories |
| auth-gate | AuthGate — session gate owning the checking → authenticated \| anonymous boot machine (identity-auth §4): app-owned boot flow + login/checking nodes in, resetKey replay for mid-session 401s and forced drops, onStatusChange out | stories |
| sidebar-nav | SidebarNav (family-standard vertical nav: groups + sections, badges, controlled active id, pinned secondary items, optional collapsed icon rail with hover flyouts, header/footer slots) | stories |
| user-menu | UserMenu (full identity dropdown: avatar/identity trigger + switcher slot + identity header with role badge + status pill + theme/language checkables + app items + logout; composed over Menu) | stories |
| profile-switcher | ProfileSwitcher — controlled profile switcher (identity-auth §6/§12): current-profile trigger + panel with select, create, rename, set-Default and delete flows; label/icon props with English defaults | stories |
| model-picker / task-assignment-picker / capability-chips | ModelPicker (grouped provider→model combobox, capability chips) · TaskAssignmentPicker (task → model mapping rows, capability-filtered, sections + fallback assignment, meta slot) · CapabilityChips (cap toggle group / badges, min-selected) | stories |
| model-registry | ModelRegistry (provider cards → registered model rows; add/edit in one catalog modal fed by app-fetched remote models; manual id entry; add-all of pending ids) | stories |
| upload-dropzone / file-card / file-queue | file attachment surface: dropzone (block/row) · card (status, include toggle) · queue (summary + reorder) | stories |
| marquee | useMarquee, MarqueeSurface, MarqueeBand (rubber-band selection) | stories |
| portal / theme-scope / tokens | Portal · ThemeScope · token name lists + types | stories |
| logo / about | NeuronectionMark, NeuronectionWordmark, Career/Study/Health/DesktopMark · AboutPanel + about-page building blocks (AboutCard, AboutLinkList, FamilyBadge, SponsorCard, …) | stories |
API rules: controlled-first, className merges (never replaces), asChild
where it makes sense, refs forwarded everywhere, English label props with
defaults (translate at call sites), icons as props. No fetching, no stores,
no router — data in, events out.
Documentation
Guides (docs/guides/):
- Import & theming — import paths
(subpath vs barrel vs app shims), CSS load order,
--as-*tokens,data-as-*styling hooks, Vite setup checklist - Utilities —
searchScore/fuzzyScoreranking model,cn, the tokens entry point - AI settings surfaces — the providers →
models → tasks recipe (
ProviderForm,ConnectionTestRow,ModelRegistry,TaskAssignmentPicker) with real app call-sites - Chat surfaces — the bubble/sidepanel/ page assembly from the chat modules: transport adapters, the live tail, branching (versioning) contract, composer slots
Per-component reference (docs/components/<module>.md): purpose, import
line, props table, controlled contract, i18n contract, snippets,
accessibility notes — every module in the table above links its page.
- Adopting in an app — the install/switchover recipe each family app follows (incl. the TW3 cascade notes)
- Theming — tokens, app themes, dark mode, styling hooks
- Accessibility — the keyboard + ARIA contract per module (test-asserted)
- Adding a component — rules, checklist, required companions
- Local development — live-test library changes in the family apps without publishing
- Publishing — the Changesets release flow and CI setup
Development
pnpm install
pnpm dev # Ladle playground on :61000 (CSS watches alongside)
pnpm test --watch
pnpm verify # lint + typecheck + test + build
pnpm test:visual # Playwright screenshots of every story vs committed baselinesReleases happen via Changesets — CI publishes, never a laptop.
License
Apache-2.0 — same as the family apps this library was extracted from.
