Keys

A responsive and interactive piano keyboard component with configurable number of keys, note highlighting, and multiple styling modes.

The Keys component provides a responsive and interactive piano keyboard visualization. It shares the same layout and accessibility as other vector components; see the Vector introduction.

  • Flexible configuration: Variable number of keys (1-128), different starting positions, octave transposition
  • Note highlighting: Visual feedback for active notes via notesOn prop
  • Multiple styling modes: theme-based, classic ivory/ebony, and inverted classic
  • Interactive control: Optional onChange callback for key press/release events
  • Full theming support: color, roundness customization (in theme mode)

Props

NameTypeDefaultRequiredDescription
nbKeysnumber61OptionalNumber of keys on the keyboard (1-128)
startKey"A" | "B" | "C" | "D" | "E" | "F" | "G"—OptionalStarting note name (A-G). Default 'C' for 61 keys, 'A' for 88 keys
octaveShiftnumber0OptionalOctave transpose index. Positive values shift notes up by that many octaves, negative values shift down
notesOn(string | number)[][]OptionalArray of notes that should be highlighted. Notes can be specified as strings (e.g., 'C4', 'F#5') or MIDI note numbers (e.g., 60 for C4)
keyStyle"theme" | "classic" | "classic-inverted"themeOptionalKey styling mode. 'theme' uses theme colors, 'classic' uses ivory/ebony colors, 'classic-inverted' inverts the classic colors
onChange(event: AudioControlEvent<{ note: number; active: boolean }>) => void—OptionalCallback triggered when a key is pressed or released. Only active if this prop is provided.
colorstring—OptionalComponent primary color - any valid CSS color value (used in theme mode)
roundnessnumber—OptionalRoundness for component corners (normalized 0.0-1.0)
adaptiveSizebooleanfalseOptionalWhether the component stretches to fill its container
size"xsmall" | "small" | "normal" | "large" | "xlarge"normalOptionalSize of the component
displayMode"scaleToFit" | "fill"scaleToFitOptionalLayout mode for the control
onClickReact.MouseEventHandler—OptionalClick event handler
classNamestring—OptionalAdditional CSS classes
styleReact.CSSProperties—OptionalAdditional inline styles

Basic Usage

The Keys component can be used as a simple visualization or as an interactive control:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function BasicKeys() {
  return <Keys size="large" />;
}

Number of Keys

Configure the number of keys on the keyboard (1-128):

import { Keys } from "@cutoff/audio-ui-react";
 
export default function NumberOfKeys() {
  return (
    <div className="flex gap-4 items-center flex-wrap">
      <Keys nbKeys={25} label="25 Keys" size="large" />
      <Keys nbKeys={61} label="61 Keys (Default)" size="large" />
      <Keys nbKeys={88} label="88 Keys (Full Piano)" size="large" />
    </div>
  );
}

Starting Position

Control which note the keyboard starts with:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function StartingPosition() {
  return (
    <div className="flex gap-4 items-center flex-wrap">
      <Keys nbKeys={25} startKey="C" label="Starting at C" size="large" />
      <Keys nbKeys={25} startKey="A" label="Starting at A" size="large" />
      <Keys nbKeys={25} startKey="F" label="Starting at F" size="large" />
    </div>
  );
}

Octave Transposition

Use the octaveShift prop to transpose the keyboard up or down by octaves:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function OctaveShift() {
  return (
    <div className="flex gap-4 items-center flex-wrap">
      <Keys nbKeys={25} octaveShift={-2} label="2 Octaves Down" size="large" />
      <Keys nbKeys={25} octaveShift={0} label="No Shift (Default)" size="large" />
      <Keys nbKeys={25} octaveShift={2} label="2 Octaves Up" size="large" />
    </div>
  );
}

Note Highlighting

Highlight active notes using the notesOn prop. Notes can be specified as strings (note names) or MIDI note numbers:

Using Note Names

import { Keys } from "@cutoff/audio-ui-react";
 
export default function NoteNames() {
  return (
    <Keys notesOn={["C4", "E4", "G4"]} label="C Major Chord" size="large" />
  );
}

Using MIDI Note Numbers

import { Keys } from "@cutoff/audio-ui-react";
 
export default function MIDINotes() {
  return (
    <Keys notesOn={[60, 64, 67]} label="C Major Chord (MIDI)" size="large" />
  );
}

Mixing Note Names and MIDI Numbers

