|
1 | | -# ux primitives |
| 1 | +# Chords |
2 | 2 |
|
3 | | -This repo is an experiment to make common Assistant UI interactions simpler by wrapping them into small, smart UI components. |
| 3 | +State-aware, drop-in components for [assistant-ui](https://github.com/assistant-ui/assistant-ui) that eliminate chat UI boilerplate. |
4 | 4 |
|
5 | | -Instead of manually wiring assistant state, send/cancel logic, and visuals in every app, these primitives handle it for you — with sensible defaults and optional customization. |
| 5 | +A chord is a pre-composed component that **reads runtime state and makes rendering decisions for you**. You don't think about conditionals or status checks — the chord handles the wiring, you own the UI. |
6 | 6 |
|
7 | | ---- |
| 7 | +## Quick Example |
8 | 8 |
|
9 | | -## What this simplifies (before → after) |
| 9 | +```tsx |
| 10 | +// Before — manual wiring |
| 11 | +<ActionBarPrimitive.Root hideWhenRunning autohide="not-last" autohideFloat="single-branch"> |
| 12 | + <ActionBarPrimitive.Copy asChild> |
| 13 | + <button>{/* conditional icon toggle logic */}<CopyIcon /></button> |
| 14 | + </ActionBarPrimitive.Copy> |
| 15 | + <ActionBarPrimitive.Reload asChild> |
| 16 | + <button><ReloadIcon /></button> |
| 17 | + </ActionBarPrimitive.Reload> |
| 18 | +</ActionBarPrimitive.Root> |
10 | 19 |
|
11 | | -### Before |
| 20 | +// After — one chord |
| 21 | +<MessageActionBar actions={["copy", "reload"]} /> |
| 22 | +``` |
12 | 23 |
|
13 | | -```tsx |
14 | | -<AuiIf |
15 | | - condition={({ composer, thread }) => composer.isEditing && !composer.isEmpty} |
16 | | -> |
17 | | - <AuiIf condition={({ thread }) => !thread.isRunning}> |
18 | | - <ComposerPrimitive.Send |
19 | | - className={cn( |
20 | | - "m-2 flex size-8 items-center justify-center rounded-full bg-white text-black", |
21 | | - composer.isEmpty && "opacity-30 pointer-events-none", |
22 | | - )} |
23 | | - > |
24 | | - <ArrowUpIcon className="size-5" /> |
25 | | - </ComposerPrimitive.Send> |
26 | | - </AuiIf> |
27 | | - |
28 | | - <AuiIf condition={({ thread }) => thread.isRunning}> |
29 | | - <ComposerPrimitive.Cancel className="m-2 flex size-8 items-center justify-center rounded-full bg-white text-black"> |
30 | | - <StopIcon className="size-4" /> |
31 | | - </ComposerPrimitive.Cancel> |
32 | | - </AuiIf> |
33 | | -</AuiIf> |
| 24 | +## Chords |
| 25 | + |
| 26 | +### Major Chords |
| 27 | + |
| 28 | +| Chord | What it wires | |
| 29 | +|-------|--------------| |
| 30 | +| **ComposerActionStatus** | `isRunning` → Cancel, `isEmpty` → disabled, else → Send | |
| 31 | +| **MessageActionBar** | Config-driven actions, copy state feedback, visibility | |
| 32 | +| **BranchNavigation** | `branchNumber`, `branchCount`, hideWhenSingleBranch | |
| 33 | +| **MessageStatus** | Derives running/error/complete from `message.status` | |
| 34 | +| **EditComposer** | `composer.canCancel`, `composer.isEmpty`, wires Cancel/Send | |
| 35 | +| **FollowUpSuggestions** | Reads `thread.suggestions`, hides when `isRunning` | |
| 36 | +| **ToolCallRenderer** | Derives running/complete/error/incomplete from `status` | |
| 37 | +| **Attachment** | Reads type/source/file/content, image vs file, composer vs message | |
| 38 | + |
| 39 | +### Minor Chords |
| 40 | + |
| 41 | +| Chord | What it composes | |
| 42 | +|-------|-----------------| |
| 43 | +| **CopyButton** | ActionBarPrimitive.Copy + copied/not-copied icon toggle | |
| 44 | +| **SuggestionChips** | Maps array → ThreadPrimitive.Suggestion buttons | |
| 45 | +| **ThreadEmpty** | ThreadPrimitive.Empty + greeting + avatar + SuggestionChips | |
| 46 | +| **ScrollToBottom** | ThreadPrimitive.ScrollToBottom + positioned icon button | |
| 47 | + |
| 48 | +## Install |
| 49 | + |
| 50 | +```bash |
| 51 | +npm install @assistant-ui/chords |
34 | 52 | ``` |
35 | 53 |
|
36 | | -### After (with UX primitives) |
| 54 | +Requires `@assistant-ui/react` >= 0.12.3, React >= 18, and Tailwind CSS v4. |
| 55 | + |
| 56 | +### Tailwind Setup |
| 57 | + |
| 58 | +Add a `@source` directive so Tailwind detects the chord classes: |
| 59 | + |
| 60 | +```css |
| 61 | +@import "tailwindcss"; |
| 62 | +@source "../node_modules/@assistant-ui/chords/dist"; |
| 63 | +``` |
| 64 | + |
| 65 | +### Usage |
37 | 66 |
|
38 | 67 | ```tsx |
| 68 | +import { ComposerActionStatus, MessageActionBar, CopyButton } from "@assistant-ui/chords"; |
| 69 | + |
| 70 | +// Drop in with zero config |
39 | 71 | <ComposerActionStatus /> |
| 72 | +<MessageActionBar actions={["copy", "reload", "edit"]} /> |
| 73 | +<CopyButton /> |
40 | 74 | ``` |
41 | 75 |
|
42 | | -Same behavior. One line. |
| 76 | +## Styling |
| 77 | + |
| 78 | +Every chord ships with sensible defaults for light and dark themes. Pass `className` to **replace** defaults — no specificity fights. |
| 79 | + |
| 80 | +```tsx |
| 81 | +<MessageActionBar |
| 82 | + className="flex gap-2 bg-gray-100" |
| 83 | + buttonClassName="rounded-full p-1" |
| 84 | + actions={["copy", "reload"]} |
| 85 | +/> |
| 86 | +``` |
| 87 | + |
| 88 | +## Design Principles |
| 89 | + |
| 90 | +- **Zero config works** — drop in with no props, get a working component |
| 91 | +- **Full override** — `className` replaces defaults, `renderVisual` swaps icons |
| 92 | +- **No lock-in** — mix chords with raw primitives freely |
| 93 | +- **Lightweight** — minimal dependencies, no design system opinions |
| 94 | + |
| 95 | +## Documentation |
| 96 | + |
| 97 | +[View the full docs →](https://chords.assistant-ui.com) |
43 | 98 |
|
44 | | -The component automatically handles: |
| 99 | +## License |
45 | 100 |
|
46 | | -- idle / composing / running states |
47 | | -- send vs cancel behavior |
48 | | -- sensible default visuals |
49 | | - (with full customization if needed) |
| 101 | +MIT |
0 commit comments