Skip to main content

Piano

Customizable piano component.

Source Style
Loading...
function App() {
  const synthRef = useRef<Tone.PolySynth | null>(null)

  useEffect(() => {
    const synth = new Tone.PolySynth({ volume: -6 }).toDestination()
    synthRef.current = synth

    return () => {
      synth.releaseAll()
      synth.dispose()
      if (synthRef.current === synth) synthRef.current = null
    }
  }, [])

  return (
    <div
      style={{
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
      }}
    >
      <Piano.Root
        className={pianoTheme.root}
        classes={{
          keyLabelWrapper: pianoTheme.keyLabelWrapper,
          keyLabel: pianoTheme.keyLabel,
        }}
        keyProps={(_note, { keyType }) => ({
          className:
            keyType === 'white' ? pianoTheme.whiteKey : pianoTheme.blackKey,
        })}
        noteRange={{ first: noteNumber('C3'), last: noteNumber('B4') }}
        keyboardShortcuts={SHORTCUTS.HOME_ROW}
        onPlayNote={(noteNumber) => {
          synthRef.current?.triggerAttack(noteName(noteNumber))
        }}
        onStopNote={(noteNumber) => {
          synthRef.current?.triggerRelease(noteName(noteNumber))
        }}
        label={(_, { index }) => SHORTCUTS.HOME_ROW.keys[index]?.toUpperCase()}
      />
    </div>
  )
}

Import​

import { SHORTCUTS } from '@tremolo-ui/dom'
import { noteNumber, noteName } from '@tremolo-ui/functions'
import { Piano } from '@tremolo-ui/react'

API Reference​

Piano.Root also takes the attributes of the element it renders, such as className and style.

In Svelte and Vue the props are the same, written their way — class rather than className, bind:value or v-model, snippets or slots for content. See Svelte and Vue.

State is on the element as a data attribute, so it is a selector away — see Styling. An attribute is only there while its state is on.

Root​

Draws a key for every note in noteRange. It has no other parts: a key is customised through label and keyProps.

PropTypeDefault
classes{ keyLabelWrapper?: string; keyLabel?: string }—
noteRange*NoteRange—
glissandobooleantrue
midiMaxnumber127
keyboardShortcutsKeyboardShortcuts—
keyboardShortcutsScopeKeyboardShortcutsScope'root'
resizablebooleanfalse
whiteKeyWidthnumber40
keyGapnumber1
blackKeyWidthRationumber0.65
blackKeyHeightRationumber0.6
label(note: number, state: KeyState) => ReactNode—
keyProps(note: number, state: KeyState) => KeyAttributes—
onPlayNote(note: number, velocity?: number) => void—
onStopNote(note: number) => void—
actionsRefRef<PianoMethods>—

Full reference: PianoProps

The keyboard's height and each key's colours are set in CSS. Use keyProps to give individual keys a class, style, or data-* attribute.

Data attributeValues
[data-resizable]Present when resizable is set

Keys​

Each key carries these.

Data attributeValues
[data-note]"0" … "127"
[data-note-key]"C" | "C#" | … | "B"
[data-active]Present while the note is sounding
[data-disabled]Present when the note is past midiMax

A key takes more through keyProps, so a state of your own is one more attribute:

keyProps={(note) => ({ 'data-in-scale': inScale(note, 0, 'major') ? '' : undefined })}