import { Keys } from "@cutoff/audio-ui-react";
 
export default function MixedNotes() {
  return (
    <Keys notesOn={["C4", 64, "G4"]} label="Mixed Format" size="large" />
  );
}

Interactive Note Highlighting

Update the highlighted notes based on user interaction:

import { useState } from "react";
import { Keys } from "@cutoff/audio-ui-react";
 
export default function InteractiveNotesExample() {
  const [notesOn, setNotesOn] = useState([]);
 
  const handleChange = (e) => {
    const noteNum = e.value.note;
    if (e.value.active) {
      setNotesOn((prev) => [...prev, noteNum]);
    } else {
      setNotesOn((prev) => prev.filter((n) => n !== noteNum));
    }
  };
 
  return (
    <Keys nbKeys={61} notesOn={notesOn} onChange={handleChange} size="large" />
  );
}

Key Styling Modes

The Keys component supports three styling modes:

Theme Mode

The default mode uses theme colors (from the color prop or global theme):

import { Keys } from "@cutoff/audio-ui-react";
 
export default function ThemeMode() {
  return (
    <Keys
      keyStyle="theme"
      color="#3b82f6"
      label="Theme Mode"
      size="large"
    />
  );
}

Classic Mode

Classic piano style with ivory white keys and ebony black keys:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function ClassicMode() {
  return (
    <Keys keyStyle="classic" label="Classic Piano" size="large" />
  );
}

Classic Inverted Mode

Inverted classic style with ebony white keys and ivory black keys:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function ClassicInvertedMode() {
  return (
    <Keys
      keyStyle="classic-inverted"
      label="Inverted Classic"
      size="large"
    />
  );
}

Note:

In classic and classic-inverted modes, active keys always use the theme color for visual feedback, regardless of the key style.

Theming

Vector components share common theming features; see Theming Features in the Vector introduction for an overview. The Keys component supports theming through the color and roundness props:

Color Customization

In theme mode, customize the color of active keys:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function KeysColor() {
  return (
    <div className="flex gap-4 items-center flex-wrap">
      <Keys
        keyStyle="theme"
        color="#3b82f6"
        notesOn={["C4", "E4", "G4"]}
        label="Blue Theme"
        size="large"
      />
      <Keys
        keyStyle="theme"
        color="#ef4444"
        notesOn={["C4", "E4", "G4"]}
        label="Red Theme"
        size="large"
      />
      <Keys
        keyStyle="theme"
        color="#10b981"
        notesOn={["C4", "E4", "G4"]}
        label="Green Theme"
        size="large"
      />
    </div>
  );
}

Roundness

Control the roundness of the key corners:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function KeysRoundness() {
  return (
    <div className="flex gap-4 items-center flex-wrap">
      <Keys roundness={0.0} label="Square Keys" size="large" />
      <Keys roundness={0.5} label="Rounded Keys" size="large" />
      <Keys roundness={1.0} label="Fully Rounded Keys" size="large" />
    </div>
  );
}

Adaptive Sizing

The Keys component supports both fixed sizes and adaptive sizing. By default, the size of the component is driven by the Size System and their size attribute:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function KeysSizing() {
  return (
    <div className="flex gap-4 items-center flex-wrap">
      <Keys nbKeys={25} size="small" label="Small" />
      <Keys nbKeys={25} size="normal" label="Normal" />
      <Keys nbKeys={25} size="large" label="Large" />
      <Keys nbKeys={25} size="xlarge" label="XLarge" />
    </div>
  );
}

Adaptive sizing allows the component to adapt to the size of its parent container, adjusting its dimensions intelligently without causing distortion (scaleToFit display mode). The fill display mode makes the component fill the entire container, potentially distorting it.

Interaction

The Keys component becomes interactive when you provide the onChange prop:

import { useState } from "react";
import { Keys } from "@cutoff/audio-ui-react";
 
export default function InteractiveKeysExample() {
  const [notesOn, setNotesOn] = useState([]);
 
  const handleChange = (e) => {
    const noteNum = e.value.note;
    if (e.value.active) {
      setNotesOn((prev) => [...prev, noteNum]);
    } else {
      setNotesOn((prev) => prev.filter((n) => n !== noteNum));
    }
  };
 
  return (
    <Keys
      nbKeys={61}
      notesOn={notesOn}
      onChange={handleChange}
      size="large"
    />
  );
}

