Reusable components for Alpine.js

Components for Alpine.js

Define reusable template components in their own files and render them anywhere with a single directive — no build step required.

components/Counter.js
// Define your component once
export default {
  template: `
    <div x-data="__self__">
      <p>Count: <span x-text="count"></span></p>
      <button @click="increment">+</button>
    </div>
  `,
  data: () => ({ count: 0, increment() { this.count++ } })
}
index.html
<!-- Use it anywhere -->
<div x-component="'Counter'"></div>
x-component

Render any component inline by name

Isolated state

Each component manages its own Alpine data

No build step

Works with CDN or any Alpine.js setup

Interactive Demo

Components defined in separate files, rendered via x-component.

Counter

from components/Counter.js

x-component="'Counter'"

Dropdown

from components/Dropdown.js

x-component="'Dropdown'"

Modal

from components/Modal.js

x-component="'Modal'"

Accordion

from components/Accordion.js

x-component="'Accordion'"
/
directive

x-component

Looks up a registered component by name, sets the element's innerHTML to its template, registers its data factory with Alpine, then calls Alpine.initTree() on the element.

Usage

index.html
<div x-component="'Counter'"></div>

Component format

A component is a plain JS module that exports a template string and a data factory function.

src/components/Counter.js
export default {
  template: `
    <div x-data="__self__">
      <p>Count: <span x-text="count"></span></p>
      <button @click="increment">+</button>
    </div>
  `,
  data: () => ({
    count: 0,
    increment() { this.count++ }
  })
}

The __self__ convention

The root element of your template must have x-data="__self__". Before calling Alpine.initTree(), x-component registers your data factory under the name __self__ — so Alpine knows where to find the component's state.

Registering components

Call registerComponent for every component before Alpine.start().

main.js
import Alpine from 'alpinejs'
import AlpineComponents, { registerComponent } from 'alpine-components'
import Counter from './components/Counter.js'
import Modal from './components/Modal.js'

// register before Alpine.start()
registerComponent('Counter', Counter)
registerComponent('Modal', Modal)

Alpine.plugin(AlpineComponents)
Alpine.start()

The name passed to registerComponent must match the string in x-component="'Counter'" exactly.

Data-less components

If a component has no reactive state, omit data and x-data entirely.

src/components/Banner.js
export default {
  template: `<div class="banner">Hello world</div>`
}

Troubleshooting

Component doesn't render — nothing appears

The component name wasn't registered before Alpine.start(). Make sure you've called registerComponent('Name', component) and that the name exactly matches the string in x-component="'Name'" (case-sensitive).

Alpine directives in the template don't work

The template root element must have x-data="__self__". Without it, Alpine doesn't know where the component's data scope is, so directives like x-text and @click won't bind.

Error: data is not a function

data must be a factory function that returns an object — data: () => ({ count: 0 }) — not a plain object. Alpine calls it each time a component is instantiated so each instance gets its own isolated state.

Multiple instances share state

If two counters affect each other, data is probably defined as a plain object rather than a factory function. Change data: { count: 0 } to data: () => ({ count: 0 }) so each instance gets its own copy.

Template renders but Alpine isn't initialised

Make sure Alpine.plugin(AlpineComponents) is called before Alpine.start(). If you're loading Alpine from a CDN, call registerComponent and Alpine.plugin in a defer script before Alpine's own script tag.

Getting Started

Up and running in under 2 minutes.

1

Install

terminal
npm install alpine-components
2

Register the plugin

main.js
import Alpine from 'alpinejs'
import AlpineComponents, { registerComponent } from 'alpine-components'
import Counter from './components/Counter.js'

registerComponent('Counter', Counter)
Alpine.plugin(AlpineComponents)
Alpine.start()
3

Create a component

src/components/Counter.js
export default {
  template: `
    <div x-data="__self__">
      <p>Count: <span x-text="count"></span></p>
      <button @click="increment">+</button>
    </div>
  `,
  data: () => ({
    count: 0,
    increment() { this.count++ }
  })
}

Call registerComponent('Counter', Counter) in main.js before Alpine.start().

4

Use it in your HTML

index.html
<div x-component="'Counter'"></div>

What's next?

GitHub →

Source and issues