Alpine.js 3.16.2¶
Résumé¶
Alpine.js est une petite couche de réactivité pour ajouter du comportement interactif directement dans le HTML. Il est particulièrement pratique pour des interfaces server-rendered : dropdowns, modals, onglets, menus, formulaires, filtres et petites interactions qui ne justifient pas un framework frontend complet.
Cette fiche couvre le commit 9ffa27d9807a2443f301d6a9d5c27892771981b, correspondant au tag v3.16.2.
| Fonction | Description |
|---|---|
| Réactivité dans le HTML | Déclarer un état avec x-data et laisser Alpine mettre à jour le DOM automatiquement. |
| Événements | Réagir aux clics, frappes clavier et événements personnalisés avec x-on ou @. |
| Affichage conditionnel | Afficher, cacher ou créer des éléments avec x-show et x-if. |
| Liaison avec les formulaires | Synchroniser des champs HTML avec l’état Alpine grâce à x-model. |
| Rendu de listes | Générer des éléments à partir d’un tableau avec x-for. |
| Composants réutilisables | Enregistrer des composants avec Alpine.data() et de l’état global avec Alpine.store(). |
| Transitions | Animer l’apparition et la disparition d’un élément avec x-transition. |
| Extensions | Ajouter des plugins officiels comme Persist, Focus, Intersect, Mask, Morph et Collapse. |
| Intégration progressive | Utiliser Alpine dans une page existante sans adopter une architecture SPA complète. |
Quand utiliser Alpine.js¶
Alpine est un bon choix quand :
- le HTML est rendu par un backend ou un générateur de site;
- l’interface a des interactions locales ou de petits composants;
- tu veux éviter une chaîne de build frontend pour quelques comportements;
- le state doit rester proche du markup;
- tu veux améliorer progressivement une application existante.
Ce n’est pas nécessairement le meilleur choix pour une application dont presque toute l’interface est un gros graphe d’état partagé, avec navigation cliente complexe et beaucoup de logique métier côté navigateur.
Installation¶
Option CDN¶
C’est l’option la plus simple pour une page HTML existante. Le script doit être chargé avec defer.
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.16.2/dist/cdn.min.js"></script>
Après le chargement, Alpine est disponible dans la page. Il faut toutefois déclarer un contexte Alpine avec x-data pour que les attributs Alpine soient actifs.
<div x-data="{ message: 'Bonjour Alpine' }">
<p x-text="message"></p>
</div>
En production, épingle une version précise plutôt que d’utiliser une URL générique comme alpinejs@3.x.x.
Option NPM¶
npm install alpinejs
Dans le point d’entrée JavaScript :
import Alpine from 'alpinejs'
window.Alpine = Alpine
Alpine.start()
window.Alpine = Alpine est facultatif, mais pratique pour inspecter Alpine dans les DevTools ou enregistrer des extensions depuis du code global.
Si tu ajoutes des composants, stores ou plugins, enregistre-les entre l’import et Alpine.start() :
import Alpine from 'alpinejs'
import persist from '@alpinejs/persist'
Alpine.plugin(persist)
Alpine.data('dropdown', () => ({
open: false,
}))
window.Alpine = Alpine
Alpine.start()
Appelle Alpine.start() une seule fois par page. Un appel répété peut démarrer plusieurs instances Alpine.
Le modèle mental : x-data crée le contexte¶
Tout part généralement de x-data. Il définit une portion de HTML et rend son état disponible à l’intérieur de cette portion.
<div x-data="{ open: false }">
<button @click="open = ! open">Afficher</button>
<div x-show="open">
Contenu caché ou visible
</div>
</div>
Quand open change, les directives qui dépendent de open sont recalculées automatiquement.
Les données peuvent être imbriquées. Un composant enfant peut accéder aux propriétés de son parent, sauf lorsqu’il définit lui-même une propriété du même nom.
<div x-data="{ open: false }">
<div x-data="{ label: 'Contenu' }">
<span x-text="label"></span>
<span x-show="open">Visible quand le parent est ouvert</span>
</div>
</div>
Pour une interaction sans état explicite, un x-data vide suffit :
<button x-data @click="alert('Cliqué')">
Cliquer
</button>
Les directives essentielles¶
x-text et x-html¶
x-text remplace le texte d’un élément :
<div x-data="{ title: 'Tableau de bord' }">
<h1 x-text="title"></h1>
</div>
Pour injecter du HTML, utilise x-html :
<div x-data="{ content: '<strong>Important</strong>' }">
<div x-html="content"></div>
</div>
Ne passe pas de contenu non fiable à x-html. Pour du texte provenant d’un utilisateur ou d’une API, préfère x-text afin d’éviter d’introduire du HTML dangereux.
x-show et x-if¶
x-show conserve l’élément dans le DOM et contrôle sa visibilité avec display: none :
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle</button>
<div x-show="open">
Le contenu reste dans le DOM.
</div>
</div>
x-if ajoute ou retire réellement l’élément. Il doit être utilisé sur un élément <template> :
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle</button>
<template x-if="open">
<div>Créé seulement quand open vaut true.</div>
</template>
</div>
Choisis x-show pour les toggles fréquents et x-if quand tu veux que le contenu n’existe pas du tout hors de son état actif.
x-transition¶
x-transition s’utilise avec x-show, pas avec x-if :
<div x-data="{ open: false }">
<button @click="open = ! open">Ouvrir</button>
<div x-show="open" x-transition>
Apparition avec une transition par défaut.
</div>
</div>
Tu peux modifier la durée ou limiter la transition :
<div x-show="open" x-transition.opacity.duration.500ms>
Transition d’opacité de 500 ms.
</div>
Pour un contrôle plus précis, utilise les phases enter, enter-start, enter-end, leave, leave-start et leave-end avec tes classes CSS.
x-bind et le raccourci :¶
x-bind lie une propriété HTML à une expression Alpine. Le raccourci est :.
<button
x-data="{ enabled: false }"
@click="enabled = ! enabled"
:disabled="! enabled"
:aria-pressed="enabled"
>
Activer
</button>
Les classes et styles peuvent aussi être conditionnels :
<div
x-data="{ active: true }"
:class="{ 'is-active': active }"
:style="active ? 'opacity: 1' : 'opacity: .5'"
>
Élément dynamique
</div>
x-on et le raccourci @¶
x-on écoute un événement navigateur. La forme courte @ est généralement plus lisible :
<button @click="count++">Ajouter</button>
Les modificateurs courants :
<form @submit.prevent="save()">
...
</form>
<div @click.stop="open = false">
...
</div>
<input @keyup.shift.enter="submit()">
Alpine fournit l’objet événement avec $event :
<button @click="$event.target.remove()">
Retirer ce bouton
</button>
Pour écouter un événement sur window, ajoute .window :
<div x-data="{ message: '' }" @app-notification.window="message = $event.detail">
<span x-text="message"></span>
</div>
Pour envoyer un événement personnalisé, utilise $dispatch :
<div x-data>
<button @click="$dispatch('app-notification', 'Sauvegardé')">
Sauvegarder
</button>
</div>
x-model¶
x-model synchronise une valeur de formulaire avec l’état Alpine :
<div x-data="{ name: '' }">
<label>
Nom
<input type="text" x-model="name">
</label>
<p>Bonjour <strong x-text="name"></strong></p>
</div>
Modificateurs pratiques :
<input x-model.trim="name">
<input x-model.number="quantity">
<input x-model.debounce.300ms="search">
Pour une valeur booléenne avec une case à cocher :
<label>
<input type="checkbox" x-model="accepted">
J’accepte
</label>
x-for¶
x-for rend une liste et doit être placé sur un <template> :
<ul x-data="{ users: ['Ana', 'Marc', 'Jo'] }">
<template x-for="user in users" :key="user">
<li x-text="user"></li>
</template>
</ul>
Pour obtenir l’index :
<template x-for="(user, index) in users" :key="user.id">
<li>
<span x-text="index + 1"></span>.
<span x-text="user.name"></span>
</li>
</template>
Utilise une clé stable avec :key quand la liste peut être modifiée.
x-ref et $refs¶
x-ref donne un nom à un élément. $refs permet ensuite de le manipuler depuis une expression Alpine :
<div x-data>
<input x-ref="search" type="search">
<button @click="$refs.search.focus()">Focus</button>
</div>
x-init et x-effect¶
x-init exécute du code durant l’initialisation d’un élément :
<div x-data="{ ready: false }" x-init="ready = true">
<span x-show="ready">Prêt</span>
</div>
x-effect réexécute automatiquement son expression lorsqu’une donnée utilisée dans celle-ci change :
<div x-data="{ query: '' }" x-effect="console.log('Recherche:', query)">
<input x-model="query">
</div>
Composants réutilisables avec Alpine.data()¶
Quand un même comportement revient plusieurs fois, enregistre une factory avec Alpine.data() :
Alpine.data('dropdown', () => ({
open: false,
toggle() {
this.open = ! this.open
},
close() {
this.open = false
},
}))
Utilise-la dans le HTML :
<div x-data="dropdown">
<button @click="toggle">Menu</button>
<nav x-show="open" @click.outside="close">
...
</nav>
</div>
Chaque occurrence de x-data="dropdown" reçoit son propre état.
État global avec Alpine.store()¶
Pour une donnée partagée entre plusieurs composants, crée un store :
Alpine.store('darkMode', {
on: false,
toggle() {
this.on = ! this.on
},
})
Accès dans le markup avec $store :
<div x-data>
<button @click="$store.darkMode.toggle()">
Mode sombre
</button>
<span x-text="$store.darkMode.on ? 'Activé' : 'Désactivé'"></span>
</div>
Garde les stores pour l’état réellement partagé. Pour une interaction locale, x-data est généralement plus simple et plus facile à maintenir.
Les magic properties les plus utiles¶
| Magic property | Utilité |
|---|---|
$el |
Référence vers l’élément courant. |
$refs |
Accès aux éléments marqués avec x-ref. |
$event |
Événement navigateur courant. |
$dispatch |
Émettre un événement personnalisé. |
$store |
Accéder aux stores globaux. |
$root |
Référence vers la racine du composant Alpine courant. |
$data |
Accéder aux données du composant courant. |
$id |
Générer des identifiants cohérents avec x-id. |
$nextTick |
Exécuter du code après la prochaine mise à jour du DOM. |
$watch |
Observer une propriété précise. |
Exemple avec $nextTick :
<div x-data="{ open: false }">
<button @click="open = true; $nextTick(() => $refs.panel.focus())">
Ouvrir
</button>
<div x-show="open" x-ref="panel" tabindex="-1">
Panneau
</div>
</div>
Exemple complet : filtre de produits¶
Voici un composant réaliste qui combine état local, champ de recherche, liste filtrée et affichage conditionnel.
<div
x-data="{
query: '',
products: [
{ id: 1, name: 'Clavier', category: 'Bureau' },
{ id: 2, name: 'Souris', category: 'Bureau' },
{ id: 3, name: 'Casque', category: 'Audio' },
],
get filteredProducts() {
const query = this.query.toLowerCase().trim()
if (! query) return this.products
return this.products.filter(product =>
product.name.toLowerCase().includes(query)
)
},
}"
>
<label>
Rechercher
<input x-model.debounce.200ms="query" type="search">
</label>
<p x-show="query && filteredProducts.length === 0">
Aucun produit trouvé.
</p>
<ul>
<template x-for="product in filteredProducts" :key="product.id">
<li>
<strong x-text="product.name"></strong>
<span x-text="product.category"></span>
</li>
</template>
</ul>
</div>
Le getter filteredProducts est recalculé quand query ou products change. Le champ utilise un debounce pour éviter de recalculer à chaque frappe trop rapidement.
Plugins officiels du monorepo¶
Le commit documenté contient plusieurs packages et plugins. Le README du dépôt identifie notamment :
collapse: expansion et collapse avec animations;csp: build compatible avec des contraintes de Content Security Policy;focus: gestion du focus et piège de focus;history: synchronisation avec les paramètres de l’URL;intersect: déclenchement selon l’intersection avec le viewport;mask: formatage automatique des champs;morph: mise à jour intelligente du HTML;persist: conservation de l’état entre les chargements;navigate: navigation de type SPA;resize,sort,anchoretuidans l’arbre de packages de cette release.
Un plugin importé doit être enregistré avant le démarrage d’Alpine :
import Alpine from 'alpinejs'
import focus from '@alpinejs/focus'
Alpine.plugin(focus)
Alpine.start()
Pour un plugin CDN, charge Alpine et le plugin dans l’ordre recommandé par sa documentation officielle. Vérifie toujours le nom du package et le build correspondant à la release utilisée.
Architecture du dépôt¶
Le dépôt est un monorepo npm utilisant les workspaces :
packages/
├── alpinejs/ # cœur d’Alpine
├── collapse/ # plugin
├── focus/ # plugin
├── intersect/ # plugin
├── mask/ # plugin
├── morph/ # plugin
├── persist/ # plugin
└── docs/ # documentation du projet
La release v3.16.2 est compilée avec la chaîne définie dans le dépôt. Les scripts principaux du package.json sont :
npm run build
npm run watch
npm run vitest
npm run cypress
npm run update-docs
Ces commandes servent surtout aux contributeurs du dépôt Alpine. Pour utiliser Alpine dans une application, préfère l’installation NPM du package alpinejs ou le build CDN versionné.
Gotchas et troubleshooting¶
Rien ne se passe dans le HTML¶
Vérifie les points suivants :
- Le script Alpine est bien chargé.
- L’attribut
deferest présent avec l’installation CDN. - Le composant possède un
x-data, même vide si aucune donnée n’est requise. - Avec l’installation NPM,
Alpine.start()est appelé. Alpine.start()n’est pas appelé plus d’une fois.- Les plugins et composants sont enregistrés avant
Alpine.start().
x-if ne fonctionne pas¶
x-if doit être placé sur un <template> et son contenu doit respecter les contraintes de template du navigateur.
La transition ne joue pas¶
x-transition fonctionne avec x-show, pas avec x-if. Si tu utilises des classes de transition, vérifie que les classes CSS existent vraiment dans ton CSS final.
Le contenu flash avant le chargement¶
Pour éviter qu’un élément Alpine visible brièvement avant l’initialisation, utilise x-cloak et le CSS correspondant :
<style>
[x-cloak] { display: none !important; }
</style>
<div x-data="{ open: false }" x-cloak x-show="open">
Contenu
</div>
x-html affiche du contenu non fiable¶
x-html injecte du HTML. Ne l’utilise pas directement avec une chaîne provenant d’un utilisateur ou d’une source non nettoyée. Utilise x-text lorsque tu veux afficher du texte.
Composant copié mais état partagé par accident¶
Une factory Alpine.data() doit retourner un nouvel objet d’état pour chaque composant. Évite de réutiliser un objet global mutable pour l’état local.
État qui devrait être global, mais ne l’est pas¶
Si deux composants indépendants doivent lire et modifier la même donnée, utilise un Alpine.store() ou un événement personnalisé avec $dispatch. N’essaie pas de dépendre d’un parent DOM qui n’existe pas entre les deux composants.
Fonctionnalités plus avancées¶
Démarrage manuel¶
L’installation comme module permet d’enregistrer des directives, magics, stores, composants et plugins avant le démarrage. C’est la voie recommandée quand l’application possède une vraie étape de build.
Extension d’Alpine¶
Le dépôt expose des APIs pour créer des plugins et des directives personnalisées. Avant d’en écrire une, vérifie si un plugin officiel ou une directive existante règle déjà le problème.
Build CSP¶
Le dépôt inclut un package csp pour les contextes où l’évaluation dynamique utilisée par le build normal n’est pas acceptable. Les contraintes exactes dépendent de l’application et de sa politique CSP; valide-les contre la documentation et le build de la release ciblée.
Sources¶
- Dépôt officiel : https://github.com/alpinejs/alpine
- Commit documenté : https://github.com/alpinejs/alpine/commit/9ffa27d9807a2443f301d6a9d5c27892771981b
- Tag : https://github.com/alpinejs/alpine/tree/v3.16.2
- Documentation officielle : https://alpinejs.dev
- Installation de la release : https://alpinejs.dev/essentials/installation
- État : https://alpinejs.dev/essentials/state
- Événements : https://alpinejs.dev/essentials/events
- Templating : https://alpinejs.dev/essentials/templating
- Directives : https://alpinejs.dev/directives
- Plugins : https://alpinejs.dev/plugins
Cette fiche a été rédigée à partir du dépôt cloné au commit indiqué et de la documentation incluse dans packages/docs. Les exemples n’ont pas été exécutés comme tests de la librairie, conformément au workflow du skill; seule la validation du site MkDocs est pertinente ici.