Controls
Playback control elements. Compose them freely against any timegroup.
Controls are unstyled building blocks — each one wires a single playback concern to a target timegroup. Use ef-controls as a context provider so child controls share a target without individual target attributes, or wire each element directly with target.
<ef-timegroup id="demo" mode="fixed" duration="5s" loop class="w-[1920px] h-[1080px] bg-slate-900 flex items-center justify-center">
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
.demo-text {
animation: pulse 2.4s ease-in-out infinite;
display: inline-block;
}
</style>
<ef-text class="demo-text text-white text-7xl font-bold">Hello</ef-text>
</ef-timegroup>
<ef-controls target="demo">
<div class="ctrl-grid">
<span class="ctrl-label">ef-toggle-play</span>
<div class="ctrl-cell">
<ef-toggle-play>
<button slot="play" class="ctrl-btn">▶ Play</button>
<button slot="pause" class="ctrl-btn">⏸ Pause</button>
</ef-toggle-play>
</div>
<span class="ctrl-label">ef-play</span>
<div class="ctrl-cell ctrl-pair">
<ef-play><button class="ctrl-btn">▶ Play</button></ef-play>
</div>
<span class="ctrl-label">ef-pause</span>
<div class="ctrl-cell ctrl-pair">
<ef-pause><button class="ctrl-btn">⏸ Pause</button></ef-pause>
</div>
<span class="ctrl-label">ef-scrubber</span>
<div class="ctrl-cell">
<ef-scrubber class="ctrl-scrubber"></ef-scrubber>
</div>
<span class="ctrl-label">ef-time-display</span>
<div class="ctrl-cell">
<ef-time-display class="ctrl-time"></ef-time-display>
</div>
<span class="ctrl-label">ef-toggle-loop</span>
<div class="ctrl-cell">
<ef-toggle-loop data-loop-on onclick="this.toggleAttribute('data-loop-on')">
<button class="ctrl-btn ctrl-loop-btn" title="Toggle loop">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<polyline points="17 1 21 5 17 9"></polyline>
<path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
<polyline points="7 23 3 19 7 15"></polyline>
<path d="M21 13v2a4 4 0 0 1-4 4H3"></path>
</svg>
Loop
</button>
</ef-toggle-loop>
</div>
<span class="ctrl-label">ef-volume</span>
<div class="ctrl-cell">
<ef-volume class="ctrl-volume"></ef-volume>
</div>
<span class="ctrl-label">ef-mute</span>
<div class="ctrl-cell">
<ef-mute>
<button slot="unmuted" class="ctrl-btn">🔊 Mute</button>
<button slot="muted" class="ctrl-btn">🔇 Unmute</button>
</ef-mute>
</div>
<span class="ctrl-label">ef-fullscreen</span>
<div class="ctrl-cell">
<ef-fullscreen target="demo">
<button slot="enter" class="ctrl-btn">⛶ Fullscreen</button>
<button slot="exit" class="ctrl-btn">✕ Exit</button>
</ef-fullscreen>
</div>
<span class="ctrl-label">ef-pip</span>
<div class="ctrl-cell">
<ef-pip target="demo">
<button slot="enter" class="ctrl-btn">⧉ Picture in Picture</button>
<button slot="exit" class="ctrl-btn">✕ Exit PiP</button>
</ef-pip>
</div>
<span class="ctrl-label">ef-resolution</span>
<div class="ctrl-cell ctrl-resolution">
<ef-resolution target="demo"></ef-resolution>
</div>
</div>
</ef-controls>
<style>
.ctrl-grid {
display: grid;
grid-template-columns: max-content 1fr;
grid-auto-rows: 44px;
align-items: center;
border-top: 1px solid var(--border-subtle);
}
.ctrl-label {
font-size: 11px;
font-family: monospace;
color: var(--chrome-fg-muted);
white-space: nowrap;
padding: 0 12px 0 16px;
height: 100%;
display: flex;
align-items: center;
border-bottom: 1px solid var(--border-subtle);
}
.ctrl-cell {
display: flex;
align-items: center;
padding: 0 16px 0 0;
height: 100%;
border-bottom: 1px solid var(--border-subtle);
}
.ctrl-pair {
gap: 8px;
}
.ctrl-btn {
cursor: pointer;
padding: 4px 12px;
font-size: 13px;
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--chrome-fg);
background: transparent;
border: 1px solid var(--border-subtle);
border-radius: 4px;
}
.ctrl-btn:hover {
border-color: var(--chrome-fg-muted);
}
.ctrl-loop-btn {
color: var(--chrome-fg-muted);
border-style: dashed;
transition: background 0.15s, color 0.15s, border-color 0.15s, border-style 0.15s;
}
ef-toggle-loop[data-loop-on] .ctrl-loop-btn {
background: var(--chrome-fg);
color: var(--chrome-bg);
border-color: var(--chrome-fg);
border-style: solid;
}
.ctrl-scrubber {
flex: 1;
--ef-scrubber-background: var(--chrome-fg-muted);
--ef-scrubber-progress-color: var(--chrome-fg);
}
.ctrl-time {
font-variant-numeric: tabular-nums;
color: var(--chrome-fg);
}
.ctrl-volume {
flex: 1;
max-width: 180px;
accent-color: var(--chrome-fg);
}
.ctrl-resolution {
height: auto;
min-height: 44px;
padding-top: 6px;
padding-bottom: 6px;
}
ef-resolution select,
ef-resolution input {
background: transparent;
color: var(--chrome-fg);
border: 1px solid var(--border-subtle);
border-radius: 4px;
padding: 3px 8px;
font-size: 13px;
}
</style>Elements
| Element | Description |
|---|---|
ef-controls | Context provider — wraps controls and connects them to a target timegroup |
ef-toggle-play | Single button that swaps between play and pause slots |
ef-play | Play-only button — hides itself when already playing |
ef-pause | Pause-only button — hides itself when already paused |
ef-scrubber | Seek slider — drag to scrub through the composition |
ef-time-display | Live currentTime / duration readout |
ef-toggle-loop | Flips the loop property on click — bring your own UI |
ef-volume | Range slider (0–1) controlling playback volume; auto-unmutes when raised from zero. Customisable via CSS custom properties. |
ef-mute | Toggle button for muting/unmuting — unmuted and muted slots |
ef-fullscreen | Enters/exits browser fullscreen on a target element — enter and exit slots |
ef-pip | Floats a video or canvas in the browser's native Picture-in-Picture window — enter and exit slots |
ef-resolution | Preset dropdown and custom inputs for setting a timegroup's output dimensions |
Customising ef-volume
ef-volume exposes CSS custom properties for styling the track without touching shadow DOM:
| Property | Default | Description |
|---|---|---|
--ef-volume-height | 4px | Track and thumb height |
--ef-volume-track-color | rgba(255,255,255,0.2) | Unfilled track color |
--ef-volume-fill-color | white | Filled (progress) track color |
<ef-volume style="
--ef-volume-height: 3px;
--ef-volume-track-color: rgba(255,255,255,0.15);
--ef-volume-fill-color: #e53935;
"></ef-volume>
In React:
<Volume
className="w-16"
style={{
"--ef-volume-height": "3px",
"--ef-volume-track-color": "rgba(255,255,255,0.15)",
"--ef-volume-fill-color": "var(--brand-red)",
} as React.CSSProperties}
/>