# Piano roll

> The <audio-ui-piano-roll> custom element of @audio-ui/elements: its attributes, its parts and a live preview.

`<audio-ui-piano-roll>` is a note editor placed inside an `<audio-ui-timeline>`, which provides its length, zoom, grid and position. Its entry point also registers the timeline and piano elements. Notes are set through the `value` property as `{ id, pitch, start, length, velocity?, chance?, muted? }[]`, and the selection through the `selection` property. Rows, notes and velocity bars are generated from them:

## Usage

{/* usage:start: written by `bun scripts/element-usage.ts --write` */}

The styles read the theme's CSS variables, which [Styling](/docs/elements#styling) sets.

**JavaScript**

*With CSS*

```html
<style>
  audio-ui-timeline {
    display: block;
    width: 100%;
    max-width: 36rem;
  }

  audio-ui-piano-roll {
    display: grid;
    grid-template-columns: 3rem 1fr;
    overflow: hidden;
    border: 1px solid var(--border, oklch(0.922 0 0));
    border-radius: calc(var(--radius, 0.625rem) - 2px);
  }

  audio-ui-piano-roll-keys {
    height: calc(var(--spacing, 0.25rem) * 48);
    border-inline-end: 1px solid var(--border, oklch(0.922 0 0));
  }

  audio-ui-piano-roll-keys audio-ui-piano {
    display: block;
    height: 100%;
  }

  audio-ui-piano-roll-keys audio-ui-piano-key {
    z-index: 0;
    top: auto;
    right: 0;
    left: 0;
    box-sizing: border-box;
    height: 100%;
    border: 0;
    border-bottom: 1px solid var(--border, oklch(0.922 0 0));
    border-radius: 0;
    background: var(--background, oklch(1 0 0));
    cursor: pointer;
    outline: none;
  }

  audio-ui-piano-roll-keys audio-ui-piano-key[data-color="black"] {
    z-index: 1;
    right: 35%;
    height: 62%;
    border-color: var(--foreground, oklch(0.145 0 0));
    background: var(--foreground, oklch(0.145 0 0));
  }

  audio-ui-piano-roll-keys audio-ui-piano-key[data-active] {
    border-color: var(--primary, oklch(0.205 0 0));
    background: var(--primary, oklch(0.205 0 0));
  }

  audio-ui-piano-roll-grid {
    height: calc(var(--spacing, 0.25rem) * 48);
    outline: none;
  }

  audio-ui-piano-roll-row[data-color="black"] {
    background: color-mix(in oklab, var(--muted, oklch(0.97 0 0)) 70%, transparent);
  }

  audio-ui-piano-roll-row[data-octave] {
    border-bottom: 1px solid var(--border, oklch(0.922 0 0));
  }

  audio-ui-piano-roll-note {
    border-radius: 2px;
    background: color-mix(
      in oklab,
      var(--primary, oklch(0.205 0 0)) calc(40% + 60% * var(--piano-roll-velocity)),
      transparent
    );
    cursor: grab;
    outline: none;
  }

  audio-ui-piano-roll-note[data-selected] {
    box-shadow: 0 0 0 2px var(--ring, oklch(0.708 0 0));
  }

  audio-ui-piano-roll-marquee {
    border: 1px dashed var(--primary, oklch(0.205 0 0));
    background: color-mix(in oklab, var(--primary, oklch(0.205 0 0)) 8%, transparent);
  }

  audio-ui-piano-roll-velocity-lane {
    grid-column-start: 2;
    height: calc(var(--spacing, 0.25rem) * 12);
    border-top: 1px solid var(--border, oklch(0.922 0 0));
  }

  audio-ui-piano-roll-velocity {
    width: 3px;
    translate: -1px 0;
    background: var(--primary, oklch(0.205 0 0));
  }

  audio-ui-timeline-viewport {
    scrollbar-width: none;
  }

  audio-ui-timeline-playhead {
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--foreground, oklch(0.145 0 0));
  }
</style>

<audio-ui-timeline duration="8" snap-step="0.5">
  <audio-ui-piano-roll min-pitch="57" max-pitch="74">
    <audio-ui-piano-roll-keys>
      <audio-ui-piano aria-label="Keys">
        <audio-ui-piano-keys></audio-ui-piano-keys>
      </audio-ui-piano>
    </audio-ui-piano-roll-keys>
    <audio-ui-timeline-viewport>
      <audio-ui-piano-roll-grid aria-label="Notes">
        <audio-ui-piano-roll-rows></audio-ui-piano-roll-rows>
        <audio-ui-piano-roll-notes></audio-ui-piano-roll-notes>
        <audio-ui-timeline-playhead></audio-ui-timeline-playhead>
      </audio-ui-piano-roll-grid>
    </audio-ui-timeline-viewport>
  </audio-ui-piano-roll>
</audio-ui-timeline>

<script type="module">
  import "@audio-ui/elements/piano-roll";

  const pianoRoll = document.querySelector("audio-ui-piano-roll");
  pianoRoll.addEventListener("change", (event) =>
    console.log(event.detail.value)
  );
</script>
```

