custom-css

npm: @playerstack/plugin-custom-css

The escape hatch for any styling that controls-policy (theme color + show/hide controls) doesn’t cover. Drop in arbitrary CSS that only affects this <player-stack> instance.

Quickstart

<player-stack
  src="https://example.com/v.mp4"
  data-config='{
    "customCss": {
      "enabled": true,
      "css": "border-radius: 16px; box-shadow: 0 8px 24px rgba(0,0,0,0.4); & video { filter: contrast(1.1); }"
    }
  }'
></player-stack>

Config

config.customCss:

FieldTypeDefaultDescription
enabledbooleanfalsePlugin is a no-op unless true
cssstringRaw CSS, automatically scoped to this player instance via CSS Nesting. Use & to target descendants.

How scoping works

Your CSS is wrapped in player-stack[data-ps-id="<unique-id>"] { ... } and injected as a <style data-playerstack="custom-css"> in <head>. Each <player-stack> on the page gets its own scope, so styles never leak between instances.

The wrapper uses native CSS Nesting (browser baseline since 2023). Inside your CSS you can:

/* style the player root itself */
border-radius: 16px;

/* descend with & */
& video {
  filter: grayscale(1);
}

& media-control-bar {
  background: rgba(0, 0, 0, 0.8);
}

/* at-rules nest naturally */
@media (min-width: 800px) {
  & .playerstack-watermark {
    width: 120px;
  }
}

Common patterns

// Frosted control bar
"& media-control-bar { background: rgba(0,0,0,0.5); backdrop-filter: blur(8px); }";

// Bigger play button
"& media-play-button { --media-button-icon-width: 48px; }";

// Cinema mode (rounded + shadow)
"border-radius: 12px; overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,0.5);";

// Force a thicker progress bar
"& media-time-range { --media-range-track-height: 6px; }";

Security

The plugin rejects (renders nothing) when the CSS contains:

This plugin is meant for trusted CSS provided by the embedder (config provider, brand settings). Don’t pipe arbitrary user input into it without your own validation on top.

Coordinating with controls-policy

controls-policy already manages a data-ps-id attribute on the inner media element. custom-css reuses the same id — no conflict, no double-id collision.

Events

None.