Start
//

The ef-controls element bridges playback context from a target element to controls placed anywhere in the DOM, enabling flexible UI layouts that aren't constrained by DOM hierarchy.

The Problem with Direct Nesting

Control elements like ef-toggle-play, ef-scrubber, and ef-time-display work by reading playback context from an ancestor element — specifically an ef-timegroup, ef-video, or ef-preview. This means the simplest setup is to nest controls directly inside the composition:

<ef-timegroup mode="contain" class="w-full h-96" id="player">
<ef-video src="/video.mp4" class="size-full"></ef-video>
<!-- Works: controls are descendants of the timegroup -->
<div class="absolute bottom-4 left-4 flex gap-2">
<ef-toggle-play>
<button slot="play">Play</button>
<button slot="pause">Pause</button>
</ef-toggle-play>
</div>
</ef-timegroup>

Direct nesting works, but it constrains your layout. UI designs often require controls in separate areas — a sticky footer, a sidebar, a modal, or a completely different part of the component tree. Direct nesting prevents this.

The Solution: ef-controls

ef-controls bridges context from a target element to controls placed anywhere in the DOM. Controls inside ef-controls receive the same playback context as if they were nested directly inside the target, enabling flexible layouts without sacrificing functionality.

<ef-timegroup id="player" mode="contain" class="w-full h-96">
<ef-video src="/video.mp4" class="size-full"></ef-video>
</ef-timegroup>
<!-- Controls are outside the timegroup — anywhere on the page -->
<div class="fixed bottom-0 left-0 right-0 bg-black p-4">
<ef-controls target="player" class="flex justify-center gap-4">
<ef-toggle-play>
<button slot="play"></button>
<button slot="pause"></button>
</ef-toggle-play>
<ef-scrubber class="flex-1"></ef-scrubber>
<ef-time-display></ef-time-display>
</ef-controls>
</div>

The target attribute takes the id of the element to control. Everything inside ef-controls behaves identically to controls nested directly inside that element.

What Context Is Bridged

ef-controls provides these contexts to its children:

  • playing — Current playback state (boolean)
  • loop — Loop state (boolean)
  • currentTimeMs — Current playhead position in milliseconds
  • durationMs — Total duration in milliseconds
  • targetTemporal — Reference to the controlled element
  • focusedElement — Currently focused element

Child elements like ef-toggle-play, ef-toggle-loop, ef-scrubber, and ef-time-display automatically consume these contexts. You don't need to wire anything up manually.

What ef-controls Can Target

ef-controls works with any temporal element:

  • ef-timegroup — Control a full composition
  • ef-video — Control a single video element
  • ef-audio — Control a single audio element
  • ef-preview — Control via the preview wrapper

Multiple Control Sets

Multiple ef-controls elements can target the same composition. All sets stay synchronized — a seek in one will update the time display in all others:

<ef-timegroup id="main" mode="contain" class="w-full h-96">
<!-- composition -->
</ef-timegroup>
<!-- Desktop control bar -->
<ef-controls target="main" class="desktop-controls flex items-center gap-4 p-4">
<ef-toggle-play>...</ef-toggle-play>
<ef-scrubber class="flex-1"></ef-scrubber>
<ef-time-display></ef-time-display>
</ef-controls>
<!-- Mobile control bar -->
<ef-controls target="main" class="mobile-controls flex items-center gap-2 p-2">
<ef-toggle-play>...</ef-toggle-play>
<ef-scrubber class="flex-1"></ef-scrubber>
</ef-controls>

Live Demo

Controls placed outside the timegroup work identically to nested controls:

<ef-timegroup mode="contain" class="w-[720px] h-[405px] bg-black" id="context-bridge-demo">
<ef-video src="https://assets.editframe.com/bars-n-tone.mp4" sourceout="2s" class="size-full object-contain"></ef-video>
</ef-timegroup>
<ef-controls target="context-bridge-demo" class="mt-4 flex items-center gap-4 p-4 bg-gray-900 rounded">
<ef-toggle-play>
<button slot="play" class="px-4 py-2 bg-blue-600 text-white rounded"></button>
<button slot="pause" class="px-4 py-2 bg-orange-600 text-white rounded"></button>
</ef-toggle-play>
<ef-time-display class="text-white"></ef-time-display>
<ef-scrubber class="flex-1"></ef-scrubber>
</ef-controls>

When You Don't Need ef-controls

If your controls are already descendants of the timegroup, you don't need ef-controls at all — the context flows down through the DOM naturally:

<ef-timegroup mode="contain" class="w-full h-96 relative">
<ef-video src="/video.mp4" class="size-full"></ef-video>
<!-- No ef-controls needed — these are descendants of the timegroup -->
<div class="absolute bottom-0 left-0 right-0 p-4 flex gap-3">
<ef-toggle-play>
<button slot="play">Play</button>
<button slot="pause">Pause</button>
</ef-toggle-play>
<ef-scrubber class="flex-1"></ef-scrubber>
</div>
</ef-timegroup>

Use ef-controls only when the controls and their target are not in an ancestor–descendant relationship.

Practical Benefits

Flexible layouts: Build interfaces where the video preview is in the main content area and controls are in a sticky footer, sidebar, or modal dialog. Position controls based on your design needs, not DOM structure.

Portal-based rendering: Works with React portals, Vue teleports, and similar rendering patterns. Controls rendered outside the normal component tree remain connected to the preview through ef-controls.

Multi-location control: Build applications where the same video is controlled from multiple places — for example, both an inline control bar and a floating mini-player. All sets stay synchronized.

Separation of concerns: Keep preview and control markup separate in your component structure while maintaining tight synchronization. This improves code organization and makes layouts easier to reason about.