Chords

Getting Started

State-aware components for assistant-ui that eliminate chat UI boilerplate.

What are Chords?

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.

  • Major Chord — Reads runtime state, makes multi-branch rendering decisions (e.g. ComposerActionStatus switches send/cancel/disabled based on thread state)
  • Minor Chord — Composes 2+ primitives into a pattern every app rebuilds (e.g. ScrollToBottom wraps the primitive + icon positioning)

Each chord replaces 20-60 lines of manual wiring with a single, configurable component — while giving you full control over styling and behavior.

Why?

Building a chat UI with assistant-ui primitives gives you maximum flexibility, but common patterns require a lot of boilerplate:

  • Toggling Send/Cancel buttons based on thread state
  • Showing copy-with-feedback buttons on messages
  • Configuring action bars with copy, reload, edit actions
  • Rendering suggestion chips on empty threads
  • Managing scroll-to-bottom visibility

Chords handle all of this for you, while keeping the same composability you're used to.

Quick Example

import { MessageActionBar } from "@assistant-ui/chords";

const AssistantMessage = () => (
  <MessagePrimitive.Root>
    <MessagePrimitive.Content />
    <MessageActionBar actions={["copy", "reload"]} />
  </MessagePrimitive.Root>
);
const AssistantMessage = () => (
  <MessagePrimitive.Root>
    <MessagePrimitive.Content />
    <ActionBarPrimitive.Root
      hideWhenRunning
      autohide="not-last"
      autohideFloat="single-branch"
    >
      <ActionBarPrimitive.Copy asChild>
        <button>
          {/* need conditional icon toggle logic */}
          <CopyIcon />
        </button>
      </ActionBarPrimitive.Copy>
      <ActionBarPrimitive.Reload asChild>
        <button>
          <ReloadIcon />
        </button>
      </ActionBarPrimitive.Reload>
    </ActionBarPrimitive.Root>
  </MessagePrimitive.Root>
);

Design Principles

  • Zero config works — drop in with no props, get a working component
  • Full overrideclassName replaces defaults, renderVisual swaps icons — no fighting specificity
  • No lock-in — mix chords with raw primitives freely in the same tree
  • Lightweight — minimal dependencies, no design system opinions

Next Steps

On this page