v3 removes the Shadow DOM and renders component content into the light DOM. The directive API is otherwise unchanged.
Breaking
x-component-styles and its styles alias are gone, along with src/styles.js. Document styles now reach component content on their own, so there is nothing to opt into.
<!-- v2 -->
<div x-component="'person-card'" x-component-styles="person-card"></div>
<!-- v3 -->
<div x-component="'person-card'"></div>If you relied on the shadow boundary to keep page styles out of a component, scope your CSS with @scope or a class convention instead.
Templates, .url, .external, x-slot, and the lifecycle events all carry over unchanged. See Migration From v2 for the full notes.
What dropping the shadow boundary gains you
- Page styles apply to component content with no configuration
$refsand$rootresolve across the host boundarylabel[for],aria-describedby, and friends can reference component content- Form controls inside a component submit with an ancestor
<form> document.querySelectorfinds component content
<slot> is now resolved by manual projection rather than natively, since there is no shadow root to do it.
Known limitations
Both are documented in the README and pinned by tests on purpose:
- A
<slot>insidex-fororx-ifevaluates in the component's scope instead of the host's - A throwing directive expression emits no
x-component:error
Internals
src/ is split by responsibility — directive wiring, source resolution, template loading, slots, render, events, caching — instead of one index.js. Remote templates are parsed once and the caches are bounded.
Tooling
- CI on every push and pull request: lint, build, a committed-output drift check, and the Playwright suite
- The suite runs on Chromium, Firefox, and WebKit
- Coverage for CSP and Trusted Types
- esbuild bumped to 0.28.1
Full Changelog: v2.0.3...v3.0.0