# Radio tuner
> FM radio tuner where static thins out near a station, a waveform appears and the name resolves on lock; release nearby springs onto it.
- Element: `<px-tuner>`
- React: `import { PxTuner } from "@/components/lumesec/px-tuner"`
- Collection: Pixel Lab (https://elements.lumesec.ai/components/pixel-lab)
- Registry item: https://elements.lumesec.ai/r/px-tuner.json
- Page: https://elements.lumesec.ai/components/pixel-lab/tuner



Live preview: https://elements.lumesec.ai/view/px-tuner

```html
<script type="module" src="https://elements.lumesec.ai/cdn/px-tuner.js"></script>

<px-tuner></px-tuner>
```

## Installation

```bash
npx shadcn@latest add @lumesec/px-tuner
```

First time with the @lumesec registry? Register it once, or install by URL:

```bash
npx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.json
npx shadcn@latest add https://elements.lumesec.ai/r/px-tuner.json
```

## Usage

React:

```tsx
import { PxTuner } from "@/components/lumesec/px-tuner";

export function Example() {
  return (
    <PxTuner />
  );
}
```

HTML:

```html
<script type="module" src="https://elements.lumesec.ai/cdn/px-tuner.js"></script>

<px-tuner></px-tuner>
```

## Behaviour

Drag the band to tune. Static thins out as you near a station, a waveform appears, and the station name resolves when it locks. Let go nearby and it springs onto the frequency.

## API reference

### Events

Events bubble and cross the shadow boundary unless the description says otherwise.

| Event  | React prop | Detail                                   | Description                                                                        |
| ------ | ---------- | ---------------------------------------- | ---------------------------------------------------------------------------------- |
| `lock` | `onLock`   | `{ station: string, frequency: number }` | Fires each time the dial locks onto a station, with its name and frequency in MHz. |

## Keyboard

| Keys                   | Action                                                                 |
| ---------------------- | ---------------------------------------------------------------------- |
| ArrowLeft / ArrowRight | Tune down or up by 0.1 MHz, then spring onto a station within 0.6 MHz. |
| PageUp / PageDown      | Jump to the next or previous station, wrapping around.                 |

## Accessibility

* The canvas is focusable with `role="slider"`, `aria-label="Radio frequency"`, `aria-valuemin="87.5"` and `aria-valuemax="108"`.
* `aria-valuenow` is the frequency; `aria-valuetext` adds the station name when locked.
* The status text ("Searching…" or "Locked · \<station>") is an `aria-live="polite"` region.
* Reduced motion: the dial jumps onto a station instead of springing and the static stops flickering; the waveform still moves.

## Theming

The element reads your shadcn theme tokens through its shadow root, so light and dark follow your theme. The accent comes from `--lumesec`. See [Theming](/docs/theming).

This component reads `--foreground`, `--lumesec` and `--muted-foreground`.

To restyle only LumeSec elements, set the matching `--ui-*` overrides: `--ui-accent`, `--ui-fg`, `--ui-mono` and `--ui-muted`.

## Notes

* The stations are built-in demo data: 89.3 LOFI BEATS, 94.7 NEWS 24, 101.1 OPUS FM and 105.5 DEEP CUTS. The band runs from 87.5 to 108 MHz and the dial starts at 97.6.
* No audio plays. The host has a `data-locked` attribute while a station is locked; the station name fades in on the screen in the page font.
* The canvas is 176px tall and animates continuously while on screen.


