Files
v10/packages/element
Wesley LuytenGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
da598b285a chore: release main (#1313)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-14 15:58:18 -05:00
..
2026-04-14 15:58:18 -05:00
2026-04-14 15:58:18 -05:00

@videojs/element

package-badge

⚠️ Beta Close to stable. Experimental adoption in real projects.

A lightweight reactive custom element base class for Video.js. Type-aligned with Lit's ReactiveElement but stripped down to only what we use.

npm install @videojs/element

Why?

Video.js web components used @lit/reactive-element but only needed a fraction of its API — reactive properties, batched updates, and controllers. The rest (Shadow DOM, static styles, decorators, complex attribute converters, shouldUpdate, custom scheduling) shipped as dead code (~2.8 kB brotli).

@videojs/element provides the same programming model at ~840 B brotli.

Quick Start

import { ReactiveElement } from '@videojs/element';
import type { PropertyValues } from '@videojs/element';

class MyElement extends ReactiveElement {
  static override properties = {
    label: { type: String },
    disabled: { type: Boolean },
  };

  label = 'Click me';
  disabled = false;

  protected override update(changed: PropertyValues): void {
    super.update(changed);
    this.textContent = this.label;
  }
}

customElements.define('my-element', MyElement);

Alignment with Lit

The API is a subset of Lit's ReactiveElement. Types are aligned so controllers written for Lit work with @videojs/element without changes.

What's included

  • static properties — Declare reactive properties with type (String, Boolean, Number) and attribute (custom attribute name)
  • Reactive accessors — Installed automatically, change detection via Object.is()
  • Batched updates — Multiple property changes in one tick trigger a single update via queueMicrotask()
  • Full lifecyclewillUpdateupdatefirstUpdated (first time) → updatedupdateComplete
  • hasUpdatedfalse until first update completes, true during firstUpdated and updated (matches Lit)
  • isUpdatePendingtrue while an update is queued or in progress
  • performUpdate() — Synchronously flush a pending update
  • scheduleUpdate() — Override point for custom update timing (default calls performUpdate())
  • Reactive controllersaddController/removeController with hostConnected, hostDisconnected, hostUpdate, hostUpdated
  • Element upgrade handling — Properties set before registration are preserved

What's NOT included

Lit feature Why excluded
Shadow DOM / createRenderRoot() We use light DOM exclusively
static styles / CSS adoption No shadow root to adopt into
Decorators (@property, @state) We use static properties
shouldUpdate() No use case for skipping updates
getUpdateComplete() No async update chaining needed
reflect option No property-to-attribute reflection
converter option Simple type coercion is sufficient
state option All properties are observable
hasChanged option Object.is() is always used

Property inheritance

Lit walks the prototype chain to collect properties from all ancestors. We don't — subclasses that define their own static properties must spread the parent:

class FancyButton extends MyButton {
  static override properties = {
    ...MyButton.properties,
    variant: { type: String },
  };
}

This is only needed when a subclass declares static properties. If it doesn't, JS static property inheritance means the parent's properties are used automatically.

Context

Context is re-exported from @lit/context — the same implementation used across the Lit ecosystem:

import { createContext, ContextProvider, ContextConsumer } from '@videojs/element/context';

This provides tree-scoped data sharing without prop drilling, using Lit's Context Protocol.

Community

If you need help with anything related to Video.js v10, or if you'd like to casually chat with other members:

License

Apache-2.0