Reusable HTML components powered by Alpine JS reactivity.
โจ Demos: https://alpinejs-component-demo.vercel.app/.
v2 is directive-based and built around x-component.
- No custom element registration required
- Supports on-page templates and remote templates
- Renders into Shadow DOM for style encapsulation
- Supports default and named slots from host templates
- Emits lifecycle events for loading, loaded, and error states
- Uses bounded caches for templates, remote responses, and stylesheets
<script
defer
src="https://unpkg.com/alpinejs-component@latest/dist/component.min.js"
></script>
<script defer src="https://unpkg.com/alpinejs@latest/dist/cdn.min.js"></script>npm install alpinejs-component
yarn add alpinejs-component
pnpm add alpinejs-componentimport Alpine from 'alpinejs'
import component from 'alpinejs-component'
Alpine.plugin(component)
Alpine.start()v2 uses an Alpine directive: x-component.
x-component="expression": render from an on-page<template id="...">x-component.url="expression": render from a URLx-component.url.external="expression": allow cross-originhttp(s)URLsx-component-styles="title-a,title-b": include matching document stylesheetsstyles="...": alias forx-component-styles
The directive expression can be static or dynamic. Values are normalized as:
string: trimmed and used directlynumber/boolean/ other primitives: converted withString(...)null/undefined/ empty string: treated as empty source
When the resolved source is empty, the mounted component is unmounted/cleared.
<div
x-data="{
people: [
{ name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
{ name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
]
}"
>
<ul>
<template x-for="person in people" :key="person.name">
<li>
<div x-data="{ item: person }" x-component="'person-card'"></div>
</li>
</template>
</ul>
</div>
<template id="person-card">
<article>
<h2 x-text="item.name"></h2>
<p x-text="item.age"></p>
<ul>
<template x-for="skill in item.skills" :key="skill">
<li x-text="skill"></li>
</template>
</ul>
</article>
</template>Use the .url modifier when the expression resolves to a URL.
By default, .url only allows http(s) URLs on the current origin. Add the
.external modifier to allow cross-origin http(s) URLs.
<div
x-data="{
people: [
{ name: 'John', age: '25', skills: ['JavaScript', 'CSS'] },
{ name: 'Jane', age: '30', skills: ['Laravel', 'MySQL', 'jQuery'] }
]
}"
>
<ul>
<template x-for="person in people" :key="person.name">
<li>
<div
x-data="{ item: person }"
x-component.url="'/public/person-card.html'"
></div>
</li>
</template>
</ul>
</div>x-component and x-component.url support dynamic expressions.
<div
x-data="{
view: 'person-card',
remoteView: '/public/person-card.html'
}"
>
<section x-component="view"></section>
<section x-component.url="remoteView"></section>
</div>Rendered component content is mounted in a Shadow DOM root.
Use x-component-styles (or styles) to include selected document stylesheets
by title.
<style title="person-card">
article {
border: 1px solid #ddd;
}
</style>
<div x-component="'person-card'" x-component-styles="person-card"></div>Use global to include all local stylesheets:
<div x-component="'person-card'" x-component-styles="global"></div>Slot templates can be declared on the host element with x-slot.
<div x-component="'card-with-slot'">
<template x-slot>
<p>Default slot content</p>
</template>
<template x-slot="actions">
<button>Save</button>
</template>
</div>
<template id="card-with-slot">
<article>
<slot></slot>
<footer>
<slot name="actions"></slot>
</footer>
</article>
</template>The host element emits lifecycle events:
x-component:loadingwhen URL loading startsx-component:loadedwhen render completesx-component:errorwhen expression evaluation, loading, or rendering fails
Event detail payloads:
x-component:loading:{ source }x-component:loaded:{ source }x-component:error:{ source, error }
source is the resolved template id/URL for load/render failures, and the raw
directive expression for expression-evaluation failures.
Evaluation failure behavior:
- If directive expression evaluation throws, the plugin emits
x-component:errorwith the evaluation error. - The component source is treated as empty, so any currently mounted content is cleared.
<div
x-component.url="'/public/person-card.html'"
x-on:x-component:loaded="console.log('component ready', $event.detail)"
x-on:x-component:error="console.error('component failed', $event.detail)"
></div>Important: Only load templates from trusted sources. This plugin:
- Renders HTML content directly (no sanitization)
- Performs minimal URL validation (only
http(s)and same-origin by default) - Is designed for developer-controlled content
Your responsibility:
- Don't use user input directly in
x-componentorx-component.url - Only load templates from your own trusted servers
- Validate/sanitize any dynamic template selection
- Use CSP headers for additional protection
x-component.urlaccepts onlyhttp(s)URLsx-component.urlblocks cross-origin requests by default- Use
x-component.url.externalto opt into cross-originhttp(s)requests
This plugin targets modern browsers with support for:
- Shadow DOM
adoptedStyleSheets(when usingx-component-styles/styles)CSSStyleSheet(when usingx-component-styles/styles)template.content
If your target environment lacks these APIs, use a compatibility strategy or avoid Shadow DOM style adoption features.
The plugin maintains bounded in-memory caches:
- Template fragments by template id (limit: 200)
- Remote template fetch promises by normalized URL (limit: 200)
- Adopted stylesheets by style target list (limit: 100)
When a cache exceeds its limit, oldest entries are evicted.
For URL mode, failed fetches are removed from cache so retries can succeed.
npm install
npm run buildAvailable scripts:
npm run build: lint then build minified CDN + ESM outputs indist/npm run lint: run ESLint with--fix
- Missing templates and failed URL requests are handled with console warnings/errors and lifecycle error events.
- Expression evaluation failures dispatch
x-component:errorand clear mounted content for that host. - URL responses are cached by URL.
- Template fragments are cached by template id.
- Stylesheets are cached by style target list.
v1:
<x-component template="person"></x-component>
<x-component url="/public/person.html"></x-component>v2:
<div x-component="'person'"></div>
<div x-component.url="'/public/person.html'"></div>window.xComponent.name custom-element renaming is no longer used because v2 is
directive-based.