Time
Components for displaying and composing media time information
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).
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.
Examples
Current Time
import { createPlayer, features, Time } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './CurrentTime.css';
const Player = createPlayer({ features: [...features.video] });
export default function CurrentTime() {
return (
<Player.Provider>
<Player.Container className="react-time-current-time">
<Video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<Time.Value type="current" className="react-time-current-time__value" />
</Player.Container>
</Player.Provider>
);
}
.react-time-current-time {
position: relative;
}
.react-time-current-time video {
width: 100%;
}
.react-time-current-time__value {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
<video-player class="html-time-current-time">
<video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<media-time class="html-time-current-time__value" type="current"></media-time>
</video-player>
.html-time-current-time {
position: relative;
}
.html-time-current-time video {
width: 100%;
}
.html-time-current-time__value {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/time';
Current / Duration
import { createPlayer, features, Time } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './CurrentDuration.css';
const Player = createPlayer({ features: [...features.video] });
export default function CurrentDuration() {
return (
<Player.Provider>
<Player.Container className="react-time-current-duration">
<Video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<Time.Group className="react-time-current-duration__group">
<Time.Value type="current" />
<Time.Separator />
<Time.Value type="duration" />
</Time.Group>
</Player.Container>
</Player.Provider>
);
}
.react-time-current-duration {
position: relative;
}
.react-time-current-duration video {
width: 100%;
}
.react-time-current-duration__group {
display: flex;
align-items: center;
gap: 4px;
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
<video-player class="html-time-current-duration">
<video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<media-time-group class="html-time-current-duration__group">
<media-time type="current"></media-time>
<media-time-separator></media-time-separator>
<media-time type="duration"></media-time>
</media-time-group>
</video-player>
.html-time-current-duration {
position: relative;
}
.html-time-current-duration video {
width: 100%;
}
.html-time-current-duration__group {
display: flex;
align-items: center;
gap: 4px;
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/time';
Remaining
import { createPlayer, features, Time } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './Remaining.css';
const Player = createPlayer({ features: [...features.video] });
export default function Remaining() {
return (
<Player.Provider>
<Player.Container className="react-time-remaining">
<Video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<Time.Value type="remaining" className="react-time-remaining__value" />
</Player.Container>
</Player.Provider>
);
}
.react-time-remaining {
position: relative;
}
.react-time-remaining video {
width: 100%;
}
.react-time-remaining__value {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
<video-player class="html-time-remaining">
<video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<media-time class="html-time-remaining__value" type="remaining"></media-time>
</video-player>
.html-time-remaining {
position: relative;
}
.html-time-remaining video {
width: 100%;
}
.html-time-remaining__value {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/time';
Custom Separator
import { createPlayer, features, Time } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './CustomSeparator.css';
const Player = createPlayer({ features: [...features.video] });
export default function CustomSeparator() {
return (
<Player.Provider>
<Player.Container className="react-time-custom-separator">
<Video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<Time.Group className="react-time-custom-separator__group">
<Time.Value type="current" />
<Time.Separator> of </Time.Separator>
<Time.Value type="duration" />
</Time.Group>
</Player.Container>
</Player.Provider>
);
}
.react-time-custom-separator {
position: relative;
}
.react-time-custom-separator video {
width: 100%;
}
.react-time-custom-separator__group {
display: flex;
align-items: center;
gap: 4px;
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
<video-player class="html-time-custom-separator">
<video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<media-time-group class="html-time-custom-separator__group">
<media-time type="current"></media-time>
<media-time-separator> of </media-time-separator>
<media-time type="duration"></media-time>
</media-time-group>
</video-player>
.html-time-custom-separator {
position: relative;
}
.html-time-custom-separator video {
width: 100%;
}
.html-time-custom-separator__group {
display: flex;
align-items: center;
gap: 4px;
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
import '@videojs/html/video/player';
import '@videojs/html/ui/time';
Custom Negative Sign
import { createPlayer, features, Time } from '@videojs/react';
import { Video } from '@videojs/react/video';
import './CustomNegativeSign.css';
const Player = createPlayer({ features: [...features.video] });
export default function CustomNegativeSign() {
return (
<Player.Provider>
<Player.Container className="react-time-custom-negative-sign">
<Video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoPlay
muted
playsInline
loop
/>
<Time.Value type="remaining" negativeSign="~" className="react-time-custom-negative-sign__value" />
</Player.Container>
</Player.Provider>
);
}
.react-time-custom-negative-sign {
position: relative;
}
.react-time-custom-negative-sign video {
width: 100%;
}
.react-time-custom-negative-sign__value {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
<video-player class="html-time-custom-negative-sign">
<video
src="https://stream.mux.com/lhnU49l1VGi3zrTAZhDm9LUUxSjpaPW9BL4jY25Kwo4/highest.mp4"
autoplay
muted
playsinline
loop
></video>
<media-time
class="html-time-custom-negative-sign__value"
type="remaining"
negative-sign="~"
></media-time>
</video-player>
.html-time-custom-negative-sign {
position: relative;
}
.html-time-custom-negative-sign video {
width: 100%;
}
.html-time-custom-negative-sign__value {
position: absolute;
bottom: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
color: black;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 9999px;
padding-block: 8px;
padding-inline: 20px;
font-variant-numeric: tabular-nums;
}
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 | |
|---|---|---|---|
label | string | function | '' | |
| |||
negativeSign | string | '-' | |
| |||
type | 'current' | 'duration' | 'remaining' | 'current' | |
| |||
State
render, className, and style props.
| Property | Type | |
|---|---|---|
type | 'current' | 'duration' | 'remaining' | |
| ||
seconds | number | |
| ||
negative | boolean | |
| ||
text | string | |
| ||
phrase | string | |
| ||
datetime | string | |
| ||
Data attributes
| Attribute | Description |
|---|---|
data-type | The type of time being displayed. |
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.