*With Tailwind*

```html
<audio-ui-timeline class="block w-full max-w-xl" duration="8" snap-step="0.5">
  <audio-ui-piano-roll class="grid grid-cols-[3rem_1fr] overflow-hidden rounded-md border" min-pitch="57" max-pitch="74">
    <audio-ui-piano-roll-keys class="h-48 border-e">
      <audio-ui-piano class="block h-full" aria-label="Keys">
        <audio-ui-piano-keys class="*:inset-x-0 *:top-auto *:z-0 *:h-full *:cursor-pointer *:border-0 *:border-border *:border-b *:bg-background *:outline-none *:data-[color=black]:right-[35%] *:data-[color=black]:z-1 *:data-[color=black]:h-[62%] *:data-[color=black]:border-foreground *:data-[color=black]:bg-foreground *:data-active:border-primary *:data-active:bg-primary"></audio-ui-piano-keys>
      </audio-ui-piano>
    </audio-ui-piano-roll-keys>
    <audio-ui-timeline-viewport class="[scrollbar-width:none]">
      <audio-ui-piano-roll-grid class="h-48 outline-none" aria-label="Notes">
        <audio-ui-piano-roll-rows class="*:data-[color=black]:bg-muted/70 *:data-octave:border-b"></audio-ui-piano-roll-rows>
        <audio-ui-piano-roll-notes class="*:cursor-grab *:rounded-[2px] *:bg-[color-mix(in_oklab,var(--primary)_calc(40%+60%*var(--piano-roll-velocity)),transparent)] *:outline-none *:data-selected:ring-2 *:data-selected:ring-ring"></audio-ui-piano-roll-notes>
        <audio-ui-timeline-playhead class="inset-y-0 w-px bg-foreground"></audio-ui-timeline-playhead>
      </audio-ui-piano-roll-grid>
    </audio-ui-timeline-viewport>
  </audio-ui-piano-roll>
</audio-ui-timeline>

<script type="module">
  import "@audio-ui/elements/piano-roll";

  const pianoRoll = document.querySelector("audio-ui-piano-roll");
  pianoRoll.addEventListener("change", (event) =>
    console.log(event.detail.value)
  );
</script>
```

**Vue**

*With CSS*

```vue
<script setup lang="ts">
import "@audio-ui/elements/piano-roll";
</script>

<template>
  <audio-ui-timeline duration="8" snap-step="0.5">
    <audio-ui-piano-roll min-pitch="57" max-pitch="74" @change="(event) => console.log(event.detail.value)">
      <audio-ui-piano-roll-keys>
        <audio-ui-piano aria-label="Keys">
          <audio-ui-piano-keys />
        </audio-ui-piano>
      </audio-ui-piano-roll-keys>
      <audio-ui-timeline-viewport>
        <audio-ui-piano-roll-grid aria-label="Notes">
          <audio-ui-piano-roll-rows />
          <audio-ui-piano-roll-notes />
          <audio-ui-timeline-playhead />
        </audio-ui-piano-roll-grid>
      </audio-ui-timeline-viewport>
    </audio-ui-piano-roll>
  </audio-ui-timeline>
</template>

<style>
audio-ui-timeline {
  display: block;
  width: 100%;
  max-width: 36rem;
}

audio-ui-piano-roll {
  display: grid;
  grid-template-columns: 3rem 1fr;
  overflow: hidden;
  border: 1px solid var(--border, oklch(0.922 0 0));
  border-radius: calc(var(--radius, 0.625rem) - 2px);
}

audio-ui-piano-roll-keys {
  height: calc(var(--spacing, 0.25rem) * 48);
  border-inline-end: 1px solid var(--border, oklch(0.922 0 0));
}

audio-ui-piano-roll-keys audio-ui-piano {
  display: block;
  height: 100%;
}

audio-ui-piano-roll-keys audio-ui-piano-key {
  z-index: 0;
  top: auto;
  right: 0;
  left: 0;
  box-sizing: border-box;
  height: 100%;
  border: 0;
  border-bottom: 1px solid var(--border, oklch(0.922 0 0));
  border-radius: 0;
  background: var(--background, oklch(1 0 0));
  cursor: pointer;
  outline: none;
}

audio-ui-piano-roll-keys audio-ui-piano-key[data-color="black"] {
  z-index: 1;
  right: 35%;
  height: 62%;
  border-color: var(--foreground, oklch(0.145 0 0));
  background: var(--foreground, oklch(0.145 0 0));
}

audio-ui-piano-roll-keys audio-ui-piano-key[data-active] {
  border-color: var(--primary, oklch(0.205 0 0));
  background: var(--primary, oklch(0.205 0 0));
}

audio-ui-piano-roll-grid {
  height: calc(var(--spacing, 0.25rem) * 48);
  outline: none;
}

audio-ui-piano-roll-row[data-color="black"] {
  background: color-mix(in oklab, var(--muted, oklch(0.97 0 0)) 70%, transparent);
}

audio-ui-piano-roll-row[data-octave] {
  border-bottom: 1px solid var(--border, oklch(0.922 0 0));
}

audio-ui-piano-roll-note {
  border-radius: 2px;
  background: color-mix(
    in oklab,
    var(--primary, oklch(0.205 0 0)) calc(40% + 60% * var(--piano-roll-velocity)),
    transparent
  );
  cursor: grab;
  outline: none;
}

audio-ui-piano-roll-note[data-selected] {
  box-shadow: 0 0 0 2px var(--ring, oklch(0.708 0 0));
}

audio-ui-piano-roll-marquee {
  border: 1px dashed var(--primary, oklch(0.205 0 0));
  background: color-mix(in oklab, var(--primary, oklch(0.205 0 0)) 8%, transparent);
}

audio-ui-piano-roll-velocity-lane {
  grid-column-start: 2;
  height: calc(var(--spacing, 0.25rem) * 12);
  border-top: 1px solid var(--border, oklch(0.922 0 0));
}

audio-ui-piano-roll-velocity {
  width: 3px;
  translate: -1px 0;
  background: var(--primary, oklch(0.205 0 0));
}

audio-ui-timeline-viewport {
  scrollbar-width: none;
}

audio-ui-timeline-playhead {
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--foreground, oklch(0.145 0 0));
}
</style>
```

*With Tailwind*

```vue
<script setup lang="ts">
import "@audio-ui/elements/piano-roll";
</script>

<template>
  <audio-ui-timeline class="block w-full max-w-xl" duration="8" snap-step="0.5">
    <audio-ui-piano-roll class="grid grid-cols-[3rem_1fr] overflow-hidden rounded-md border" min-pitch="57" max-pitch="74" @change="(event) => console.log(event.detail.value)">
      <audio-ui-piano-roll-keys class="h-48 border-e">
        <audio-ui-piano class="block h-full" aria-label="Keys">
          <audio-ui-piano-keys class="*:inset-x-0 *:top-auto *:z-0 *:h-full *:cursor-pointer *:border-0 *:border-border *:border-b *:bg-background *:outline-none *:data-[color=black]:right-[35%] *:data-[color=black]:z-1 *:data-[color=black]:h-[62%] *:data-[color=black]:border-foreground *:data-[color=black]:bg-foreground *:data-active:border-primary *:data-active:bg-primary" />
        </audio-ui-piano>
      </audio-ui-piano-roll-keys>
      <audio-ui-timeline-viewport class="[scrollbar-width:none]">
        <audio-ui-piano-roll-grid class="h-48 outline-none" aria-label="Notes">
          <audio-ui-piano-roll-rows class="*:data-[color=black]:bg-muted/70 *:data-octave:border-b" />
          <audio-ui-piano-roll-notes class="*:cursor-grab *:rounded-[2px] *:bg-[color-mix(in_oklab,var(--primary)_calc(40%+60%*var(--piano-roll-velocity)),transparent)] *:outline-none *:data-selected:ring-2 *:data-selected:ring-ring" />
          <audio-ui-timeline-playhead class="inset-y-0 w-px bg-foreground" />
        </audio-ui-piano-roll-grid>
      </audio-ui-timeline-viewport>
    </audio-ui-piano-roll>
  </audio-ui-timeline>
</template>
```

**Svelte**

*With CSS*

```svelte
<script lang="ts">
  import "@audio-ui/elements/piano-roll";
</script>

<audio-ui-timeline duration="8" snap-step="0.5">
  <audio-ui-piano-roll min-pitch="57" max-pitch="74" onchange={(event) => console.log(event.detail.value)}>
    <audio-ui-piano-roll-keys>
      <audio-ui-piano aria-label="Keys">
        <audio-ui-piano-keys></audio-ui-piano-keys>
      </audio-ui-piano>
    </audio-ui-piano-roll-keys>
    <audio-ui-timeline-viewport>
      <audio-ui-piano-roll-grid aria-label="Notes">
        <audio-ui-piano-roll-rows></audio-ui-piano-roll-rows>
        <audio-ui-piano-roll-notes></audio-ui-piano-roll-notes>
        <audio-ui-timeline-playhead></audio-ui-timeline-playhead>
      </audio-ui-piano-roll-grid>
    </audio-ui-timeline-viewport>
  </audio-ui-piano-roll>
</audio-ui-timeline>

<style>
:global {
  audio-ui-timeline {
    display: block;
    width: 100%;
    max-width: 36rem;
  }

  audio-ui-piano-roll {
    display: grid;
    grid-template-columns: 3rem 1fr;
    overflow: hidden;
    border: 1px solid var(--border, oklch(0.922 0 0));
    border-radius: calc(var(--radius, 0.625rem) - 2px);
  }

  audio-ui-piano-roll-keys {
    height: calc(var(--spacing, 0.25rem) * 48);
    border-inline-end: 1px solid var(--border, oklch(0.922 0 0));
  }

  audio-ui-piano-roll-keys audio-ui-piano {
    display: block;
    height: 100%;
  }

  audio-ui-piano-roll-keys audio-ui-piano-key {
    z-index: 0;
    top: auto;
    right: 0;
    left: 0;
    box-sizing: border-box;
    height: 100%;
    border: 0;
    border-bottom: 1px solid var(--border, oklch(0.922 0 0));
    border-radius: 0;
    background: var(--background, oklch(1 0 0));
    cursor: pointer;
    outline: none;
  }

  audio-ui-piano-roll-keys audio-ui-piano-key[data-color="black"] {
    z-index: 1;
    right: 35%;
    height: 62%;
    border-color: var(--foreground, oklch(0.145 0 0));
    background: var(--foreground, oklch(0.145 0 0));
  }

  audio-ui-piano-roll-keys audio-ui-piano-key[data-active] {
    border-color: var(--primary, oklch(0.205 0 0));
    background: var(--primary, oklch(0.205 0 0));
  }

  audio-ui-piano-roll-grid {
    height: calc(var(--spacing, 0.25rem) * 48);
    outline: none;
  }

  audio-ui-piano-roll-row[data-color="black"] {
    background: color-mix(in oklab, var(--muted, oklch(0.97 0 0)) 70%, transparent);
  }

  audio-ui-piano-roll-row[data-octave] {
    border-bottom: 1px solid var(--border, oklch(0.922 0 0));
  }

  audio-ui-piano-roll-note {
    border-radius: 2px;
    background: color-mix(
      in oklab,
      var(--primary, oklch(0.205 0 0)) calc(40% + 60% * var(--piano-roll-velocity)),
      transparent
    );
    cursor: grab;
    outline: none;
  }

  audio-ui-piano-roll-note[data-selected] {
    box-shadow: 0 0 0 2px var(--ring, oklch(0.708 0 0));
  }

  audio-ui-piano-roll-marquee {
    border: 1px dashed var(--primary, oklch(0.205 0 0));
    background: color-mix(in oklab, var(--primary, oklch(0.205 0 0)) 8%, transparent);
  }

  audio-ui-piano-roll-velocity-lane {
    grid-column-start: 2;
    height: calc(var(--spacing, 0.25rem) * 12);
    border-top: 1px solid var(--border, oklch(0.922 0 0));
  }

  audio-ui-piano-roll-velocity {
    width: 3px;
    translate: -1px 0;
    background: var(--primary, oklch(0.205 0 0));
  }

  audio-ui-timeline-viewport {
    scrollbar-width: none;
  }

  audio-ui-timeline-playhead {
    top: 0;
    bottom: 0;
    width: 1px;
    background: var(--foreground, oklch(0.145 0 0));
  }
}
</style>
```

