SeekIndicator
A temporary visual indicator for keyboard and gesture seeking
Import
import { SeekIndicator } from '@videojs/react';import '@videojs/html/ui/seek-indicator';Anatomy
<SeekIndicator.Root>
<SeekIndicator.Value />
</SeekIndicator.Root><media-seek-indicator>
<media-seek-indicator-value></media-seek-indicator-value>
</media-seek-indicator>Behavior
SeekIndicator displays feedback for seek actions emitted by a Hotkey or Gesture in the same player Container. It does not react to current-time changes or seek commands invoked directly through the player store.
| Action | Direction | Value |
|---|---|---|
seekStep |
The sign of value determines forward or backward |
The absolute number of seconds, such as 10s |
seekToPercent |
The target percentage is compared with the current time | The formatted current time when the action occurred |
Rapid seekStep actions in the same direction accumulate while the indicator is open. For example, two 10-second forward actions display 20s. Changing direction or using seekToPercent starts a new display. During a rapid sequence, another full step is not added when it would pass the start or duration boundary.
For seekToPercent, pass a percentage from 0 to 100 as value. A Hotkey with keys="0-9" can omit value; the pressed digit maps to 0%, 10%, and so on through 90%.
The indicator closes after closeDelay, which defaults to 800 milliseconds. React stops rendering the Root after its close transition. The HTML custom element remains in the document with hidden until the next handled seek action.
Styling
| Attribute | Values | Description |
|---|---|---|
data-open |
Present / absent | Present while the indicator is open |
data-direction |
"forward" | "backward" |
Direction of the handled seek |
data-starting-style |
Present / absent | Present during the open transition |
data-ending-style |
Present / absent | Present during the close transition |
Position the Root from data-direction and use the transition attributes for entry and exit styles.
media-seek-indicator[data-direction="backward"] {
left: 1rem;
}
media-seek-indicator[data-direction="forward"] {
right: 1rem;
}
media-seek-indicator[data-starting-style],
media-seek-indicator[data-ending-style] {
opacity: 0;
}React renders standard DOM elements. Add a className to the Root:
.seek-indicator[data-direction="backward"] {
left: 1rem;
}
.seek-indicator[data-direction="forward"] {
right: 1rem;
}
.seek-indicator[data-starting-style],
.seek-indicator[data-ending-style] {
opacity: 0;
}Accessibility
SeekIndicator is visual feedback and does not create a live region. Keep the same seek operation available through keyboard-operable controls, and pair it with StatusAnnouncer when changes should be announced to screen readers. Do not make SeekIndicator.Value a live region because rapid seek input would produce repeated announcements.
Examples
Basic Usage
Focus the player, then press the left or right arrow key to seek by ten seconds. Press a digit from 0 through 9 to seek to a percentage of the duration.
Focus the player · ←/→: seek 10s · 0–9: seek to percent
import { Container, createPlayer, Hotkey, SeekIndicator } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
import './BasicUsage.css';
const { Player } = createPlayer({ features: videoFeatures });
export default function BasicUsage() {
return (
<Player>
<Container className="react-seek-indicator-basic" tabIndex={0}>
<Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
<p className="react-seek-indicator-basic__instructions">
Focus the player · ←/→: seek 10s · 0–9: seek to percent
</p>
<SeekIndicator.Root className="react-seek-indicator-basic__indicator" aria-hidden="true">
<SeekIndicator.Value className="react-seek-indicator-basic__value" />
</SeekIndicator.Root>
<Hotkey keys="ArrowLeft" action="seekStep" value={-10} />
<Hotkey keys="ArrowRight" action="seekStep" value={10} />
<Hotkey keys="0-9" action="seekToPercent" />
</Container>
</Player>
);
}
.react-seek-indicator-basic {
position: relative;
}
.react-seek-indicator-basic:focus-visible {
outline: 3px solid #60a5fa;
outline-offset: 2px;
}
.react-seek-indicator-basic video {
display: block;
width: 100%;
}
.react-seek-indicator-basic__instructions {
position: absolute;
top: 10px;
left: 50%;
padding: 6px 10px;
margin: 0;
color: white;
text-align: center;
background: rgb(0 0 0 / 70%);
border-radius: 4px;
translate: -50%;
}
.react-seek-indicator-basic__indicator {
position: absolute;
top: 50%;
left: 50%;
display: grid;
min-width: 72px;
padding: 16px;
color: white;
pointer-events: none;
background: rgb(0 0 0 / 72%);
border-radius: 9999px;
place-items: center;
transform: translate(-50%, -50%);
transition:
opacity 160ms ease-in-out,
scale 160ms ease-in-out;
}
.react-seek-indicator-basic__indicator::before {
font-size: 24px;
line-height: 1;
content: "↔";
}
.react-seek-indicator-basic__indicator[data-direction="backward"] {
right: auto;
left: 16px;
transform: translateY(-50%);
}
.react-seek-indicator-basic__indicator[data-direction="backward"]::before {
content: "↶";
}
.react-seek-indicator-basic__indicator[data-direction="forward"] {
right: 16px;
left: auto;
transform: translateY(-50%);
}
.react-seek-indicator-basic__indicator[data-direction="forward"]::before {
content: "↷";
}
.react-seek-indicator-basic__indicator[data-starting-style],
.react-seek-indicator-basic__indicator[data-ending-style] {
opacity: 0;
scale: 0.85;
}
.react-seek-indicator-basic__value {
font-variant-numeric: tabular-nums;
}
Focus the player · ←/→: seek 10s · 0–9: seek to percent
<video-player>
<media-container class="html-seek-indicator-basic" tabindex="0">
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
<p class="html-seek-indicator-basic__instructions">Focus the player · ←/→: seek 10s · 0–9: seek to percent</p>
<media-seek-indicator class="html-seek-indicator-basic__indicator" aria-hidden="true">
<media-seek-indicator-value class="html-seek-indicator-basic__value"></media-seek-indicator-value>
</media-seek-indicator>
<media-hotkey keys="ArrowLeft" action="seekStep" value="-10"></media-hotkey>
<media-hotkey keys="ArrowRight" action="seekStep" value="10"></media-hotkey>
<media-hotkey keys="0-9" action="seekToPercent"></media-hotkey>
</media-container>
</video-player>
.html-seek-indicator-basic {
position: relative;
}
.html-seek-indicator-basic:focus-visible {
outline: 3px solid #60a5fa;
outline-offset: 2px;
}
.html-seek-indicator-basic video {
display: block;
width: 100%;
}
.html-seek-indicator-basic__instructions {
position: absolute;
top: 10px;
left: 50%;
padding: 6px 10px;
margin: 0;
color: white;
text-align: center;
background: rgb(0 0 0 / 70%);
border-radius: 4px;
translate: -50%;
}
.html-seek-indicator-basic__indicator {
position: absolute;
top: 50%;
left: 50%;
display: grid;
min-width: 72px;
padding: 16px;
color: white;
pointer-events: none;
background: rgb(0 0 0 / 72%);
border-radius: 9999px;
place-items: center;
transform: translate(-50%, -50%);
transition:
opacity 160ms ease-in-out,
scale 160ms ease-in-out;
}
.html-seek-indicator-basic__indicator::before {
font-size: 24px;
line-height: 1;
content: "↔";
}
.html-seek-indicator-basic__indicator[data-direction="backward"] {
right: auto;
left: 16px;
transform: translateY(-50%);
}
.html-seek-indicator-basic__indicator[data-direction="backward"]::before {
content: "↶";
}
.html-seek-indicator-basic__indicator[data-direction="forward"] {
right: 16px;
left: auto;
transform: translateY(-50%);
}
.html-seek-indicator-basic__indicator[data-direction="forward"]::before {
content: "↷";
}
.html-seek-indicator-basic__indicator[data-starting-style],
.html-seek-indicator-basic__indicator[data-ending-style] {
opacity: 0;
scale: 0.85;
}
.html-seek-indicator-basic__value {
font-variant-numeric: tabular-nums;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/hotkey';
import '@videojs/html/ui/seek-indicator';
API Reference
Rootmedia-seek-indicator
Props
| Prop | Type | Default | Details |
|---|---|---|---|
closeDelay | number | — | |
| |||
State
render, className, and style props.| Property | Type | Details |
|---|---|---|
transitionStarting | boolean | |
| ||
transitionEnding | boolean | |
| ||
open | boolean | |
| ||
generation | number | |
| ||
direction | 'forward' | 'backward' | null | |
| ||
count | number | |
| ||
seekTotal | number | |
| ||
value | string | null | |
| ||
currentTime | string | |
| ||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-open | ||
| ||
data-direction | 'forward' | 'backward' | null | |
| ||
data-starting-style | ||
| ||
data-ending-style | ||
| ||
Valuemedia-seek-indicator-value
Props
| Prop | Type | Default | Details |
|---|---|---|---|
className | string | ((state: SeekIndicatorCore.State) => string | undefined) | — | |
| |||
render | ReactElement | ((props: HTMLProps, state: SeekIndicatorCore.State) => ReactElement | null) | — | |
| |||
style | CSSProperties | ((state: SeekIndicatorCore.State) => CSSProperties | undefined) | — | |
| |||