Component Registry
Register named Alpine components for backend targeting and cross-component communication.
Overview
The Component Registry gives your Alpine components names, making them targetable from the backend and accessible from other components. This is essential for:
- Backend-driven updates to specific components
- Cross-component communication
- Coordinated state between multiple components
- Calling component methods from the server
Auto Cleanup
Registering Components
The x-component Directive
Add x-component to any Alpine component to register it:
<!-- Register a component named 'cart' -->
<div
x-data="{ items: [], total: 0 }"
x-component="cart">
<template x-for="item in items">
...
</template>
<p>Total: $<span x-text="total"></span></p>
</div>
<!-- Register a component named 'cart' -->
<div
x-data="{ items: [], total: 0 }"
x-component="cart">
<template x-for="item in items">
...
</template>
<p>Total: $<span x-text="total"></span></p>
</div>
Component Tags
Add data-tags to group related components:
<!-- Multiple components with shared tags -->
<div
x-data="{ items: [] }"
x-component="cart"
data-tags="commerce, checkout">
...
</div>
<div
x-data="{ count: 0 }"
x-component="mini-cart"
data-tags="commerce">
...
</div>
<!-- Multiple components with shared tags -->
<div
x-data="{ items: [] }"
x-component="cart"
data-tags="commerce, checkout">
...
</div>
<div
x-data="{ count: 0 }"
x-component="mini-cart"
data-tags="commerce">
...
</div>
The $components Magic
Access registered components from any Alpine expression using $components:
Basic Access
<!-- Get component data -->
<span x-text="$components.get('cart')?.name"></span>
<!-- Check if component exists -->
<div x-show="$components.has('cart')">Cart loaded</div>
<!-- Get components by tag -->
<span x-text="$components.getByTag('commerce').length"></span> commerce components
<!-- Get all components -->
<span x-text="$components.all().length"></span> total components
<!-- Get component data -->
<span x-text="$components.get('cart')?.name"></span>
<!-- Check if component exists -->
<div x-show="$components.has('cart')">Cart loaded</div>
<!-- Get components by tag -->
<span x-text="$components.getByTag('commerce').length"></span> commerce components
<!-- Get all components -->
<span x-text="$components.all().length"></span> total components
Reactive State Access
Access another component's reactive state with $components.state():
<!-- Display cart total from header -->
<header x-data>
<span>Cart: $<span x-text="$components.state('cart')?.total || 0"></span></span>
</header>
<!-- Access specific state key -->
<div x-data>
<span x-text="$components.state('cart', 'items')?.length || 0"></span> items
</div>
<!-- Display cart total from header -->
<header x-data>
<span>Cart: $<span x-text="$components.state('cart')?.total || 0"></span></span>
</header>
<!-- Access specific state key -->
<div x-data>
<span x-text="$components.state('cart', 'items')?.length || 0"></span> items
</div>
Updating State
<!-- Update another component's state -->
<button @click="$components.update('cart', { total: 99.99 })">
Set Total
</button>
<!-- Create initial state (safe for existing props) -->
<button @click="$components.create('cart', { discount: 0 }, { onlyIfMissing: true })">
Initialize Discount
</button>
<!-- Delete state properties -->
<button @click="$components.delete('cart', 'discount')">
Remove Discount
</button>
<!-- Update another component's state -->
<button @click="$components.update('cart', { total: 99.99 })">
Set Total
</button>
<!-- Create initial state (safe for existing props) -->
<button @click="$components.create('cart', { discount: 0 }, { onlyIfMissing: true })">
Initialize Discount
</button>
<!-- Delete state properties -->
<button @click="$components.delete('cart', 'discount')">
Remove Discount
</button>
Waiting for Components
When components load dynamically, use when() or onReady()
to safely access them:
<!-- Promise-based waiting -->
<button @click="$components.when('cart').then(c => console.log(c))">
Wait for Cart
</button>
<!-- With async/await -->
<button @click="async () => {
const cart = await $components.when('cart');
console.log('Cart ready:', cart);
}">
Await Cart
</button>
<!-- Callback-based (fires immediately if ready) -->
<div x-data x-init="$components.onReady('cart', c => console.log('Cart:', c))">
...
</div>
<!-- Wait for multiple components -->
<div x-data x-init="$components.onReady(['cart', 'user'], ({ cart, user }) => {
console.log('Both ready:', cart, user);
})">
...
</div>
<!-- Promise-based waiting -->
<button @click="$components.when('cart').then(c => console.log(c))">
Wait for Cart
</button>
<!-- With async/await -->
<button @click="async () => {
const cart = await $components.when('cart');
console.log('Cart ready:', cart);
}">
Await Cart
</button>
<!-- Callback-based (fires immediately if ready) -->
<div x-data x-init="$components.onReady('cart', c => console.log('Cart:', c))">
...
</div>
<!-- Wait for multiple components -->
<div x-data x-init="$components.onReady(['cart', 'user'], ({ cart, user }) => {
console.log('Both ready:', cart, user);
})">
...
</div>
Watching State Changes
React to state changes in other components with $components.watch():
<!-- Watch all state changes -->
<div x-data="{ cleanup: null }"
x-init="cleanup = $components.watch('cart', (state) => {
console.log('Cart changed:', state);
})"
x-destroy="cleanup?.()">
...
</div>
<!-- Watch specific key -->
<div x-data="{ cleanup: null }"
x-init="cleanup = $components.watch('cart', 'total', (newTotal, oldTotal) => {
console.log(`Total: ${oldTotal} -> ${newTotal}`);
})"
x-destroy="cleanup?.()">
...
</div>
<!-- Watch all state changes -->
<div x-data="{ cleanup: null }"
x-init="cleanup = $components.watch('cart', (state) => {
console.log('Cart changed:', state);
})"
x-destroy="cleanup?.()">
...
</div>
<!-- Watch specific key -->
<div x-data="{ cleanup: null }"
x-init="cleanup = $components.watch('cart', 'total', (newTotal, oldTotal) => {
console.log(`Total: ${oldTotal} -> ${newTotal}`);
})"
x-destroy="cleanup?.()">
...
</div>
Invoking Methods
Call methods on components using $components.invoke() or the shorthand $invoke:
<!-- The cart component -->
<div x-data="{
items: [],
total: 0,
addItem(product) {
this.items.push(product);
this.recalculate();
},
recalculate() {
this.total = this.items.reduce((sum, i) => sum + i.price, 0);
}
}" x-component="cart">
...
</div>
<!-- Invoke from another component -->
<button @click="$invoke('cart', 'addItem', { name: 'Widget', price: 29.99 })">
Add to Cart
</button>
<!-- Or using $components.invoke -->
<button @click="$components.invoke('cart', 'recalculate')">
Recalculate
</button>
<!-- The cart component -->
<div x-data="{
items: [],
total: 0,
addItem(product) {
this.items.push(product);
this.recalculate();
},
recalculate() {
this.total = this.items.reduce((sum, i) => sum + i.price, 0);
}
}" x-component="cart">
...
</div>
<!-- Invoke from another component -->
<button @click="$invoke('cart', 'addItem', { name: 'Widget', price: 29.99 })">
Add to Cart
</button>
<!-- Or using $components.invoke -->
<button @click="$components.invoke('cart', 'recalculate')">
Recalculate
</button>
Quick Reference
| Method | Description |
|---|---|
$components.get(name) |
Get component data by name |
$components.has(name) |
Check if component exists |
$components.getByTag(tag) |
Get all components with tag |
$components.all() |
Get all registered components |
$components.state(name, key?) |
Get reactive state proxy |
$components.update(name, updates) |
Update component state |
$components.create(name, state, opts?) |
Initialize state properties |
$components.delete(name, keys) |
Remove state properties |
$components.when(name, timeout?) |
Promise that resolves when ready |
$components.onReady(name, callback) |
Callback when component ready |
$components.watch(name, key?, cb) |
Watch for state changes |
$invoke(name, method, ...args) |
Call component method |