Transitions avec Headless UI
Utilisez le composant Transition pour animer les états d’entrée et de sortie des boîtes de dialogue, listes déroulantes et panneaux coulissants avec les utilitaires de transition de Tailwind.
Transitions avec Headless UI est une leçon Tailwind CSS Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Tailwind CSS Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Tailwind CSS Academy comprend 4 leçons au total.
Pourquoi animer avec Transition de Headless UI ?
Les utilitaires transition-* de Tailwind gèrent les changements d’état fluides des éléments persistants, mais ils ne peuvent pas animer les éléments qui sont montés et démontés du DOM. Lorsqu’une boîte de dialogue ou un menu déroulant apparaît, il passe de l’inexistence à la visibilité en une seule image : aucune transition n’est possible. Le composant Transition de Headless UI résout ce problème en maintenant brièvement l’élément monté pendant l’animation de sortie, ce qui permet aux utilitaires de transition de Tailwind de s’exécuter avant la suppression de l’élément.
API du composant Transition
Le composant Transition de Headless UI accepte un booléen show et un ensemble de propriétés de classes : enter, enterFrom et enterTo pour l’animation d’entrée, ainsi que leave, leaveFrom et leaveTo pour la sortie. Les classes de enter et leave sont actives pendant toute la transition. Les classes de enterFrom/leaveFrom définissent l’état initial, tandis que celles de enterTo/leaveTo définissent l’état final.
import { Transition } from '@headlessui/react';
<Transition
show={isVisible}
enter='transition-opacity duration-200 ease-out'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='transition-opacity duration-150 ease-in'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<div className='bg-white rounded-xl p-6 shadow-lg'>
Content that fades in and out
</div>
</Transition>Animer l’arrière-plan et le panneau d’une boîte de dialogue
L’ouverture soignée d’une boîte de dialogue utilise deux animations distinctes : l’arrière-plan apparaît progressivement, tandis que le panneau est agrandi et apparaît progressivement depuis une taille légèrement inférieure. Enveloppez chaque élément dans son propre Transition.Child et définissez des durées différentes afin que l’arrière-plan apparaisse légèrement avant le panneau. Utilisez Transition comme conteneur externe avec la propriété show, et Transition.Child pour coordonner les animations des éléments enfants.
import { Dialog, Transition } from '@headlessui/react';
import { Fragment } from 'react';
function AnimatedDialog({ open, onClose, children }) {
return (
<Transition show={open} as={Fragment}>
<Dialog onClose={onClose} className='relative z-50'>
{/* Backdrop fade */}
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='ease-in duration-200'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<div className='fixed inset-0 bg-black/50' aria-hidden='true' />
</Transition.Child>
{/* Panel scale-in */}
<div className='fixed inset-0 flex items-center justify-center p-4'>
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0 scale-95'
enterTo='opacity-100 scale-100'
leave='ease-in duration-200'
leaveFrom='opacity-100 scale-100'
leaveTo='opacity-0 scale-95'
>
<Dialog.Panel className='bg-white rounded-2xl shadow-2xl max-w-md w-full p-6'>
{children}
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
);
}Animer les menus déroulants et les fenêtres contextuelles
Les menus déroulants gagnent à utiliser une entrée subtile combinant glissement et fondu. Enveloppez Menu.Items dans un composant Transition qui le fait glisser vers le bas depuis le déclencheur tout en le faisant apparaître progressivement. L’animation de sortie doit être plus rapide que celle d’entrée : une sortie de 100 à 150 ms donne une sensation nette et réactive. Utilisez transform origin-top afin que l’animation de mise à l’échelle parte du haut, là où se trouve le bouton.
import { Menu, Transition } from '@headlessui/react';
import { Fragment } from 'react';
<Menu as='div' className='relative'>
<Menu.Button>Options</Menu.Button>
<Transition
as={Fragment}
enter='transition ease-out duration-150'
enterFrom='transform opacity-0 scale-95 -translate-y-2'
enterTo='transform opacity-100 scale-100 translate-y-0'
leave='transition ease-in duration-100'
leaveFrom='transform opacity-100 scale-100 translate-y-0'
leaveTo='transform opacity-0 scale-95 -translate-y-2'
>
<Menu.Items className='absolute right-0 mt-1 w-48 rounded-xl bg-white shadow-lg ring-1 ring-black/5 p-1 focus:outline-none'>
{/* items */}
</Menu.Items>
</Transition>
</Menu>Panneau latéral coulissant
Un panneau latéral mobile coulisse depuis le bord gauche ou droit. Utilisez Transition avec les utilitaires translate-x pour définir la direction du glissement. Commencez hors écran (-translate-x-full à gauche, translate-x-full à droite), puis appliquez translate-x-0 pour le faire apparaître. Ajoutez simultanément une transition à l’arrière-plan avec Transition.Child.
function MobileSidebar({ open, onClose }) {
return (
<Transition show={open} as={Fragment}>
<Dialog onClose={onClose}>
{/* Backdrop */}
<Transition.Child
as={Fragment}
enter='ease-out duration-300'
enterFrom='opacity-0'
enterTo='opacity-100'
leave='ease-in duration-200'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<div className='fixed inset-0 bg-black/40 z-40' aria-hidden='true' />
</Transition.Child>
{/* Sidebar panel */}
<Transition.Child
as={Fragment}
enter='transition ease-out duration-300'
enterFrom='-translate-x-full'
enterTo='translate-x-0'
leave='transition ease-in duration-200'
leaveFrom='translate-x-0'
leaveTo='-translate-x-full'
>
<Dialog.Panel className='fixed left-0 top-0 h-full w-72 bg-white shadow-xl z-50 overflow-y-auto'>
{/* sidebar content */}
</Dialog.Panel>
</Transition.Child>
</Dialog>
</Transition>
);
}Animation des notifications éphémères
Les notifications éphémères glissent généralement depuis le bas ou un coin supérieur, puis disparaissent progressivement après un délai d’attente. Utilisez Transition avec translate-y pour définir la direction du glissement. Combinez show avec un booléen qui bascule automatiquement à false après un délai d’attente à l’aide de setTimeout dans un useEffect. L’animation de sortie se termine avant le démontage du composant.
function Toast({ message, show, onHide }) {
return (
<Transition
show={show}
as={Fragment}
enter='transition ease-out duration-300'
enterFrom='translate-y-4 opacity-0'
enterTo='translate-y-0 opacity-100'
leave='transition ease-in duration-200'
leaveFrom='translate-y-0 opacity-100'
leaveTo='translate-y-4 opacity-0'
>
<div className='
fixed bottom-4 right-4 z-50
bg-gray-900 text-white
px-4 py-3 rounded-xl shadow-lg
flex items-center gap-3
max-w-sm
'>
<CheckCircleIcon className='h-5 w-5 text-green-400 flex-shrink-0' />
<span className='text-sm'>{message}</span>
<button onClick={onHide} className='ml-auto text-gray-400 hover:text-white'>
<XMarkIcon className='h-4 w-4' />
</button>
</div>
</Transition>
);
}Coordonner plusieurs transitions
Le composant Transition.Child coordonne le minutage avec un composant parent Transition. Les éléments enfants observent l’état show du parent et peuvent ajouter leurs propres décalages temporels. Cela permet de créer des animations échelonnées, où différentes parties de l’interface apparaissent successivement. Headless UI ne prend pas nativement en charge les délais échelonnés arbitraires, mais vous pouvez les simuler avec des durées enter progressivement plus longues ou des délais d’animation CSS.
// Staggered items using animation-delay workaround
<Transition show={open}>
<div className='bg-white rounded-xl p-6 shadow-lg'>
{items.map((item, i) => (
<Transition.Child
key={item.id}
as={Fragment}
enter='transition ease-out duration-200'
enterFrom='opacity-0 translate-y-2'
enterTo='opacity-100 translate-y-0'
style={{ transitionDelay: i * 50 + 'ms' }}
leave='transition ease-in duration-150'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<div className='py-2 border-b border-gray-100'>{item.label}</div>
</Transition.Child>
))}
</div>
</Transition>Transition avec la propriété appear
Par défaut, le composant Transition n’anime pas le premier rendu si show vaut déjà true lors de son montage. La propriété appear permet de modifier ce comportement : la transition d’entrée est alors jouée même lors du montage initial. Cette option est utile pour les animations au chargement de la page ou lorsqu’un composant est rendu conditionnellement avec show défini immédiatement sur true.
// Without 'appear': no animation on initial mount when show=true
<Transition show={true}>
<div>This appears without animation on first load</div>
</Transition>
// With 'appear': animates in even on first mount
<Transition
show={true}
appear // enables enter transition on initial mount
enter='transition-all ease-out duration-500'
enterFrom='opacity-0 scale-95'
enterTo='opacity-100 scale-100'
>
<div className='bg-white rounded-xl p-6 shadow'>
This animates in on first load
</div>
</Transition>Gérer les fonctions de rappel afterLeave
La propriété afterLeave déclenche une fonction de rappel une fois la transition de sortie terminée. Elle est utile pour les opérations de nettoyage qui ne doivent avoir lieu qu’après la disparition complète de l’élément, comme la réinitialisation de l’état d’un formulaire, l’effacement des messages d’erreur ou le démontage de composants enfants coûteux. Effectuer le nettoyage pendant la transition, alors que l’élément est encore visible, provoquerait un changement visuel brutal.
function SearchDialog({ open, onClose }) {
const [query, setQuery] = useState('');
return (
<Transition
show={open}
afterLeave={() => {
// Reset query AFTER dialog has fully faded out
// Prevents seeing the input clear while dialog is still visible
setQuery('');
}}
enter='ease-out duration-300'
enterFrom='opacity-0 scale-95'
enterTo='opacity-100 scale-100'
leave='ease-in duration-200'
leaveFrom='opacity-100 scale-100'
leaveTo='opacity-0 scale-95'
>
<Dialog open={open} onClose={onClose}>
<Dialog.Panel className='bg-white rounded-2xl p-6 shadow-xl'>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder='Search...'
className='w-full border rounded-lg px-3 py-2'
/>
</Dialog.Panel>
</Dialog>
</Transition>
);
}Bonnes pratiques pour les transitions d’interface
Gardez les animations courtes et justifiées. Les transitions d’entrée doivent durer 150 à 300 ms : suffisamment longtemps pour sembler fluides, mais assez peu pour ne pas faire attendre les utilisateurs. Les transitions de sortie doivent être 10 à 30 % plus courtes que celles d’entrée : les sorties doivent sembler nettes. Utilisez ease-out pour les entrées (un début rapide et une fin lente semblent naturels) et ease-in pour les sorties (un début lent et une fin rapide donnent une impression intentionnelle). N’animez jamais pour le simple plaisir d’animer : chaque mouvement doit communiquer un changement d’état.
/* Recommended timing guidelines */
/* Tooltip / small dropdown */
enter: 'transition ease-out duration-100'
leave: 'transition ease-in duration-75'
/* Modal dialog */
enter: 'ease-out duration-300'
leave: 'ease-in duration-200'
/* Sidebar drawer */
enter: 'transition ease-out duration-300'
leave: 'transition ease-in duration-250'
/* Page transitions */
enter: 'transition ease-out duration-500'
leave: 'transition ease-in duration-300'
/* Avoid: too slow (>500ms) feels laggy */
/* Avoid: ease-in for enters (starts slow, looks stuck) */Respecter les préférences de réduction des mouvements
Certaines personnes ressentent le mal des transports ou des crises d’épilepsie à cause des animations à l’écran. La requête média prefers-reduced-motion permet au système d’exploitation de signaler cette préférence au navigateur. Respectez-la toujours dans vos animations Transition. Utilisez la variante motion-reduce: de Tailwind (ou une variante personnalisée) pour supprimer les transitions destinées aux personnes qui ont besoin de réduire les mouvements, tout en conservant une interface fonctionnelle mais statique.
/* globals.css — respect reduced motion globally */
@media (prefers-reduced-motion: reduce) {
.transition,
.transition-all,
.transition-colors,
.transition-transform {
transition-duration: 0.01ms !important;
}
}
<!-- In Tailwind with motion-reduce: variant -->
<div
class='
transition-all ease-out duration-300
motion-reduce:transition-none
opacity-0 translate-y-4
open:opacity-100 open:translate-y-0
'
>
Animates for standard users, instant for reduced-motion users
</div>Vérification rapide
Vérifiez votre compréhension des concepts de Tailwind CSS Mastery abordés dans cette leçon.
Récapitulatif de la leçon
Dans cette leçon, vous avez appris que Transition enveloppe les éléments montés pour permettre les animations d’entrée et de sortie, que Transition.Child coordonne le minutage de plusieurs éléments au sein d’un Transition parent, et que afterLeave exécute les fonctions de rappel de nettoyage uniquement lorsque l’animation de sortie est terminée. Ensuite, vous apprendrez à écrire des extensions Tailwind personnalisées pour étendre le système d’utilitaires.
Apprends HTML avec un tuteur IA — gratuit
Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.
- Cours
- 30
- Leçons
- 120
Questions Fréquemment Posées
La leçon « Transitions avec Headless UI » est-elle gratuite ?
Oui — le texte complet de « Transitions avec Headless UI » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Tailwind CSS Academy, passe à CoddyKit PRO. Le cours Tailwind CSS Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Transitions avec Headless UI » ?
Utilisez le composant Transition pour animer les états d’entrée et de sortie des boîtes de dialogue, listes déroulantes et panneaux coulissants avec les utilitaires de transition de Tailwind. Tu pratiques Tailwind CSS Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Tailwind CSS Academy ?
Aucune expérience préalable n'est requise. Tailwind CSS Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Transitions avec Headless UI » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Tailwind CSS Academy ?
Oui. Chaque leçon Tailwind CSS Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Introduction à Headless UI
- Styliser un menu et une liste déroulante Headless
- Créer des boîtes de dialogue accessibles
- Transitions avec Headless UI