# StatusIndicator

Display temporary visual feedback for keyboard and gesture actions

## Import

```tsx
import { StatusIndicator } from '@videojs/react';
```

## Anatomy

```tsx
<StatusIndicator.Root>
  <StatusIndicator.Value />
</StatusIndicator.Root>
```

## Behavior

`StatusIndicator` displays feedback for actions emitted by a [`Hotkey`](https://videojs.org/docs/framework/react/reference/components/hotkey) or [`Gesture`](https://videojs.org/docs/framework/react/reference/components/gesture) in the same player `Container`. It does not react to buttons, direct player-store changes, or media state changes on their own.

The input event arrives before its action is resolved. The indicator uses the media snapshot from that moment to predict the next visual status:

| Action | `data-status` | Value |
| --- | --- | --- |
| `togglePaused` | `"play"` or `"pause"` | Translated playing or paused label |
| `toggleMuted` | `"volume-off"`, `"volume-low"`, or `"volume-high"` | Predicted volume percentage |
| `volumeStep` | `"volume-off"`, `"volume-low"`, or `"volume-high"` | Predicted volume percentage |
| `toggleSubtitles` | `"captions-on"` or `"captions-off"` | Translated captions label |
| `toggleFullscreen` | `"fullscreen"` or `"exit-fullscreen"` | Translated fullscreen label |
| `togglePictureInPicture` | `"pip"` or `"exit-pip"` | Translated picture-in-picture label |

`toggleSubtitles` is ignored when the player reports that no captions or subtitles are available. Seek actions, `toggleControls`, playback-rate actions, and custom actions do not open this indicator.

Use `actions` to allow only some of the supported actions. Omitting it allows all supported actions.

Pass a readonly array of action names:

```tsx
<StatusIndicator.Root actions={["togglePaused", "toggleMuted"]}>
  <StatusIndicator.Value />
</StatusIndicator.Root>
```

The indicator closes after `closeDelay`, which defaults to 800 milliseconds. A repeated handled action updates the current value and restarts that close timer without replaying the entry transition.

`StatusIndicator.Root` stops rendering after its close transition.

## Styling

| Attribute | Values | Description |
| --- | --- | --- |
| `data-open` | Present / absent | Present while the indicator is open |
| `data-status` | `"play"`, `"pause"`, `"volume-off"`, `"volume-low"`, `"volume-high"`, `"captions-on"`, `"captions-off"`, `"fullscreen"`, `"exit-fullscreen"`, `"pip"`, or `"exit-pip"` | Predicted status for the handled action |
| `data-starting-style` | Present / absent | Present during the open transition |
| `data-ending-style` | Present / absent | Present during the close transition |

Use `data-status` to select an icon or other visual treatment, and use the transition attributes for entry and exit styles.

React renders standard DOM elements. Add a `className` to the Root:

```css
.status-indicator[data-status="play"] {
  color: green;
}

.status-indicator[data-starting-style],
.status-indicator[data-ending-style] {
  opacity: 0;
}
```

## Accessibility

`StatusIndicator` is visual feedback and does not create a live region. Keep every action available through keyboard-operable controls, and pair the player with [`StatusAnnouncer`](https://videojs.org/docs/framework/react/reference/components/status-announcer) when state changes should be announced to screen readers. Do not make `StatusIndicator.Value` a live region.

## Examples

### Basic Usage

Focus the player, then press K to play or pause, M to mute, F for fullscreen, C for captions, or I for picture-in-picture.

**App.tsx**

```tsx
import { Container, createPlayer, Hotkey, StatusIndicator } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';

import './BasicUsage.css';

const { Player } = createPlayer({ features: videoFeatures });

const statusActions = [
  'togglePaused',
  'toggleMuted',
  'toggleSubtitles',
  'toggleFullscreen',
  'togglePictureInPicture',
] as const;

export default function BasicUsage() {
  return (
    <Player>
      <Container className="react-status-indicator-basic" tabIndex={0}>
        <Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop>
          <track kind="captions" src="/docs/demos/captions-button/captions.vtt" srcLang="en" label="English" />
        </Video>
        <p className="react-status-indicator-basic__instructions">
          Focus the player · K: play/pause · M: mute · F: fullscreen · C: captions · I: picture-in-picture
        </p>
        <StatusIndicator.Root
          className="react-status-indicator-basic__indicator"
          actions={statusActions}
          aria-hidden="true"
        >
          <StatusIndicator.Value className="react-status-indicator-basic__value" />
        </StatusIndicator.Root>
        <Hotkey keys="k" action="togglePaused" />
        <Hotkey keys="m" action="toggleMuted" />
        <Hotkey keys="f" action="toggleFullscreen" />
        <Hotkey keys="c" action="toggleSubtitles" />
        <Hotkey keys="i" action="togglePictureInPicture" />
      </Container>
    </Player>
  );
}
```

**App.css**

```css
.react-status-indicator-basic {
  position: relative;
}

.react-status-indicator-basic:focus-visible {
  outline: 3px solid #60a5fa;
  outline-offset: 2px;
}

.react-status-indicator-basic video {
  display: block;
  width: 100%;
}

.react-status-indicator-basic__instructions {
  position: absolute;
  top: 10px;
  right: 10px;
  left: 10px;
  padding: 6px 10px;
  margin: 0;
  color: white;
  text-align: center;
  background: rgb(0 0 0 / 70%);
  border-radius: 4px;
}

.react-status-indicator-basic__indicator {
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  min-width: 92px;
  padding: 16px;
  color: white;
  pointer-events: none;
  background: rgb(0 0 0 / 72%);
  border-radius: 12px;
  place-items: center;
  transform: translate(-50%, -50%);
  transition:
    opacity 160ms ease-in-out,
    scale 160ms ease-in-out;
}

.react-status-indicator-basic__indicator::before {
  font-size: 28px;
  font-weight: 700;
  line-height: 1;
}

.react-status-indicator-basic__indicator[data-status="play"]::before {
  content: "▶";
}

.react-status-indicator-basic__indicator[data-status="pause"]::before {
  content: "Ⅱ";
}

.react-status-indicator-basic__indicator[data-status="volume-off"]::before {
  content: "🔇";
}

.react-status-indicator-basic__indicator[data-status="volume-low"]::before {
  content: "🔉";
}

.react-status-indicator-basic__indicator[data-status="volume-high"]::before {
  content: "🔊";
}

.react-status-indicator-basic__indicator[data-status="captions-on"]::before,
.react-status-indicator-basic__indicator[data-status="captions-off"]::before {
  content: "CC";
}

.react-status-indicator-basic__indicator[data-status="captions-off"]::before {
  text-decoration: line-through;
}

.react-status-indicator-basic__indicator[data-status="fullscreen"]::before {
  content: "⛶";
}

.react-status-indicator-basic__indicator[data-status="exit-fullscreen"]::before {
  content: "⊠";
}

.react-status-indicator-basic__indicator[data-status="pip"]::before,
.react-status-indicator-basic__indicator[data-status="exit-pip"]::before {
  content: "▣";
}

.react-status-indicator-basic__indicator[data-status="exit-pip"]::before {
  text-decoration: line-through;
}

.react-status-indicator-basic__indicator[data-starting-style],
.react-status-indicator-basic__indicator[data-ending-style] {
  opacity: 0;
  scale: 0.85;
}

.react-status-indicator-basic__value {
  margin-top: 8px;
  font-variant-numeric: tabular-nums;
}
```

## API Reference

### Root

#### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `actions` | `readonly InputAction[]` | — | Input actions allowed to open the indicator. All supported actions are allowed when omitted. |
| `closeDelay` | `number` | — | Delay in milliseconds before the indicator closes. |
| `labels` | `Partial<InputIndicatorLabels>` | — | Internal translated label overrides supplied by framework adapters. |

#### State

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

| Property | Type | Description |
| --- | --- | --- |
| `transitionStarting` | `boolean` | Whether the open transition is in progress. |
| `transitionEnding` | `boolean` | Whether the close transition is in progress. |
| `open` | `boolean` | Whether the indicator is open. |
| `generation` | `number` | Increments each time a supported input action updates the indicator. |
| `status` | `ReturnType<typeof deriveStatus> extends infer Details ? Details extends { status: infer Status } ? Status \| null : never : never` | Predicted visual status for the handled input action. |
| `label` | `string \| null` | Translated label for the predicted status. |
| `value` | `string \| null` | Predicted volume percentage for volume actions, otherwise `null`. |

#### Data attributes

| Attribute | Type | Description |
| --- | --- | --- |
| `data-open` | — | Present while the indicator is open. |
| `data-status` | `ReturnType<typeof deriveStatus> extends infer Details ? Details extends { status: infer Status } ? Status \| null : never : never` | Predicted visual status for the handled input action. |
| `data-starting-style` | — | Present during the open transition. |
| `data-ending-style` | — | Present during the close transition. |

### Value

#### Props

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