# Sequencer

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

`<audio-ui-sequencer>` is a step grid with one row per sound and `role="group"`. Pressing a cell toggles it, and dragging applies the new state to every cell crossed. The cells share one tab stop and are navigated with the arrow keys. Rows and steps are generated from templates:

## 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-sequencer {
    display: grid;
    gap: calc(var(--spacing, 0.25rem) * 1 * var(--density, 1));
    width: 100%;
    max-width: 36rem;
  }

  audio-ui-sequencer-row {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: calc(var(--spacing, 0.25rem) * 1 * var(--density, 1));
  }

  audio-ui-sequencer-cell {
    aspect-ratio: 1;
    border-radius: calc(var(--radius, 0.625rem) - 4px);
    background: var(--muted, oklch(0.97 0 0));
    outline: none;
  }

  audio-ui-sequencer-cell:focus-visible {
    box-shadow: 0 0 0 2px var(--ring, oklch(0.708 0 0));
  }

  audio-ui-sequencer-cell[data-on] {
    background: var(--primary, oklch(0.205 0 0));
  }
</style>

<audio-ui-sequencer aria-label="Pattern" default-value="x.x. ..x.">
  <audio-ui-sequencer-row index="0" aria-label="Kick">
    <audio-ui-sequencer-cell step="0"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="1"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="2"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="3"></audio-ui-sequencer-cell>
  </audio-ui-sequencer-row>
  <audio-ui-sequencer-row index="1" aria-label="Snare">
    <audio-ui-sequencer-cell step="0"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="1"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="2"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="3"></audio-ui-sequencer-cell>
  </audio-ui-sequencer-row>
</audio-ui-sequencer>

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

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

*With Tailwind*

```html
<audio-ui-sequencer class="grid w-full max-w-xl gap-[calc(var(--spacing)*var(--density,1))]" aria-label="Pattern" default-value="x.x. ..x.">
  <audio-ui-sequencer-row class="grid grid-flow-col auto-cols-fr gap-[calc(var(--spacing)*var(--density,1))]" index="0" aria-label="Kick">
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="0"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="1"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="2"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="3"></audio-ui-sequencer-cell>
  </audio-ui-sequencer-row>
  <audio-ui-sequencer-row class="grid grid-flow-col auto-cols-fr gap-[calc(var(--spacing)*var(--density,1))]" index="1" aria-label="Snare">
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="0"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="1"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="2"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="3"></audio-ui-sequencer-cell>
  </audio-ui-sequencer-row>
</audio-ui-sequencer>

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

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

**Vue**

*With CSS*

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

<template>
  <audio-ui-sequencer aria-label="Pattern" default-value="x.x. ..x." @change="(event) => console.log(event.detail.value)">
    <audio-ui-sequencer-row index="0" aria-label="Kick">
      <audio-ui-sequencer-cell step="0" />
      <audio-ui-sequencer-cell step="1" />
      <audio-ui-sequencer-cell step="2" />
      <audio-ui-sequencer-cell step="3" />
    </audio-ui-sequencer-row>
    <audio-ui-sequencer-row index="1" aria-label="Snare">
      <audio-ui-sequencer-cell step="0" />
      <audio-ui-sequencer-cell step="1" />
      <audio-ui-sequencer-cell step="2" />
      <audio-ui-sequencer-cell step="3" />
    </audio-ui-sequencer-row>
  </audio-ui-sequencer>
</template>

<style>
audio-ui-sequencer {
  display: grid;
  gap: calc(var(--spacing, 0.25rem) * 1 * var(--density, 1));
  width: 100%;
  max-width: 36rem;
}

audio-ui-sequencer-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: calc(var(--spacing, 0.25rem) * 1 * var(--density, 1));
}

audio-ui-sequencer-cell {
  aspect-ratio: 1;
  border-radius: calc(var(--radius, 0.625rem) - 4px);
  background: var(--muted, oklch(0.97 0 0));
  outline: none;
}

audio-ui-sequencer-cell:focus-visible {
  box-shadow: 0 0 0 2px var(--ring, oklch(0.708 0 0));
}

audio-ui-sequencer-cell[data-on] {
  background: var(--primary, oklch(0.205 0 0));
}
</style>
```

