Corrective Action: Mux Player Web Component Fails When Rendered via React Props

Corrective Action: Mux Player Web Component Fails When Rendered via React Props

Date: April 3, 2026 Category: Tool Limitation / Data Format Mismatch Impact: 4 "Error NaN" headings visible in DOM + screen readers; 3 video thumbnails rendered as black rectangles on the AI-Native Shift pillar page; credibility issue on a page about AI-native operations Resolution Time: ~2 hours across multiple commits (FR-1/FR-2 fix, then thumbnail fix after Chris flagged black frames)


Incident

What Happened

The AI-Native Shift pillar page (/ai-native-shift) embedded 4 Mux video players — 1 in the hero (Astro template) and 3 in methodology cards (React component). The hero video worked correctly. The 3 methodology card videos rendered with "Error NaN" headings in the DOM and displayed black rectangles instead of video poster thumbnails. Mirror's audit flagged 4 "Error NaN" h3 elements and 8 broken/empty images.

Timeline

Time Event
Apr 1, evening Initial page build with 4 Mux players — hero (Astro) + 3 methodology cards (Astro with {methodologyStack.map()})
Apr 2, morning Multimedia enhancement commit adds Mux players to methodology section
Apr 2, afternoon Mirror audit detects 4 "Error NaN" h3 elements + 8 empty-src images
Apr 3, morning FR-1/FR-2 fix: added preload="none" + explicit poster URLs + CSS to suppress Error NaN
Apr 3, afternoon Methodology cards rewritten as React component (MethodologyCards.tsx). Videos hidden behind expand — Chris can't see them
Apr 3, evening Chris requests two-column layout with videos always visible. Rewritten. Videos show black thumbnails.
Apr 3, evening Chris screenshots black rectangles. Root cause identified: React className prop doesn't pass through to web components. Fix: dangerouslySetInnerHTML with native HTML attributes.

Root Cause

Two distinct but related failures:

Failure 1: Error NaN Headings

Mux Player renders a duration/time display in its shadow DOM. When preload is not set (default: auto), the player attempts to load video metadata immediately. If metadata loading fails or is slow, the duration display renders "Error NaN" as an h3 element visible to screen readers and DOM inspectors.

Failure 2: Black Thumbnail Rectangles

When <mux-player> is rendered inside a React component via JSX, React treats it as a custom element but passes props using React conventions:

  • className="w-full h-full" — web components expect class, not className. The attribute is silently ignored.
  • poster={url} — React may serialize this as a property assignment rather than an HTML attribute. The Mux player web component reads poster as an HTML attribute, not a JS property.
  • accent-color, playback-id, metadata-video-title — these hyphenated attributes work in Astro (native HTML) but may have inconsistent behavior in React's JSX prop passing for custom elements.

The result: the Mux player received no styling (class/style missing), no poster image, and attempted to auto-load metadata that failed — producing black frames with Error NaN.

Category: Tool Limitation / Data Format Mismatch

This is a known gap in React's handling of web components. React does not map className to class for custom elements the way it does for native HTML elements. The React team has documented this as a known limitation (React 19 improves this, but we're on React 18).

The Astro-rendered hero video worked because Astro outputs native HTML attributes directly — no React prop transformation.


Fix Applied

Immediate Resolution (FR-1/FR-2 — partial)

Added preload="none" and explicit poster URLs to all 4 Mux players in the Astro template. Added CSS to suppress Error NaN:

mux-player [slot="time-display"],
mux-player [slot="duration"] {
  display: none !important;
}

This fixed the Astro-rendered hero video but did not fix the React-rendered methodology cards.

Full Resolution (thumbnail fix)

Replaced React JSX <mux-player> elements with dangerouslySetInnerHTML rendering native HTML:

dangerouslySetInnerHTML={{
  __html: `<mux-player
    playback-id="${item.playbackId}"
    metadata-video-title="${item.name}"
    accent-color="${item.color}"
    preload="none"
    poster="https://image.mux.com/${item.playbackId}/thumbnail.webp?time=5"
    style="width:100%;height:100%"
  ></mux-player>`
}}

Code/Configuration Changes

File Change
apps/website/src/pages/ai-native-shift.astro Added preload="none" + poster URLs to hero mux-player; added CSS to suppress Error NaN slots
apps/website/src/components/ai-native-shift/MethodologyCards.tsx Replaced JSX <mux-player> with dangerouslySetInnerHTML for native HTML attribute passing

Verification

Chris confirmed poster thumbnails display correctly after the dangerouslySetInnerHTML fix was deployed.


Prevention Measures

Rules Added

Layer File Rule
Critical Lessons memory/MEMORY.md NEVER render <mux-player> (or any web component) via React JSX props. Use dangerouslySetInnerHTML with native HTML attributes. React 18 does not map className to class for custom elements. Fixed Apr 3 after black video thumbnails on /ai-native-shift.
Enforcement skills/enforcement/vf-self-correction.md Detection trigger: if writing JSX that includes <mux-player with className=, STOP — web component will not receive the class attribute.
Tech Stack skills/enforcement/vf-tech-stack.md Document: Mux Player is a web component loaded via CDN script. In Astro templates, use native HTML attributes. In React components, use dangerouslySetInnerHTML. Always set preload="none" and explicit poster URL.

Detection Triggers

If V or any agent writes a React component that includes <mux-player, <mux-video, or any hyphenated custom element tag with className=:

  1. STOP — this is the web component / React prop mismatch
  2. Use dangerouslySetInnerHTML instead
  3. Use native HTML attributes (class, style, poster) not React props (className, style object)

This also applies to any other web component used in React: <sl-*> (Shoelace), <ion-*> (Ionic), etc.


Lessons

Web components and React have a fundamental impedance mismatch. React treats custom elements as "unknown HTML elements" and does not apply its prop-to-attribute mapping (like classNameclass) for them. This means any web component rendered via React JSX will silently drop className, and may incorrectly serialize other props.

The safe pattern: when embedding web components in React, always use dangerouslySetInnerHTML with a template string containing native HTML. This bypasses React's prop system entirely and gives the web component exactly what it expects.

In Astro templates (.astro files), web components work correctly because Astro outputs native HTML without transformation.


Related Incidents

  • Unicode escape double-encoding (Mar 16): Similar pattern — build tool (Vite/esbuild) transforming source code in unexpected ways. The lesson is the same: when a tool transforms your output, verify the output matches expectations, don't trust the input.
  • CRLF line endings (Mar 8): Another "silent transformation" bug — the tool (Windows) silently changed the data format, causing downstream failures with no visible error.