# Piano

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

`<audio-ui-piano>` is a playable keyboard with `role="group"`. A key sounds while pressed, by pointer or with Enter and Space. Dragging glides across keys, and each pointer holds its own key, so chords can be played on touch screens. The keys share one tab stop and are navigated with the arrow keys, by octave with Shift.

## 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-piano {
    position: relative;
    display: block;
    width: 100%;
    max-width: 32rem;
    height: calc(var(--spacing, 0.25rem) * 28);
  }

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

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

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

<audio-ui-piano aria-label="Keys" min-pitch="48" max-pitch="71">
  <audio-ui-piano-keys></audio-ui-piano-keys>
</audio-ui-piano>

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

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

*With Tailwind*

```html
<audio-ui-piano class="relative block h-28 w-full max-w-lg" aria-label="Keys" min-pitch="48" max-pitch="71">
  <audio-ui-piano-keys class="*:top-0 *:z-0 *:h-full *:cursor-pointer *:rounded-b-[4px] *:border *:border-border *:bg-background *:outline-none *: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>

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

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

**Vue**

*With CSS*

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

<template>
  <audio-ui-piano aria-label="Keys" min-pitch="48" max-pitch="71" @noteon="(event) => console.log(event.detail.pitch)">
    <audio-ui-piano-keys />
  </audio-ui-piano>
</template>

<style>
audio-ui-piano {
  position: relative;
  display: block;
  width: 100%;
  max-width: 32rem;
  height: calc(var(--spacing, 0.25rem) * 28);
}

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

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

audio-ui-piano-key[data-active] {
  border-color: var(--primary, oklch(0.205 0 0));
  background: var(--primary, oklch(0.205 0 0));
}
</style>
```

*With Tailwind*

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

<template>
  <audio-ui-piano class="relative block h-28 w-full max-w-lg" aria-label="Keys" min-pitch="48" max-pitch="71" @noteon="(event) => console.log(event.detail.pitch)">
    <audio-ui-piano-keys class="*:top-0 *:z-0 *:h-full *:cursor-pointer *:rounded-b-[4px] *:border *:border-border *:bg-background *:outline-none *: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>
</template>
```

**Svelte**

*With CSS*

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

<audio-ui-piano aria-label="Keys" min-pitch="48" max-pitch="71" onnoteon={(event) => console.log(event.detail.pitch)}>
  <audio-ui-piano-keys></audio-ui-piano-keys>
</audio-ui-piano>

<style>
:global {
  audio-ui-piano {
    position: relative;
    display: block;
    width: 100%;
    max-width: 32rem;
    height: calc(var(--spacing, 0.25rem) * 28);
  }

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

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

  audio-ui-piano-key[data-active] {
    border-color: var(--primary, oklch(0.205 0 0));
    background: var(--primary, oklch(0.205 0 0));
  }
}
</style>
```

*With Tailwind*

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

<audio-ui-piano class="relative block h-28 w-full max-w-lg" aria-label="Keys" min-pitch="48" max-pitch="71" onnoteon={(event) => console.log(event.detail.pitch)}>
  <audio-ui-piano-keys class="*:top-0 *:z-0 *:h-full *:cursor-pointer *:rounded-b-[4px] *:border *:border-border *:bg-background *:outline-none *: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>
```

**React**

In React, the same control is the [Piano](/docs/react/piano) 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";
```

| Export | Kind | What it is |
| --- | --- | --- |
| `PIANO_TAGS` | const | The names the piano's elements are registered under, frozen: a page cannot rename them by accident. |
| `PianoElement` | class | The root of a playable keyboard, with `role="group"`. |
| `PianoElementEventMap` | interface | The events a piano fires, as `addEventListener` types them. |
| `PianoKeyElement` | class | A key, positioned for its `pitch`. |
| `PianoKeyLabelElement` | class | The name of its key, such as "C4". |
| `PianoKeysElement` | class | Generates an `<audio-ui-piano-key>` per pitch, lowest first, filled from its `<template>`, such as with an `<audio-ui-piano-key-label>`. |
| `PianoNoteOffDetail` | interface | What a `noteoff` event carries: the pitch released, and why. |
| `PianoNoteOffEvent` | type | Fired when a key stops sounding. |
| `PianoNoteOnDetail` | interface | What a `noteon` event carries: the pitch played, how hard, and how. |
| `PianoNoteOnEvent` | type | Fired when a key starts sounding. |

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `min-pitch` | `number` | `48` | Lowest MIDI pitch, inclusive. 60 is middle C. |
| `max-pitch` | `number` | `72` | Highest MIDI pitch, inclusive. |
| `pitches` | `string` |  | Restricts the keys to these pitches, separated by spaces. The `pitches` property accepts an array. |
| `active-notes` | `string` |  | Pitches lit externally, such as by MIDI input. The `activeNotes` property accepts an array. |
| `layout` | `"keyboard" \| "even"` | `"keyboard"` | Key sizes: piano proportions, or equal shares as on a piano roll. |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Vertical places the low end at the bottom. |
| `disabled` | `boolean` |  |  |

| Event | Detail | Description |
| --- | --- | --- |
| `noteon` | `{ pitch, velocity, reason, event }` | A key starts sounding. `event` is the originating pointer or keyboard event. `preventDefault()` keeps it silent |
| `noteoff` | `{ pitch, reason, event }` | A key stops sounding |

| Part | Description |
| --- | --- |
| `<audio-ui-piano-keys>` | Generates an `<audio-ui-piano-key>` per pitch, lowest first, from its `<template>`. Renders with `display: contents` |
| `<audio-ui-piano-key>` | A key, a button positioned for its `pitch` and labelled with its name ("C4"). `data-color` is `black` or `white` |
| `<audio-ui-piano-key-label>` | The name of its key, hidden from assistive technology |

**In React**

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

