StatusIndicator
Display temporary visual feedback for keyboard and gesture actions
Import
import { StatusIndicator } from '@videojs/react';import '@videojs/html/ui/status-indicator';Anatomy
<StatusIndicator.Root>
<StatusIndicator.Value />
</StatusIndicator.Root><media-status-indicator>
<media-status-indicator-value></media-status-indicator-value>
</media-status-indicator>Behavior
StatusIndicator displays feedback for actions emitted by a Hotkey or 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:
<StatusIndicator.Root actions={["togglePaused", "toggleMuted"]}>
<StatusIndicator.Value />
</StatusIndicator.Root>Set the actions attribute to action names separated by commas, whitespace, or both:
<media-status-indicator actions="togglePaused, toggleMuted">
<media-status-indicator-value></media-status-indicator-value>
</media-status-indicator>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. React stops rendering the Root after its close transition. The HTML custom element remains in the document with hidden until the next handled action.
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.
media-status-indicator[data-status="play"] {
color: green;
}
media-status-indicator[data-starting-style],
media-status-indicator[data-ending-style] {
opacity: 0;
}React renders standard DOM elements. Add a className to the Root:
.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 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.
Focus the player · K: play/pause · M: mute · F: fullscreen · C: captions · I: picture-in-picture
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>
);
}
.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;
}
Focus the player · K: play/pause · M: mute · F: fullscreen · C: captions · I: picture-in-picture
<video-player>
<media-container class="html-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 class="html-status-indicator-basic__instructions">
Focus the player · K: play/pause · M: mute · F: fullscreen · C: captions · I: picture-in-picture
</p>
<media-status-indicator
class="html-status-indicator-basic__indicator"
actions="togglePaused toggleMuted toggleSubtitles toggleFullscreen togglePictureInPicture"
aria-hidden="true"
>
<media-status-indicator-value class="html-status-indicator-basic__value"></media-status-indicator-value>
</media-status-indicator>
<media-hotkey keys="k" action="togglePaused"></media-hotkey>
<media-hotkey keys="m" action="toggleMuted"></media-hotkey>
<media-hotkey keys="f" action="toggleFullscreen"></media-hotkey>
<media-hotkey keys="c" action="toggleSubtitles"></media-hotkey>
<media-hotkey keys="i" action="togglePictureInPicture"></media-hotkey>
</media-container>
</video-player>
.html-status-indicator-basic {
position: relative;
}
.html-status-indicator-basic:focus-visible {
outline: 3px solid #60a5fa;
outline-offset: 2px;
}
.html-status-indicator-basic video {
display: block;
width: 100%;
}
.html-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;
}
.html-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;
}
.html-status-indicator-basic__indicator::before {
font-size: 28px;
font-weight: 700;
line-height: 1;
}
.html-status-indicator-basic__indicator[data-status="play"]::before {
content: "▶";
}
.html-status-indicator-basic__indicator[data-status="pause"]::before {
content: "Ⅱ";
}
.html-status-indicator-basic__indicator[data-status="volume-off"]::before {
content: "🔇";
}
.html-status-indicator-basic__indicator[data-status="volume-low"]::before {
content: "🔉";
}
.html-status-indicator-basic__indicator[data-status="volume-high"]::before {
content: "🔊";
}
.html-status-indicator-basic__indicator[data-status="captions-on"]::before,
.html-status-indicator-basic__indicator[data-status="captions-off"]::before {
content: "CC";
}
.html-status-indicator-basic__indicator[data-status="captions-off"]::before {
text-decoration: line-through;
}
.html-status-indicator-basic__indicator[data-status="fullscreen"]::before {
content: "⛶";
}
.html-status-indicator-basic__indicator[data-status="exit-fullscreen"]::before {
content: "⊠";
}
.html-status-indicator-basic__indicator[data-status="pip"]::before,
.html-status-indicator-basic__indicator[data-status="exit-pip"]::before {
content: "▣";
}
.html-status-indicator-basic__indicator[data-status="exit-pip"]::before {
text-decoration: line-through;
}
.html-status-indicator-basic__indicator[data-starting-style],
.html-status-indicator-basic__indicator[data-ending-style] {
opacity: 0;
scale: 0.85;
}
.html-status-indicator-basic__value {
margin-top: 8px;
font-variant-numeric: tabular-nums;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/hotkey';
import '@videojs/html/ui/status-indicator';
API Reference
Rootmedia-status-indicator
Props
| Prop | Type | Default | Details |
|---|---|---|---|
actions | readonly InputAction[] | — | |
| |||
closeDelay | number | — | |
| |||
labels | Partial<InputIndicatorLabels> | — | |
| |||
State
render, className, and style props.| Property | Type | Details |
|---|---|---|
transitionStarting | boolean | |
| ||
transitionEnding | boolean | |
| ||
open | boolean | |
| ||
generation | number | |
| ||
status | ReturnType<typeof deriveStatus> exten... | |
| ||
label | string | null | |
| ||
value | string | null | |
| ||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-open | ||
| ||
data-status | ReturnType<typeof deriveStatus> exten... | |
| ||
data-starting-style | ||
| ||
data-ending-style | ||
| ||
Valuemedia-status-indicator-value
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: StatusIndicatorCore.State) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: StatusIndicatorCore.State) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: StatusIndicatorCore.State) => CSSProperties | undefined) | — | |
| |||