*With Tailwind*

```svelte
<script lang="ts">
  import "@audio-ui/elements/piano-roll";
</script>

<audio-ui-timeline class="block w-full max-w-xl" duration="8" snap-step="0.5">
  <audio-ui-piano-roll class="grid grid-cols-[3rem_1fr] overflow-hidden rounded-md border" min-pitch="57" max-pitch="74" onchange={(event) => console.log(event.detail.value)}>
    <audio-ui-piano-roll-keys class="h-48 border-e">
      <audio-ui-piano class="block h-full" aria-label="Keys">
        <audio-ui-piano-keys class="*:inset-x-0 *:top-auto *:z-0 *:h-full *:cursor-pointer *:border-0 *:border-border *:border-b *:bg-background *:outline-none *:data-[color=black]:right-[35%] *:data-[color=black]:z-1 *:data-[color=black]:h-[62%] *:data-[color=black]:border-foreground *:data-[color=black]:bg-foreground *:data-active:border-primary *:data-active:bg-primary"></audio-ui-piano-keys>
      </audio-ui-piano>
    </audio-ui-piano-roll-keys>
    <audio-ui-timeline-viewport class="[scrollbar-width:none]">
      <audio-ui-piano-roll-grid class="h-48 outline-none" aria-label="Notes">
        <audio-ui-piano-roll-rows class="*:data-[color=black]:bg-muted/70 *:data-octave:border-b"></audio-ui-piano-roll-rows>
        <audio-ui-piano-roll-notes class="*:cursor-grab *:rounded-[2px] *:bg-[color-mix(in_oklab,var(--primary)_calc(40%+60%*var(--piano-roll-velocity)),transparent)] *:outline-none *:data-selected:ring-2 *:data-selected:ring-ring"></audio-ui-piano-roll-notes>
        <audio-ui-timeline-playhead class="inset-y-0 w-px bg-foreground"></audio-ui-timeline-playhead>
      </audio-ui-piano-roll-grid>
    </audio-ui-timeline-viewport>
  </audio-ui-piano-roll>
</audio-ui-timeline>
```

**React**

In React, the same control is the [PianoRoll](/docs/react/piano-roll) primitive of `@audio-ui/react`, with the same parts as React components and its value as props.

{/* usage:end */}

## API reference

```bash
npm install @audio-ui/elements
```

```ts
import "@audio-ui/elements/piano-roll";
```

