# @suaveplan/react-midi - React 19 adapter for the Web MIDI API: , hooks (useMidi, useMidiInput, useMidiOutput, useMidiMessages, useMidiDevices, useNRPN, useRPN, useCC14), and small UI primitives (MidiDeviceList, MidiActivityIndicator). React 19 adapter for the Web MIDI API. Provides a strict-mode-safe `` that requests `MIDIAccess` exactly once, seven typed hooks for common consumption patterns, and two small UI primitives (``, ``). ## Documentation tree - [midi-activity-indicator](./src/components/midi-activity-indicator/midi-activity-indicator.md) — `` is a small visual primitive that "blinks" on inbound MIDI traffic. It subscribes to the selec… - [midi-activity-indicator](./src/components/midi-activity-indicator/midi-activity-indicator.md) — `` is a small visual primitive that "blinks" on inbound MIDI traffic. It subscribes to the selec… - [midi-device-list](./src/components/midi-device-list/midi-device-list.md) — `` is a small, accessible list of the available MIDI devices for a chosen kind (inputs or outputs). It… - [midi-device-list](./src/components/midi-device-list/midi-device-list.md) — `` is a small, accessible list of the available MIDI devices for a chosen kind (inputs or outputs). It… - [error-codes](./src/error-codes/error-codes.md) — Defines the stable `REACT_MIDI.*` error-code namespace published by `@suaveplan/react-midi`. Every code is generated vi… - [error-codes](./src/error-codes/error-codes.md) — Defines the stable `REACT_MIDI.*` error-code namespace published by `@suaveplan/react-midi`. Every code is generated vi… - [hooks](./src/hooks/use-midi-context.md) — Internal helper that returns the live `MidiContextValue` for the nearest `` ancestor. It throws `ReactMid… - [use-cc14](./src/hooks/use-cc14/use-cc14.md) — `useCC14(selector)` exposes the consolidated 14-bit CC change for the selected input. Controllers that need finer resol… - [use-cc14](./src/hooks/use-cc14/use-cc14.md) — `useCC14(selector)` exposes the consolidated 14-bit CC change for the selected input. Controllers that need finer resol… - [use-midi](./src/hooks/use-midi/use-midi.md) — `useMidi()` is the most general hook in `@suaveplan/react-midi`. It returns the live `MidiContextValue` surface as `{ s… - [use-midi](./src/hooks/use-midi/use-midi.md) — `useMidi()` is the most general hook in `@suaveplan/react-midi`. It returns the live `MidiContextValue` surface as `{ s… - [use-midi-devices](./src/hooks/use-midi-devices/use-midi-devices.md) — `useMidiDevices()` returns the live `{ inputs, outputs }` device-list view, re-rendering whenever the provider's `state… - [use-midi-devices](./src/hooks/use-midi-devices/use-midi-devices.md) — `useMidiDevices()` returns the live `{ inputs, outputs }` device-list view, re-rendering whenever the provider's `state… - [use-midi-input](./src/hooks/use-midi-input/use-midi-input.md) — `useMidiInput(selector)` returns a typed `MidiPortLike` adapter over a single Web MIDI input. The adapter parses raw by… - [use-midi-input](./src/hooks/use-midi-input/use-midi-input.md) — `useMidiInput(selector)` returns a typed `MidiPortLike` adapter over a single Web MIDI input. The adapter parses raw by… - [use-midi-messages](./src/hooks/use-midi-messages/use-midi-messages.md) — `useMidiMessages(selector, options?)` exposes a bounded ring buffer of the most recent parsed MIDI messages from the se… - [use-midi-messages](./src/hooks/use-midi-messages/use-midi-messages.md) — `useMidiMessages(selector, options?)` exposes a bounded ring buffer of the most recent parsed MIDI messages from the se… - [use-midi-output](./src/hooks/use-midi-output/use-midi-output.md) — `useMidiOutput(selector)` returns a typed handle for a single Web MIDI output. The handle exposes both `send(bytes)` (r… - [use-midi-output](./src/hooks/use-midi-output/use-midi-output.md) — `useMidiOutput(selector)` returns a typed handle for a single Web MIDI output. The handle exposes both `send(bytes)` (r… - [use-nrpn](./src/hooks/use-nrpn/use-nrpn.md) — `useNRPN(selector)` wraps the universal NRPN reducer from the `midi-contract` module and exposes the consolidated `NRPN… - [use-nrpn](./src/hooks/use-nrpn/use-nrpn.md) — `useNRPN(selector)` wraps the universal NRPN reducer from the `midi-contract` module and exposes the consolidated `NRPN… - [use-rpn](./src/hooks/use-rpn/use-rpn.md) — `useRPN(selector)` wraps the universal RPN reducer and exposes the consolidated `RPNChange` as React state. RPN ("Regis… - [use-rpn](./src/hooks/use-rpn/use-rpn.md) — `useRPN(selector)` wraps the universal RPN reducer and exposes the consolidated `RPNChange` as React state. RPN ("Regis… - [use-midi-context](./src/hooks/use-midi-context.md) — Internal helper that returns the live `MidiContextValue` for the nearest `` ancestor. It throws `ReactMid… - [codec](./src/midi-contract/codec.md) — `codec.ts` provides minimal `decodeMessage` / `encodeMessage` helpers for the MIDI 1.0 wire format used internally by `… - [midi-message](./src/midi-contract/midi-message.md) — Defines the local mirror of the `@suaveplan/midi` `MidiMessage` discriminated union plus its peer types (`Channel`, `Mi… - [reducers](./src/midi-contract/reducers.md) — Three small reducers that fold the four-CC NRPN, RPN, and 14-bit CC "dances" into single consolidated events. The Web M… - [port](./src/port/web-midi-port.md) — Bridges the native Web MIDI `WebMIDIInput` / `WebMIDIOutput` shapes into the structural `MidiPortLike` consumed by the… - [web-midi-port](./src/port/web-midi-port.md) — Bridges the native Web MIDI `WebMIDIInput` / `WebMIDIOutput` shapes into the structural `MidiPortLike` consumed by the… - [midi-context](./src/provider/midi-context.md) — Defines the two React contexts that wire `` to its descendant hooks. `MidiReactContext` carries the resol… - [midi-provider](./src/provider/midi-provider.md) — `` is the root context provider for `@suaveplan/react-midi`. It calls `navigator.requestMIDIAccess({ syse… - [telemetry](./src/telemetry/telemetry.md) — Owns the two OTel counters published by `@suaveplan/react-midi`: `react_midi_messages_received_total` for parsed inboun… - [telemetry](./src/telemetry/telemetry.md) — Owns the two OTel counters published by `@suaveplan/react-midi`: `react_midi_messages_received_total` for parsed inboun… - [test-setup](./src/test-setup.md) — Per-package Vitest bootstrap loaded via `setupFiles` before the first test in a worker runs. Its only job is to registe… - [test-utils](./src/test-utils.md) — Provides the deterministic in-memory implementations of the Web MIDI shape used throughout the `@suaveplan/react-midi`… ## Install ```bash bun add @suaveplan/react-midi ``` Peer deps: `react@^19`, `react-dom@^19`. ## Permission model Web MIDI is gated behind `navigator.requestMIDIAccess`. `` calls it once on mount and surfaces the outcome as a `status`: - `loading` — request in flight. - `granted` — `MIDIAccess` resolved. - `denied` — user rejected, or the runtime threw (e.g. `NotAllowedError`). - `unsupported` — `navigator.requestMIDIAccess` is undefined (Firefox, sandboxed iframes, etc.). SysEx access is opt-in via ``. The provider is StrictMode-safe — it tracks an in-flight ref so React's double-mount never produces two parallel requests. ## Quickstart ```tsx import { MidiProvider, useMidi, useMidiInput, MidiDeviceList, } from "@suaveplan/react-midi"; function Workspace() { const { status } = useMidi(); if (status === "loading") return