The onChange callback receives an event with:

  • value.note: The MIDI note number (0-127)
  • value.active: Whether the note is being pressed (true) or released (false)
  • normalizedValue: The normalized value (0.0-1.0)
  • midiValue: The MIDI note number

Common Patterns

Full Piano Keyboard

A standard 88-key piano keyboard:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function FullPiano() {
  return (
    <Keys nbKeys={88} startKey="A" label="Full Piano" size="large" />
  );
}

Compact Keyboard

A compact 25-key keyboard for mobile or space-constrained interfaces:

import { Keys } from "@cutoff/audio-ui-react";
 
export default function CompactKeyboard() {
  return (
    <Keys nbKeys={25} startKey="C" label="Compact" size="large" />
  );
}

MIDI Monitor

A keyboard that highlights notes received from MIDI input. In a real app you would connect this to MIDI input; here it shows a static example:

import { useState } from "react";
import { Keys } from "@cutoff/audio-ui-react";
 
export default function MIDIMonitorExample() {
  const [notesOn, setNotesOn] = useState([]);
 
  return (
    <Keys
      nbKeys={61}
      notesOn={notesOn}
      label="MIDI Monitor"
      size="large"
    />
  );
}

Chord Display

Display a chord by highlighting specific notes:

import { useState } from "react";
import { Keys } from "@cutoff/audio-ui-react";
 
const C_MAJOR = ["C4", "E4", "G4"];
const A_MINOR = ["A3", "C4", "E4"];
const F_MAJOR = ["F3", "A3", "C4"];
 
export default function ChordDisplayExample() {
  const [chord, setChord] = useState(C_MAJOR);
 
  return (
    <div className="flex flex-col gap-4">
      <div className="flex gap-2">
        <button
          type="button"
          onClick={() => setChord(C_MAJOR)}
          className="rounded border px-2 py-1 text-sm"
        >
          C Major
        </button>
        <button
          type="button"
          onClick={() => setChord(A_MINOR)}
          className="rounded border px-2 py-1 text-sm"
        >
          A Minor
        </button>
        <button
          type="button"
          onClick={() => setChord(F_MAJOR)}
          className="rounded border px-2 py-1 text-sm"
        >
          F Major
        </button>
      </div>
      <Keys nbKeys={61} notesOn={chord} label="Chord Display" size="large" />
    </div>
  );
}

Layout Considerations

The Keys component maintains proper piano key layout and proportions regardless of the number of keys or starting position. The component:

  • Automatically calculates white and black key positions
  • Maintains correct spacing between keys
  • Handles partial octaves correctly
  • Supports any number of keys from 1 to 128

The component uses SVG for rendering, ensuring crisp display at any size and proper scaling.

Common Use Cases

MIDI Keyboard Visualization

import { useState } from "react";
import { Keys } from "@cutoff/audio-ui-react";
 
export default function MIDIKeyboardExample() {
  const [activeNotes, setActiveNotes] = useState([]);
 
  const handleNoteChange = (e) => {
    if (e.value.active) {
      setActiveNotes((prev) => [...prev, e.value.note]);
    } else {
      setActiveNotes((prev) => prev.filter((n) => n !== e.value.note));
    }
  };
 
  return (
    <Keys
      nbKeys={61}
      notesOn={activeNotes}
      onChange={handleNoteChange}
      keyStyle="theme"
      size="large"
    />
  );
}

Piano Roll Display

import { Keys } from "@cutoff/audio-ui-react";
 
export default function PianoRollExample() {
  const playedNotes = [60, 64, 67];
 
  return (
    <Keys
      nbKeys={88}
      startKey="A"
      notesOn={playedNotes}
      keyStyle="classic"
      size="large"
    />
  );
}

Compact Keyboard Control

import { useState } from "react";
import { Keys } from "@cutoff/audio-ui-react";
 
export default function CompactKeyboardControlExample() {
  const [notesOn, setNotesOn] = useState([]);
 
  const handleNoteChange = (e) => {
    if (e.value.active) {
      setNotesOn((prev) => [...prev, e.value.note]);
    } else {
      setNotesOn((prev) => prev.filter((n) => n !== e.value.note));
    }
  };
 
  return (
    <Keys
      nbKeys={25}
      startKey="C"
      octaveShift={-1}
      onChange={handleNoteChange}
      size="large"
    />
  );
}

Next Steps