| Export | Kind | What it is |
| --- | --- | --- |
| `PIANO_ROLL_TAGS` | const | The names the piano roll's elements are registered under, frozen: a page cannot rename them by accident. |
| `PianoRollElement` | class | The root of a note editor, placed inside an `<audio-ui-timeline>`, which provides its length, zoom, grid and position. |
| `PianoRollElementEventMap` | interface | The events a piano roll fires, as `addEventListener` types them. |
| `PianoRollEventDetail` | interface | What a roll's `input` and `change` events carry, besides the roll itself as their target. |
| `PianoRollGridElement` | class | The note surface: a focusable timeline lane with `role="group"`, with the rows stacked vertically. |
| `PianoRollKeysElement` | class | Lays out the `<audio-ui-piano>` it contains on the roll's rows, low end at the bottom, and lights the sounding pitches. |
| `PianoRollMarqueeElement` | class | The selection rectangle during a marquee drag. |
| `PianoRollNoteElement` | class | A note, positioned at its start in timeline units and on its pitch's row. |
| `PianoRollNoteHandleElement` | class | Resizes its note from its `edge` (`start` or `end`): the end changes the length, the start trims from the front with the end fixed. |
| `PianoRollNotesElement` | class | Generates an `<audio-ui-piano-roll-note>` per note in reading order (by start, then highest pitch first), which is also the tab order, from its `<template>`, such as with `<audio-ui-piano-roll-note-handle>`s. |
| `PianoRollRowElement` | class | The band of a `pitch` across the grid, for row shading, scale highlighting and octave lines. |
| `PianoRollRowsElement` | class | Generates an `<audio-ui-piano-roll-row>` per row, highest pitch first, from its `<template>` when present. |
| `PianoRollSelectionSpanElement` | class | The time range of the selected notes, spanning the height of its container: the grid, the velocity lane or the ruler. |
| `PianoRollStretchHandleElement` | class | An `edge` of the selection span. |
| `PianoRollVelocitiesElement` | class | Generates an `<audio-ui-piano-roll-velocity>` per note, from its `<template>` when present. |
| `PianoRollVelocityElement` | class | A note's velocity, or chance in a `level="chance"` lane, rendered as a bar sized by `--piano-roll-level`. |
| `PianoRollVelocityLaneElement` | class | A timeline lane of per-note bars for velocity or, with `level="chance"`, probability, each at its note's start. |

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `min-pitch` | `number` | `48` | Lowest row, a MIDI pitch. 60 is middle C. |
| `max-pitch` | `number` | `72` | Highest row. |
| `folded` | `string` |  | When present, only pitches with notes get a row; `"scale"` also keeps the scale's pitches. |
| `scale` | `string` |  | The scale's pitch classes, from 0 (C) to 11 (B), such as `0 2 4 5 7 9 11` for a major scale, with `scale-root` (0 for C). Its rows carry `data-in-scale` or `data-out-of-scale`, and those of its root `data-scale-root`. The `scale` property takes `{ pitchClasses, root }`, which `rollScale` of `@audio-ui/engine/pitch` gives for a scale by name. |
| `mode` | `"select" \| "draw"` | `"select"` | Behavior of a press on empty grid: marquee selection or note drawing. |
| `draw-style` | `"stretch" \| "paint"` | `"stretch"` | In draw mode, `paint` draws a note in each grid cell crossed. |
| `min-length` | `number` |  | Minimum note length. Defaults to the timeline's grid step. |
| `active-pitches` | `string` |  | Pitches lit externally, separated by spaces, such as MIDI input or playing notes. |
| `disabled` | `boolean` |  |  |

| Event | Detail | Description |
| --- | --- | --- |
| `input`, `change` | `{ value, reason }` | The notes change, then the gesture ends |
| `noteselect` | `{ selection, reason }` | The selection changes. `preventDefault()` rejects it |
| `noteon`, `noteoff` | `{ pitch, reason, event }` | A pitch starts or stops sounding. `preventDefault()` on `noteon` keeps it silent |

The `getNoteLabel(note)` property sets each note's accessible name, "C4, 4 to 6" by default.

| Part | Description |
| --- | --- |
| `<audio-ui-piano-roll-grid>` | The focusable note surface, a timeline lane. Enter adds a note at the position, Escape clears the selection, Cmd/Ctrl+A selects all, and Cmd/Ctrl+D, C, X, V and Q duplicate, copy, cut, paste and quantize |
| `<audio-ui-piano-roll-rows>` | Generates an `<audio-ui-piano-roll-row>` per pitch, highest first. Rows carry `data-color`, `data-in-scale`, `data-out-of-scale`, `data-scale-root`, `data-octave` and `data-active` |
| `<audio-ui-piano-roll-notes>` | Generates an `<audio-ui-piano-roll-note>` per note in reading order, from its `<template>`. Each note is a toggle button positioned at its start and pitch, movable by pointer and keyboard, exposing `--piano-roll-velocity` and `--piano-roll-chance` |
| `<audio-ui-piano-roll-note-handle>` | Inside a note, resizes it from its `edge` |
| `<audio-ui-piano-roll-marquee>` | The selection rectangle during a marquee drag |
| `<audio-ui-piano-roll-selection-span>` | The time range of the selection; an `<audio-ui-piano-roll-stretch-handle>` per `edge` stretches it |
| `<audio-ui-piano-roll-velocity-lane>` | A lane of per-note bars, through `<audio-ui-piano-roll-velocities>`, for velocity or, with `level="chance"`, probability. Each `<audio-ui-piano-roll-velocity>` is sized by `--piano-roll-level` and contains a native range input |
| `<audio-ui-piano-roll-keys>` | Lays out the `<audio-ui-piano>` it contains on the roll's rows. Keys sound through the roll; Shift + press selects the notes of that row |

**In React**

The same control in React, and its parts' props: [PianoRoll](/docs/react/piano-roll).