*With Tailwind*

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

<template>
  <audio-ui-sequencer class="grid w-full max-w-xl gap-[calc(var(--spacing)*var(--density,1))]" aria-label="Pattern" default-value="x.x. ..x." @change="(event) => console.log(event.detail.value)">
    <audio-ui-sequencer-row class="grid grid-flow-col auto-cols-fr gap-[calc(var(--spacing)*var(--density,1))]" index="0" aria-label="Kick">
      <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="0" />
      <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="1" />
      <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="2" />
      <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="3" />
    </audio-ui-sequencer-row>
    <audio-ui-sequencer-row class="grid grid-flow-col auto-cols-fr gap-[calc(var(--spacing)*var(--density,1))]" index="1" aria-label="Snare">
      <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="0" />
      <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="1" />
      <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="2" />
      <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="3" />
    </audio-ui-sequencer-row>
  </audio-ui-sequencer>
</template>
```

**Svelte**

*With CSS*

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

<audio-ui-sequencer aria-label="Pattern" default-value="x.x. ..x." onchange={(event) => console.log(event.detail.value)}>
  <audio-ui-sequencer-row index="0" aria-label="Kick">
    <audio-ui-sequencer-cell step="0"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="1"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="2"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="3"></audio-ui-sequencer-cell>
  </audio-ui-sequencer-row>
  <audio-ui-sequencer-row index="1" aria-label="Snare">
    <audio-ui-sequencer-cell step="0"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="1"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="2"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell step="3"></audio-ui-sequencer-cell>
  </audio-ui-sequencer-row>
</audio-ui-sequencer>

<style>
:global {
  audio-ui-sequencer {
    display: grid;
    gap: calc(var(--spacing, 0.25rem) * 1 * var(--density, 1));
    width: 100%;
    max-width: 36rem;
  }

  audio-ui-sequencer-row {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: calc(var(--spacing, 0.25rem) * 1 * var(--density, 1));
  }

  audio-ui-sequencer-cell {
    aspect-ratio: 1;
    border-radius: calc(var(--radius, 0.625rem) - 4px);
    background: var(--muted, oklch(0.97 0 0));
    outline: none;
  }

  audio-ui-sequencer-cell:focus-visible {
    box-shadow: 0 0 0 2px var(--ring, oklch(0.708 0 0));
  }

  audio-ui-sequencer-cell[data-on] {
    background: var(--primary, oklch(0.205 0 0));
  }
}
</style>
```

*With Tailwind*

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

<audio-ui-sequencer class="grid w-full max-w-xl gap-[calc(var(--spacing)*var(--density,1))]" aria-label="Pattern" default-value="x.x. ..x." onchange={(event) => console.log(event.detail.value)}>
  <audio-ui-sequencer-row class="grid grid-flow-col auto-cols-fr gap-[calc(var(--spacing)*var(--density,1))]" index="0" aria-label="Kick">
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="0"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="1"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="2"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="3"></audio-ui-sequencer-cell>
  </audio-ui-sequencer-row>
  <audio-ui-sequencer-row class="grid grid-flow-col auto-cols-fr gap-[calc(var(--spacing)*var(--density,1))]" index="1" aria-label="Snare">
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="0"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="1"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="2"></audio-ui-sequencer-cell>
    <audio-ui-sequencer-cell class="aspect-square rounded-sm bg-muted outline-none focus-visible:ring-2 focus-visible:ring-ring data-on:bg-primary" step="3"></audio-ui-sequencer-cell>
  </audio-ui-sequencer-row>
