Skip to content

Bug: Rounded Skins Composite the Black Stage Into Edge Pixels - Dark Hairline Around the Player Over Bright Content #2496

Description

@agendasys

Is there an existing issue for this?

  • I have searched the existing issues

Which package(s) or projects are affected?

React (@videojs/react)

Browser/OS/Node environment

Browser: Chromium 148.0.0.0 (headless, via Playwright), DPR 1 - also seen in desktop Chrome on a Retina display (DPR 2), where the fractional-device-pixel variant shows along straight edges too
OS: Linux x86_64 (kernel 6.8)
Node version: 24.18.0 (not relevant — rendering bug, no CLI involvement)
npm version: 11.16.0

Description

Current behavior

The video skins round every layer independently: the skin root uses border-radius plus
overflow: clip, and the light-DOM <video> and poster <img> take border-radius: inherit.
The root also paints a hardcoded opaque stage, background: oklch(0 0 0) on
.media-default-skin--video, with no CSS variable to change it.

Each rounded edge is antialiased separately. A corner boundary pixel therefore composites as
content plus black plus page, instead of content plus page: at a half-covered pixel roughly
30 to 40 percent black is mixed in. Over bright content this renders as a thin dark arc tracing
every corner. Over dark content it is invisible, which makes it look intermittent.

In real layouts (percentage sizing, DPR above 1, the composited video layer rasterizing at
fractional device offsets) the same contamination also shows along straight edges as a faint
line around the whole player. Sampling a production page: page pixel 248, boundary pixel 122,
adjacent content pixel 144. The boundary pixel is darker than both of its neighbours, which
rules out ordinary antialiasing between the two.

Expected behavior

Edge pixels of a rounded player blend content directly into the page with no dark rim.

Minimal reproduction (no player needed)

<body style="background:#f8f8f8; margin:40px">
  <!-- BUG: mirrors the skin structure: rounded clip + black stage, inner content border-radius: inherit -->
  <div style="width:640px; aspect-ratio:16/9; border-radius:14px;
              overflow:clip; background:oklch(0 0 0)">
    <div style="width:100%; height:100%; border-radius:inherit; background:#fff"></div>
  </div>
  <!-- FIXED: single clip-path mask, no internal rounding -->
  <div style="width:640px; aspect-ratio:16/9;
              clip-path:inset(0 round 14px); overflow:clip; background:oklch(0 0 0)">
    <div style="width:100%; height:100%; background:#fff"></div>
  </div>
</body>

Zoom into the first box's corners: dark arc. The second box is the identical stack rounded once
by a clip-path mask, and its corners are clean.

Workaround we ship in production

Zero the skin's internal rounding natively (--media-border-radius: 0 on the skin root, the same
state the skin itself uses in fullscreen) and round once on our wrapper with
clip-path: inset(0 round 14px), a single mask over the finished composite. Verified pixel-level.

Note this is not the clip-path variant dismissed in #1449. That test used inset(...) without
round, which is why it broke the corners there, and #1449 itself is a different bug
(WebKit aspect-ratio sub-pixel gap).

Suggested fix (either one removes the need for the workaround)

  1. Round the skins with a single mask, clip-path: inset(0 round var(--media-border-radius))
    on the root, instead of per-layer border-radius and overflow rounding. Fullscreen already
    zeroes the radius, and top-layer elements escape ancestor clips, so it composes cleanly.
  2. And/or expose the stage color as background: var(--media-background, oklch(0 0 0)) on the
    skin roots, so integrators can match the stage to the page for seamless or decorative
    placements such as hero films.

Affects the default video, minimal, and live skins. Present in 10.0.0-beta.26 and unchanged
through 10.0.0-beta.32
. The affected skin CSS (dist/*/presets/*/skin.css) is shared with the
HTML package, so this applies there too.

Reduced test case

No response

Steps to reproduce

  1. Render bright video content (a white intro card is worst case) in any v10 skin with --media-border-radius set, on a light page. Alternatively use the 15-line HTML in the description, which mirrors the skin's layer structure without a player.
  2. Zoom into any corner of the player, with browser zoom or a magnified screenshot.
  3. Observe a thin dark arc tracing the rounded corner between the content and the page. Sample the pixels: the boundary pixel is darker than both the content and the page next to it.
  4. Repeat with dark content: the arc becomes invisible, confirming it is the black stage bleeding through the antialiased rounded edges, not the content itself.

Current Behavior

A thin dark hairline traces the player's rounded corners over bright content, because every layer (skin root via border-radius plus overflow: clip, light-DOM video and poster img via border-radius: inherit) is antialiased separately over the hardcoded opaque stage (background: oklch(0 0 0), no variable). On fractional device-pixel layouts (DPR 2, percentage sizing) the line also shows along the straight edges, around the whole player.

Expected Behavior

Edge pixels of a rounded player blend the content directly into the page with no dark rim, as produced by rounding the composed player once (for example clip-path: inset(0 round var(--media-border-radius)) on the root), or by letting integrators set the stage color via a variable so it can match the page.

Errors

No response

What version of the package are you using?

v10.0.0-beta.26

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions