"We replaced 8,000 lines of Stimulus with Alpine.js—then switched back. Here’s what we learned."
Both Alpine.js and Stimulus promise minimal JavaScript for maximal interactivity, but their philosophies lead to wildly different developer experiences.
After 18 months of using both in production, we discovered:
- Alpine.js is like a Swiss Army knife—quick, all-in-one, but risky at scale.
- Stimulus is more like a surgeon’s scalpel—precise, reusable, but slower to start.
Here’s how to choose (or mix them) without regret.
1. Core Philosophies
Alpine.js
"Write JS directly in your HTML."
✅ Pros:
- Instant gratification (
x-data
,x-show
) - Built-in transitions (
x-transition
) - No build step
❌ Cons:
- Temptation to write logic in markup
- Hard to debug at scale
Stimulus
"Connect HTML to reusable controllers."
✅ Pros:
- Encourages separation of concerns
- TypeScript-friendly
- Testable with Jest
❌ Cons:
- More boilerplate
- Steeper learning curve
2. Side-by-Side Comparison
Example: A Counter Component
Alpine.js:
<div x-data="{ count: 0 }">
<button @click="count++">+</button>
<span x-text="count"></span>
</div>
Stimulus:
<div data-controller="counter">
<button data-action="counter#increment">+</button>
<span data-counter-target="output">0</span>
</div>
// app/javascript/controllers/counter_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["output"]
increment() {
this.outputTarget.textContent =
parseInt(this.outputTarget.textContent) + 1
}
}
Key Difference:
- Alpine embeds logic in HTML
- Stimulus separates logic into controllers
3. When to Choose Alpine.js
✅ Prototyping (get interactive fast)
✅ Small projects (admin dashboards, marketing sites)
✅ When you hate build steps
Ideal Use Case:
<!-- Alpine shines here -->
<div
x-data="{ open: false }"
x-show="open"
x-transition
>
<button @click="open = !open">Toggle</button>
</div>
4. When to Choose Stimulus
✅ Large codebases (needs maintainability)
✅ Teams with React/Vue experience (familiar patterns)
✅ When TypeScript matters
Ideal Use Case:
// Stimulus scales better
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input", "preview"]
updatePreview() {
this.previewTarget.textContent = this.inputTarget.value
}
}
5. The Hybrid Approach
How We Use Both
Tool | When We Use It |
---|---|
Alpine.js | Quick UI tricks (toggles, tabs) |
Stimulus | Complex logic (forms, drag/drop) |
Example:
<!-- Alpine for UI state -->
<div x-data="{ activeTab: 'profile' }">
<!-- Stimulus for heavy lifting -->
<form data-controller="profile-form">
<input data-action="profile-form#validate">
</form>
</div>
6. Key Takeaways
🔹 Alpine.js = speed upfront, pain later
🔹 Stimulus = slow start, smooth scaling
🔹 Best combo: Alpine for sprinkles, Stimulus for depth
"But Our Team Knows Vue!"
Stick with what works! Here’s how we’d decide:
- Try Alpine for one small feature
- Compare dev velocity
- Gradually introduce Stimulus for complex parts
Team Alpine or Team Stimulus? Fight it out below 👇
Top comments (0)