Piano

A piano keyboard for a MIDI pitch range, played with the pointer, touch or the keyboard.

Audio UI

Plays or shows notes, for a virtual instrument, a MIDI visualizer or a chord reference. The registry component wraps the Piano primitive from @audio-ui/react and draws white and black keys for any MIDI range.

Overview

  • Range: minPitch and maxPitch are MIDI pitches (default 48 and 72, C3 to C5), or pitches lists the exact keys to show. Key colors and layout follow the chromatic pattern.
  • Events: onNoteOn(pitch, eventDetails) and onNoteOff(pitch, eventDetails) report MIDI pitches, ready for an audio engine. eventDetails.reason says how the note started ("press", "glide" or "keyboard"), and eventDetails.cancel() in onNoteOn keeps the key silent.
  • Pointer: press and hold a key to hold its note. Drag across keys for a glissando: each key plays as the pointer crosses it. Each touch holds its own key, so chords work on a touch screen.
  • External notes: activeNotes lights keys from outside, such as a MIDI input or a sequencer's current chord, without calling onNoteOn or onNoteOff.
  • Accessibility: each key is a button named after its pitch ("C4"), behind one tab stop. The arrow keys move between keys, and Space or Enter holds the focused key until it is released.
  • Layout: the primitive can also lay keys out vertically (orientation="vertical", lowest key at the bottom) or in equal widths (layout="even"). The piano roll's keyboard uses this.

Installation

pnpm dlx shadcn@latest add @audio-ui/piano

Anatomy

Import the primitive and assemble its parts.

import { Piano } from "@audio-ui/react";

<Piano.Root>
  <Piano.Keys>
    {(pitch) => (
      <Piano.Key pitch={pitch}>
        <Piano.KeyLabel />
      </Piano.Key>
    )}
  </Piano.Keys>
</Piano.Root>

Usage

import { Piano } from "@/components/audio-ui/piano";

Note events:

<Piano
  maxPitch={72}
  minPitch={48}
  onNoteOff={(pitch) => synth.release(pitch)}
  onNoteOn={(pitch) => synth.trigger(pitch)}
/>

External notes (MIDI input, a sequencer's current chord):

<Piano activeNotes={midiInputNotes} maxPitch={72} minPitch={48} />

A MIDI keyboard: useMidiInput listens to the page's MIDI input. Browsers ask for permission, so connect it from a click: with requestAccess, or the ready-made button, npx shadcn@latest add @audio-ui/midi-connect-control.

import { useMidiInput } from "@/hooks/use-midi-input";

const midi = useMidiInput({
  onNoteOff: (pitch) => synth.release(pitch),
  onNoteOn: (pitch, velocity) => synth.trigger(pitch, velocity),
});

<button onClick={midi.requestAccess} type="button">Connect MIDI</button>
<Piano activeNotes={midi.activeNotes} maxPitch={72} minPitch={48} />

Everything useMidiInput gives is in Input.

Interaction

Pointer

Behavior · 4

Press a key
Fires onNoteOn; the key stays pressed while held.
Release
Fires onNoteOff.
Drag across keys
Glissando: the previous key releases (onNoteOff) and the next one sounds (onNoteOn) as the pointer crosses it.
Multiple pointers
Each pointer (finger) holds its own key, so chords work on touch devices.

Keyboard

The keys share one tab stop. On the focused key:

Keys · 4

ArrowLeft / ArrowRight (or ArrowDown / ArrowUp)
Move focus to the next lower / higher key
Shift + arrow, PageDown / PageUp
Move focus an octave down / up
Space / Enter
Hold the focused key: onNoteOn on press, onNoteOff on release
Home / End
Move focus to the lowest / highest key

API reference

Root

A keyboard to play: every key sounds while pressed, by pointer or from the keyboard. Dragging across the keys glides from one to the next, and each pointer holds its own key, so chords can be played on a touch screen. It is laid out like a piano, or with layout="even" as one equal share per key, the way a piano roll's rows are; orientation="vertical" puts the low end at the bottom. The keys are one tab stop, walked with the arrow keys. Renders a <div> element.

Props · 9

Data attributes · 1

data-disabled
Present when disabled.

Keys

Renders children once per key, lowest first: the place to put a Piano.Key for each pitch. Renders no element of its own.

Props · 1

Key

One key, placed along the keyboard for its pitch. It sounds while pressed: by pointer through the keyboard, or with Enter or Space while focused. Its accessible name defaults to the pitch, such as "C4". Renders a <button> element.

Props · 1

Data attributes · 2

data-active
Whether the key is sounding: pressed here, or listed in activeNotes.
data-disabled
Present when disabled.

KeyLabel

The name of the key it sits in, such as "C4". Hidden from assistive technology, which reads the key's own name. Renders a <span> element.

Data attributes · 1

data-disabled
Present when disabled.

Registry component

Piano accepts style props (size, className) plus primitive props from PianoPrimitive.Root.Props.

Value and state

Props · 9

Layout and display

Props · 2

Additional valid HTML attributes passed to Piano.Root are forwarded.

Examples

External notes via activeNotes

Audio UI

More Shadcn Piano components

Browse 2 production-ready audio/ui Piano components for players, mixers, and synth interfaces. These examples use the headless primitives from @audio-ui/react and stay fully compatible with Shadcn Create so radius, color, and typography match your configured theme.

Browse all 2 Shadcn Piano components for copy-ready layouts, dashboards, and forms built with Tailwind CSS in the audio/ui library.

Built by Ouest Labs, the source code is available on GitHub.
RSS