</audio-ui-sequencer>
```

**React**

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

{/* usage:end */}

**In Web and React**

In plain HTML, a row of `<template>` gives the rows and their steps from labels:

```html
<audio-ui-sequencer aria-label="Pattern" default-value="x...x... ..x...x.">
  <audio-ui-sequencer-rows labels="Kick, Snare">
    <template>
      <audio-ui-sequencer-steps>
        <template><audio-ui-sequencer-cell></audio-ui-sequencer-cell></template>
      </audio-ui-sequencer-steps>
    </template>
  </audio-ui-sequencer-rows>
</audio-ui-sequencer>
```

## API reference

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

```ts
import "@audio-ui/elements/sequencer";
```

| Export | Kind | What it is |
| --- | --- | --- |
| `SEQUENCER_TAGS` | const | The names the sequencer's elements are registered under, frozen: a page cannot rename them by accident. |
| `SequencerCellElement` | class | A step: a toggle button, pressed while the step is on (`data-on`). |
| `SequencerChangeEvent` | type | Fired when a press, a paint or a key press that changed the pattern ends. |
| `SequencerElement` | class | The root of a step sequencer, one row per sound, with `role="group"`. |
| `SequencerElementEventMap` | interface | The events a sequencer fires, as `addEventListener` types them. |
| `SequencerEventDetail` | interface | What a sequencer's `input` and `change` events carry. |
| `SequencerInputEvent` | type | Fired while cells are flipped. |
| `SequencerRowElement` | class | A row of steps with `role="group"`, for its `index` from the top. |
| `SequencerRowsElement` | class | Generates an `<audio-ui-sequencer-row>` per pattern row from its `<template>`. |
| `SequencerSource` | interface | A live value followed without rendering: here, the step being played. |
| `SequencerStepElement` | class | A generated step, for its `index`. |
| `SequencerStepsElement` | class | Generates an `<audio-ui-sequencer-step>` per step of its row from its `<template>`: a cell, a velocity bar, or both. |
| `SequencerVelocityChangeEvent` | type | Fired when a drag, a key press or a reset that changed a velocity ends. |
| `SequencerVelocityDetail` | interface | What a sequencer's `velocityinput` and `velocitychange` events carry. |
| `SequencerVelocityElement` | class | A step's velocity, rendered as a bar sized by `--sequencer-velocity`. |
| `SequencerVelocityInputEvent` | type | Fired while a velocity is drawn or nudged. |

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` |  | The pattern as text: one word per row, one character per step, `x` for on and `.` for off. The `value` property reads and sets it as `boolean[][]`. |
| `default-value` | `string` |  | The initial pattern, in the same notation. |
| `disabled` | `boolean` |  |  |

| Event | Detail | Description |
| --- | --- | --- |
| `input`, `change` | `{ value, reason }` | The pattern changes, then the gesture ends |
| `velocityinput`, `velocitychange` | `{ velocities, reason }` | Step velocities change, then the gesture ends |

The `velocities` property holds each step's velocity as `number[][]` (0 to 1). The `source` property, `{ getSnapshot, subscribe }` returning the current step, marks that step's cells with `data-current`.

| Part | Description |
| --- | --- |
| `<audio-ui-sequencer-rows>` | Generates an `<audio-ui-sequencer-row>` per row from its `<template>`. `labels` names them, separated by commas |
| `<audio-ui-sequencer-row>` | A row with `role="group"`, for its `index` |
| `<audio-ui-sequencer-steps>` | Generates an `<audio-ui-sequencer-step>` per step from its `<template>`. Both render with `display: contents`, so the cells are the grid items |
| `<audio-ui-sequencer-cell>` | A step, a toggle button pressed when on (`data-on`), labelled with its row and step ("Kick, step 5") |
| `<audio-ui-sequencer-velocity>` | A step's velocity as a bar sized by `--sequencer-velocity`. Press or drag to set it; Alt + click or Delete resets it to full; its native range input takes the keyboard |

**In React**

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

