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>
  </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);
  }
</style>

Elements

ElementDescription
ef-controlsContext provider — wraps controls and connects them to a target timegroup
ef-toggle-playSingle button that swaps between play and pause slots
ef-playPlay-only button — hides itself when already playing
ef-pausePause-only button — hides itself when already paused
ef-scrubberSeek slider — drag to scrub through the composition
ef-time-displayLive currentTime / duration readout
ef-toggle-loopFlips the loop property on click — bring your own UI