Connecting MIDI…

; if (status === "denied") return

MIDI permission denied.

; if (status === "unsupported") return

MIDI not available in this browser.

; return ( <> ); } function NoteLogger() { const port = useMidiInput("first"); useEffect(() => { if (port === null) return; return port.onMessage((msg) => { if (msg.type === "noteOn") console.log("note", msg.note); }); }, [port]); return null; } export default function App() { return ( ); } ``` ## Device-picker example ```tsx function DevicePicker() { const [id, setId] = useState(null); return ( <> {id !== null ? : null} ); } ``` ## Hooks reference - `useMidi()` — `{ status, access, inputs, outputs, error }`. - `useMidiInput(id | "first")` — typed `MidiPortLike` with parsed `MidiMessage` events. - `useMidiOutput(id | "first")` — typed output handle with `sendMessage(msg)` (encoded) and raw `send(bytes)`. - `useMidiMessages(id, { capacity?, filter? })` — ring buffer of recent messages for log/debug UIs. - `useMidiDevices()` — `{ inputs, outputs }`, re-renders on `statechange`. - `useNRPN(id)`, `useRPN(id)`, `useCC14(id)` — high-resolution reducers folded into React state. ## Error codes `REACT_MIDI.{ACCESS_DENIED, UNSUPPORTED_PLATFORM, PORT_NOT_FOUND, SYSEX_NOT_PERMITTED, NO_PROVIDER}`. Thrown as `ReactMidiError` instances; permission failures are surfaced as a `status` value rather than thrown so consumers can render friendly fallbacks. ## Telemetry Two OTel counters tagged with `port`: `react_midi_messages_received_total{port}` and `react_midi_messages_sent_total{port}`. ## On the `@suaveplan/midi` dependency This package ships a local mirror of the `@suaveplan/midi` `MidiMessage` contract in `src/midi-contract/`. Once `@suaveplan/midi` graduates to `workspace:^`, the local mirror is replaced by a re-export from that package without any shape change.