Time
Time display components for showing current time, duration, and remaining time in a video player
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:
-
aria-labelfor the static role label (“Current time”, “Duration”, “Remaining”) -
aria-valuetextfor the dynamic human-readable time (“1 minute, 30 seconds”)
Each <Time.Value> has:
-
aria-labelfor the static role label (“Current time”, “Duration”, “Remaining”) -
aria-valuetextfor the dynamic human-readable time (“1 minute, 30 seconds”)
No aria-live region is used — time updates too frequently and might overwhelm screen readers. The separator is aria-hidden="true" since screen readers already hear each time value separately. The negative sign is also aria-hidden because aria-valuetext already conveys “remaining”. In React, <time datetime> provides machine-readable time for parsers.
Toggleable time displays receive role="button" and keyboard support for Enter and Space.
Examples
Current Time
import { createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const Player = createPlayer({ features: videoFeatures });
export default function CurrentTime() {
return (
<Player.Provider>
<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" />
</Player.Container>
</Player.Provider>
);
}
.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 {
position: relative;
}
.video-player 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/time';
Current / Duration
import { createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const Player = createPlayer({ features: videoFeatures });
export default function CurrentDuration() {
return (
<Player.Provider>
<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>
</Player.Container>
</Player.Provider>
);
}
.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 {
position: relative;
}
.video-player 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/time';
Remaining
import { createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const Player = createPlayer({ features: videoFeatures });
export default function Remaining() {
return (
<Player.Provider>
<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" />
</Player.Container>
</Player.Provider>
);
}
.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 {
position: relative;
}
.video-player 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/time';
Custom Separator
import { createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const Player = createPlayer({ features: videoFeatures });
export default function CustomSeparator() {
return (
<Player.Provider>
<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>
</Player.Container>
</Player.Provider>
);
}
.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 {
position: relative;
}
.video-player 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/time';
Custom Negative Sign
import { createPlayer, Time } from '@videojs/react';
import { Video, videoFeatures } from '@videojs/react/video';
const Player = createPlayer({ features: videoFeatures });
export default function CustomNegativeSign() {
return (
<Player.Provider>
<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" />
</Player.Container>
</Player.Provider>
);
}
.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 {
position: relative;
}
.video-player 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/time';
API Reference
Value media-time
Displays a formatted time value (current, duration, or remaining).
Props
| Prop | Type | Default | Details |
|---|---|---|---|
label | string | function | '' | |
| |||
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' | |
| ||
Group media-time-group
Container for composed time displays. Renders a <span> element.
Separator media-time-separator
Divider between time values. Hidden from screen readers.