# QualityRadioGroup

A menu radio group for selecting video quality

Creates radio items from the player video rendition state and selects automatic or manual quality.

## Import

```tsx
import { QualityRadioGroup } from '@videojs/react/ui/quality-radio-group';
```

## Anatomy

```tsx
<QualityRadioGroup.Root>
  <Menu.Trigger>
    <QualityRadioGroup.Value />
  </Menu.Trigger>
  <Menu.Content>
    <QualityRadioGroup.Options
      renderItem={(props, item) => (
        <Menu.RadioItem {...props}>
          {item.label}
          <Menu.ItemIndicator checked={item.checked} />
        </Menu.RadioItem>
      )}
    />
  </Menu.Content>
</QualityRadioGroup.Root>
```

## Behavior

The group is available when the configured media exposes more than one video rendition. The first generated option is `Auto`; selecting it returns control to adaptive bitrate selection. Pass `formatRendition` to customize visible rendition labels.

`QualityRadioGroup.Root` uses [`useQualityOptions`](https://videojs.org/docs/framework/react/reference/api/use-quality-options) to own selection and renders no element. Wrap the menu’s `Menu.Trigger` and `Menu.Content` in it so the trigger inherits the group’s disabled and hidden state and exposes `data-availability`. `QualityRadioGroup.Options` renders the items: its required `renderItem` callback renders the [`Menu.RadioItem`](https://videojs.org/docs/framework/react/reference/components/menu) root and receives item state containing the translated `label`, optional `tier` and `badge`, and current `checked` value. `QualityRadioGroup.Value` displays the selected label, typically inside the trigger. `Options` and `Value` render `null` when the [quality feature](https://videojs.org/docs/framework/react/reference/api/feature-quality) is not configured.

The `QualityRadioGroup` export from the `@videojs/react` root is a compatibility component that composes `Root` and `Options` behind the former single-component props. Prefer the parts for new code.

## Styling

| Attribute | Values | Description |
| --- | --- | --- |
| `data-quality` | `string` | Current quality value. |
| `data-disabled` | Present / absent | Present when quality selection is disabled. |
| `data-hidden` | Present / absent | Present when multiple renditions are unavailable. |
| `data-availability` | `"available"` / `"unavailable"` | Whether multiple renditions are available. |

Unavailable groups receive the native `hidden` attribute.

## Accessibility

The group uses the menu radio group pattern.

`QualityRadioGroup.Options` receives its accessible label from the `label` prop on `QualityRadioGroup.Root` or defaults to `Quality`. Override it with `aria-label` or `aria-labelledby` on `QualityRadioGroup.Options`.

## Examples

### Basic usage

**App.tsx**

```tsx
import { Container, createPlayer, Menu } from '@videojs/react';
import { HlsJsVideo } from '@videojs/react/media/hlsjs-video';
import { QualityRadioGroup } from '@videojs/react/ui/quality-radio-group';
import { videoFeatures } from '@videojs/react/video';
import type { ReactNode } from 'react';

const { Player } = createPlayer({ features: videoFeatures });
const src = 'https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4.m3u8';

function QualityMenu(): ReactNode {
  return (
    <Menu.Root side="top" align="end">
      <QualityRadioGroup.Root>
        <Menu.Trigger className="settings-trigger" render={<button type="button" />}>
          Quality
          <QualityRadioGroup.Value className="menu-hint" />
        </Menu.Trigger>
        <Menu.Popup className="menu">
          <Menu.Content>
            <QualityRadioGroup.Options
              className="menu-group"
              renderItem={(props, item) => (
                <Menu.RadioItem {...props} className="menu-item">
                  <span>
                    {item.label}
                    {item.tier ? <sup className="menu-tier">{item.tier}</sup> : null}
                  </span>
                  {item.badge ? <span className="menu-badge">{item.badge}</span> : null}
                  <Menu.ItemIndicator checked={item.checked} forceMount className="menu-indicator">
                    ✓
                  </Menu.ItemIndicator>
                </Menu.RadioItem>
              )}
            />
          </Menu.Content>
        </Menu.Popup>
      </QualityRadioGroup.Root>
    </Menu.Root>
  );
}

export default function BasicUsage() {
  return (
    <Player>
      <Container className="media-container">
        <HlsJsVideo src={src} autoPlay crossOrigin="anonymous" muted playsInline loop />
        <div className="menu-bar">
          <QualityMenu />
        </div>
      </Container>
    </Player>
  );
}
```

**App.css**

```css
.media-container {
  position: relative;
}

.media-container video {
  width: 100%;
}

.menu-bar {
  position: absolute;
  right: 10px;
  bottom: 10px;
}

.settings-trigger {
  padding: 6px 16px;
  color: black;
  cursor: pointer;
  background: rgba(255, 255, 255, 0.75);
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: 9999px;
  backdrop-filter: blur(10px);
}

.menu-hint {
  margin-left: 8px;
  color: rgba(0, 0, 0, 0.6);
}

.menu-hint:empty {
  display: none;
}

.menu {
  --media-menu-side-offset: 8px;
  box-sizing: border-box;
  display: grid;
  gap: 2px;
  min-width: 180px;
  max-width: var(--media-menu-available-width, var(--media-popover-available-width, none));
  max-height: var(--media-menu-available-height, var(--media-popover-available-height, none));
  padding: 6px;
  margin: 0;
  overflow: auto;
  overscroll-behavior: none;
  font-size: 14px;
  color: white;
  background: rgba(0, 0, 0, 0.88);
  border: 0;
  border-radius: 8px;
  backdrop-filter: blur(10px);
}

.menu-group {
  display: grid;
  gap: 2px;
}

.menu-item {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
  min-height: 32px;
  padding: 0 10px;
  font: inherit;
  color: inherit;
  cursor: pointer;
  background: none;
  border: 0;
  border-radius: 6px;
}

.menu-item[data-highlighted] {
  background: rgba(255, 255, 255, 0.16);
}

.menu-tier {
  margin-left: 2px;
  font-size: 10px;
}

.menu-badge {
  margin-left: auto;
  color: rgba(255, 255, 255, 0.72);
}

.menu-indicator {
  opacity: 0;
}

[role="menuitemradio"][aria-checked="true"] .menu-indicator {
  opacity: 1;
}
```

## API Reference

### Root

Owns quality option state and shares it with an enclosing menu. Does not render a DOM element.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | Whether quality selection is disabled. |
| `formatRendition` | `((rendition: MediaVideoRendition) => Text \| string)` | `formatRenditionLabel` | Custom formatter for visible rendition labels. |
| `label` | `{ key: string; text: string } \| string \| ((state: QualityRadioGroupState) => Text \| string)` | `''` | Custom label for the options group. |

#### State

State is accessible via the `render`, `className`, and `style` props.

| Property | Type | Description |
| --- | --- | --- |
| `label` | `{ key: string; text: string } \| string` | |
| `value` | `string` | Current radio-group value. |
| `options` | `readonly Option[]` | Ordered options displayed by platform adapters. |
| `disabled` | `boolean` | Whether the entire option group is disabled. |
| `hidden` | `boolean` | Whether the option group is hidden because no meaningful selection is available. |
| `availability` | `'available' \| 'unavailable'` | Whether the media exposes a meaningful selection. |

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-quality` | `string` | Current quality value. |
| `data-disabled` | — | Present when quality selection is disabled. |
| `data-hidden` | — | Present when quality selection is unavailable. |
| `data-availability` | `'available' \| 'unavailable'` | Indicates quality availability (`available` or `unavailable`). |

### Options

Renders menu radio items for the player's video renditions.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: QualityRadioGroupState) => string \| undefined)` | — | Class name or function returning class name from state. |
| `render` | `ReactElement \| ((props: HTMLProps, state: QualityRadioGroupState) => ReactElement \| null)` | — | Render prop for custom element. |
| `renderItem` | `((props: QualityRadioGroupItemProps, state: QualityRadioGroupItemState) => ReactElement)` | — | Render one consumer-owned menu radio item for every quality option. |
| `style` | `CSSProperties \| ((state: QualityRadioGroupState) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

### Value

Displays the selected quality label.

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string \| ((state: QualityOptionsResult) => string \| undefined)` | — | Class name or function returning class name from state. |
| `render` | `ReactElement \| ((props: HTMLProps, state: QualityOptionsResult) => ReactElement \| null)` | — | Render prop for custom element. |
| `style` | `CSSProperties \| ((state: QualityOptionsResult) => CSSProperties \| undefined)` | — | Style or function returning style from state. |

---

React documentation: https://videojs.org/docs/framework/react/llms.txt
All documentation: https://videojs.org/llms.txt
