media-time
Time display components for showing current time, duration, and remaining time in a video player
Import
import { Time } from '@videojs/react';import '@videojs/html/ui/time';Anatomy
<Time.Group>
<Time.Value type="current" />
<Time.Separator />
<Time.Value type="duration" />
</Time.Group><media-time-group>
<media-time type="current"></media-time>
<media-time-separator></media-time-separator>
<media-time type="duration"></media-time>
</media-time-group>Behavior
Three display types — current, duration, and remaining — in digital format with smart padding:
- Hours are never padded (
1:05:30, not01:05:30) - Minutes are padded when hours are shown (
1:05:30, but5:30) - Seconds are always padded (
1:05, not1:5)
Hour display is triggered when either the current value or the duration exceeds 1 hour, ensuring consistency within a Group. Remaining time displays a negative sign (customizable via the negativeSign prop).
Use toggle to let current displays switch between elapsed and remaining time, or remaining and duration displays switch between those two values. The initial display comes from type.
<Time.Value toggle />
<Time.Value toggle type="remaining" />
<Time.Value toggle type="duration" /><media-time toggle></media-time>
<media-time toggle type="remaining"></media-time>
<media-time toggle type="duration"></media-time>Styling
The negative sign is rendered inside <span aria-hidden="true"> and can be hidden with CSS:
[data-type="remaining"] > span[aria-hidden] {
display: none;
}Accessibility
Each <media-time> has:
role="time"anddatetimefor machine-readable time semanticsaria-labelfor the static role label (“Current time”, “Duration”, “Remaining”)
Each <Time.Value> has:
- a native
<time datetime>element for machine-readable time semantics aria-labelfor the static role label (“Current time”, “Duration”, “Remaining”)
No aria-live region is used — time updates too frequently and might overwhelm screen readers. The separator and negative sign are aria-hidden="true" because the accessible label already describes the value.
Toggleable time displays receive role="button", tabindex="0", and keyboard support for Enter and Space. Their aria-label starts with the action and includes the current value. Their aria-description explains which values the control toggles between.
Examples
Current Time
import { Container, createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const { Player } = createPlayer({ features: videoFeatures });
export default function CurrentTime() {
return (
<Player>
<Container className="media-container">
<Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
<Time.Value type="current" className="media-time" />
</Container>
</Player>
);
}
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.media-time {
position: absolute;
bottom: 10px;
left: 10px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
<video-player class="video-player">
<media-container>
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
<media-time class="media-time" type="current"></media-time>
</media-container>
</video-player>
.video-player media-container {
position: relative;
}
.video-player media-container video {
width: 100%;
}
.media-time {
position: absolute;
bottom: 10px;
left: 10px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
Current / Duration
import { Container, createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const { Player } = createPlayer({ features: videoFeatures });
export default function CurrentDuration() {
return (
<Player>
<Container className="media-container">
<Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
<Time.Group className="time-group">
<Time.Value type="current" />
<Time.Separator />
<Time.Value type="duration" />
</Time.Group>
</Container>
</Player>
);
}
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.time-group {
position: absolute;
bottom: 10px;
left: 10px;
display: flex;
gap: 4px;
align-items: center;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
<video-player class="video-player">
<media-container>
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
<media-time-group class="time-group">
<media-time type="current"></media-time>
<media-time-separator></media-time-separator>
<media-time type="duration"></media-time>
</media-time-group>
</media-container>
</video-player>
.video-player media-container {
position: relative;
}
.video-player media-container video {
width: 100%;
}
.time-group {
position: absolute;
bottom: 10px;
left: 10px;
display: flex;
gap: 4px;
align-items: center;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
Remaining
import { Container, createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const { Player } = createPlayer({ features: videoFeatures });
export default function Remaining() {
return (
<Player>
<Container className="media-container">
<Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
<Time.Value type="remaining" className="media-time" />
</Container>
</Player>
);
}
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.media-time {
position: absolute;
bottom: 10px;
left: 10px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
<video-player class="video-player">
<media-container>
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
<media-time class="media-time" type="remaining"></media-time>
</media-container>
</video-player>
.video-player media-container {
position: relative;
}
.video-player media-container video {
width: 100%;
}
.media-time {
position: absolute;
bottom: 10px;
left: 10px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
Custom Separator
import { Container, createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const { Player } = createPlayer({ features: videoFeatures });
export default function CustomSeparator() {
return (
<Player>
<Container className="media-container">
<Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
<Time.Group className="time-group">
<Time.Value type="current" />
<Time.Separator> of </Time.Separator>
<Time.Value type="duration" />
</Time.Group>
</Container>
</Player>
);
}
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.time-group {
position: absolute;
bottom: 10px;
left: 10px;
display: flex;
gap: 4px;
align-items: center;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
<video-player class="video-player">
<media-container>
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
<media-time-group class="time-group">
<media-time type="current"></media-time>
<media-time-separator> of </media-time-separator>
<media-time type="duration"></media-time>
</media-time-group>
</media-container>
</video-player>
.video-player media-container {
position: relative;
}
.video-player media-container video {
width: 100%;
}
.time-group {
position: absolute;
bottom: 10px;
left: 10px;
display: flex;
gap: 4px;
align-items: center;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
Custom Negative Sign
import { Container, createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const { Player } = createPlayer({ features: videoFeatures });
export default function CustomNegativeSign() {
return (
<Player>
<Container className="media-container">
<Video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoPlay muted playsInline loop />
<Time.Value type="remaining" negativeSign="~" className="media-time" />
</Container>
</Player>
);
}
.media-container {
position: relative;
}
.media-container video {
width: 100%;
}
.media-time {
position: absolute;
bottom: 10px;
left: 10px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
<video-player class="video-player">
<media-container>
<video src="https://stream.mux.com/BV3YZtogl89mg9VcNBhhnHm02Y34zI1nlMuMQfAbl3dM/highest.mp4" autoplay muted playsinline loop></video>
<media-time class="media-time" type="remaining" negative-sign="~"></media-time>
</media-container>
</video-player>
.video-player media-container {
position: relative;
}
.video-player media-container video {
width: 100%;
}
.media-time {
position: absolute;
bottom: 10px;
left: 10px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
color: black;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
backdrop-filter: blur(10px);
}
import '@videojs/html/video/player';
import '@videojs/html/ui/container';
import '@videojs/html/ui/time';
API Reference
Valuemedia-time
Displays a formatted time value (current, duration, or remaining).
Props
| Prop | Type | Default | Details |
|---|---|---|---|
label | object | '' | |
| |||
negativeSign | string | '-' | |
| |||
toggle | boolean | false | |
| |||
type | 'current' | 'duration' | 'remaining' | 'current' | |
| |||
State
render, className, and style props.| Property | Type | Details |
|---|---|---|
type | 'current' | 'duration' | 'remaining' | |
| ||
seconds | number | |
| ||
negative | boolean | |
| ||
text | string | |
| ||
phrase | string | |
| ||
datetime | string | |
| ||
Data attributes
| Attribute | Type | Details |
|---|---|---|
data-type | 'current' | 'duration' | 'remaining' | |
| ||
Groupmedia-time-group
Container for composed time displays. Renders a <span> element.
Separatormedia-time-separator
Divider between time values. Hidden from screen readers.