Chord Graph
chord-graphVisualizersA chord chart tells you the order chords arrive in. It does not tell you why one leads to another comfortably and another lands with a jolt — and that is largely about shared pitch classes: C and Am hold two notes in common and slide into each other almost imperceptibly, C and F♯ share nothing and feel like a cut. This draws that relation directly, with an edge between every pair sharing at least one note, weighted by how many. The result is a map of a progression's internal gravity: a tightly-connected cluster will interchange freely, an isolated node will always sound like an arrival. Ring layout keeps your own sequence as the geometry; fifths layout places each chord by its root, so distance on screen becomes harmonic distance.
I-V-vi-IV. The thick ties are the two-note relationships — C to Am, Am to F — which is why those swaps feel so smooth. G shares nothing with Am or F, so it sits unconnected to either.
Install
Copies the source into your project. Anything it depends on comes with it.
$ npx shadcn add @syntho-ui/chord-graphPulls in
First time? Add the registry to your components.json once:
{
"registries": {
"@syntho-ui": "https://engine.syntho.app/r/{name}.json"
}
}Usage
Controlled or uncontrolled, whichever suits the surrounding state.
import { ChordGraph } from "@/components/ui/chord-graph"
// Pitch classes root-first — the fifths layout positions by pcs[0].
<ChordGraph
chords={[
{ label: "C", pcs: [0, 4, 7] },
{ label: "G", pcs: [7, 11, 2] },
{ label: "Am", pcs: [9, 0, 4] },
{ label: "F", pcs: [5, 9, 0] },
]}
activeIndex={0}
layout="ring"
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| chords | { label: string, pcs: number[], color?: string }[] | — | The chords to plot. Give each one's pitch classes ROOT FIRST — pcs[0] is what the fifths layout positions by. Reduced mod 12 and deduped, so octave doublings never inflate a shared count. |
| layout | "ring" | "fifths" | "ring" | Even spacing in the order given, or placement at each root's circle-of-fifths angle. Chords sharing a root are pulled inward so they never stack. |
| activeIndex | number | null | null | Enlarges this chord's node and emphasises the edges touching it, dimming the rest. |
| size | number | 280 | Overall square size in pixels. |
| color | string | — | Default node and edge colour; individual chords can override it. Defaults to the --uikit-accent token. |
Interaction
- Drag with pointer capture, so the gesture keeps tracking past the edge of the control.
- Shift while dragging for a fine pass; release it mid-gesture and coarse tracking resumes.
- Double-click resets to
defaultValue. - Arrow keys step normally, Shift+arrow steps finely, PageUp/PageDown jump by ten, Home/End go to the extremes.
- Proper
sliderandswitchroles with livearia-valuetext, so a screen reader announces the formatted value rather than the raw number. - The scroll wheel adjusts the value while the pointer is over the control; stepped ranges move at least one declared step per notch by default.