Introduction
Les composants UI interactifs sont le socle de toute interface web moderne. Accordions, tabs, modals et toast notifications se retrouvent sur la majorite des sites, mais leur qualite d'animation fait toute la difference entre une experience banale et une interface premium.
Dans ce tutoriel, nous allons construire 5 composants essentiels avec des animations CSS fluides et du JavaScript accessible. Chaque composant est accompagne d'un demo interactif et du code complet a copier.
Tous les composants de ce tutoriel utilisent des transitions CSS pour la fluidite et du JavaScript vanille pour la logique. Aucune dependance externe n'est requise.
1. Accordion anime
L'accordion est le composant ideal pour afficher du contenu pliable. Notre version utilise la technique max-height pour une transition fluide a l'ouverture et a la fermeture.
Demo interactive
.accordion-item {
border: 1px solid #2a2a35;
border-radius: 12px;
margin-bottom: 8px;
overflow: hidden;
}
.accordion-header {
padding: 16px 20px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.accordion-icon {
transition: transform 0.3s ease;
}
.accordion-item.active .accordion-icon {
transform: rotate(45deg);
}
/* Transition fluide avec max-height */
.accordion-body {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease;
}
.accordion-item.active .accordion-body {
max-height: 200px;
}
function toggleAccordion(item) {
// Fermer les autres items (mode exclusif)
const siblings = item.parentElement
.querySelectorAll('.accordion-item');
siblings.forEach(sibling => {
if (sibling !== item) {
sibling.classList.remove('active');
}
});
// Basculer l'item clique
item.classList.toggle('active');
}
Comment ca fonctionne
La technique repose sur la propriete max-height :
- Etat ferme :
max-height: 0avecoverflow: hiddenmasque le contenu - Etat ouvert :
max-height: 200pxrevele le contenu avec une transition fluide - L'icone + tourne de 45 degres pour former un x grace a
transform: rotate(45deg)
Choisissez une valeur max-height legerement superieure au contenu reel. Une valeur trop grande ralentit visuellement la transition car le navigateur anime jusqu'a cette valeur meme si le contenu est plus petit.
2. Tabs animes
Les tabs (onglets) permettent d'organiser du contenu en sections accessibles sans changement de page. Notre version integre un indicateur glissant qui suit l'onglet actif avec une transition fluide.
Demo interactive
.tabs-nav {
display: flex;
border-bottom: 2px solid #2a2a35;
position: relative;
}
.tab-btn {
padding: 12px 24px;
background: none;
border: none;
color: #a1a1aa;
font-weight: 600;
cursor: pointer;
transition: color 0.3s;
}
.tab-btn.active {
color: #6366f1;
}
/* Indicateur glissant */
.tabs-indicator {
position: absolute;
bottom: -2px;
height: 2px;
background: #6366f1;
transition: left 0.3s ease,
width 0.3s ease;
}
/* Animation d'apparition du panneau */
.tab-panel {
display: none;
animation: tabFadeIn 0.3s ease;
}
.tab-panel.active {
display: block;
}
@keyframes tabFadeIn {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
function switchTab(btn, panelId) {
const container = btn.closest('.tabs-container');
// Desactiver tous les onglets et panneaux
container.querySelectorAll('.tab-btn')
.forEach(b => b.classList.remove('active'));
container.querySelectorAll('.tab-panel')
.forEach(p => p.classList.remove('active'));
// Activer l'onglet et panneau selectionnes
btn.classList.add('active');
document.getElementById(panelId)
.classList.add('active');
// Deplacer l'indicateur
updateIndicator(btn);
}
function updateIndicator(activeBtn) {
const indicator = document
.getElementById('tabIndicator');
indicator.style.left =
activeBtn.offsetLeft + 'px';
indicator.style.width =
activeBtn.offsetWidth + 'px';
}
3. Modal avec animation
Les modals (fenetres modales) sont essentiels pour les confirmations, formulaires et messages importants. Notre version combine une animation d'entree avec rebond (cubic-bezier) et un overlay avec backdrop-filter.
Demo interactive
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(4px);
z-index: 2000;
align-items: center;
justify-content: center;
}
.modal-overlay.show {
display: flex;
animation: overlayIn 0.3s ease;
}
@keyframes overlayIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Animation d'entree avec rebond */
.modal-box {
background: #12121a;
border-radius: 20px;
padding: 40px;
max-width: 480px;
width: 90%;
animation: modalIn 0.4s
cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes modalIn {
from {
opacity: 0;
transform: scale(0.9) translateY(20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
function openModal() {
const overlay = document
.getElementById('modalOverlay');
overlay.classList.add('show');
document.body.style.overflow = 'hidden';
}
function closeModal() {
const overlay = document
.getElementById('modalOverlay');
overlay.classList.remove('show');
document.body.style.overflow = '';
}
// Fermer en cliquant sur l'overlay
overlay.addEventListener('click', (e) => {
if (e.target === overlay) closeModal();
});
Pensez a ajouter overflow: hidden sur le body a l'ouverture de la modal pour empecher le scroll en arriere-plan, et a le retirer a la fermeture.
4. Toast Notification
Les toast notifications sont des messages temporaires non-intrusifs. Notre version utilise un slide-in depuis la droite avec un timing cubic-bezier pour un effet de rebond subtil.
Demo interactive
.toast {
position: fixed;
bottom: 24px;
right: 24px;
padding: 16px 24px;
background: #12121a;
border: 1px solid #10b981;
border-radius: 14px;
display: flex;
align-items: center;
gap: 12px;
z-index: 3000;
box-shadow: 0 8px 30px rgba(0,0,0,0.3);
/* Etat initial : hors ecran a droite */
transform: translateX(calc(100% + 40px));
opacity: 0;
transition: transform 0.4s
cubic-bezier(0.34, 1.56, 0.64, 1),
opacity 0.4s ease;
}
.toast.show {
transform: translateX(0);
opacity: 1;
}
function showToast() {
const toast = document
.getElementById('toastDemo');
toast.classList.add('show');
// Masquer apres 3 secondes
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
5. Tooltip anime
Les tooltips fournissent des informations contextuelles au survol. Notre version utilise un fade + translate avec une fleche CSS en pseudo-element pour un resultat elegant et leger.
.tooltip-wrapper {
position: relative;
display: inline-block;
}
.tooltip {
position: absolute;
bottom: calc(100% + 10px);
left: 50%;
transform: translateX(-50%)
translateY(8px);
padding: 8px 16px;
background: #1a1a25;
border: 1px solid #2a2a35;
border-radius: 8px;
font-size: 0.85rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease,
transform 0.2s ease;
}
/* Fleche du tooltip */
.tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: #1a1a25;
}
/* Afficher au survol */
.tooltip-wrapper:hover .tooltip {
opacity: 1;
transform: translateX(-50%)
translateY(0);
}
Le tooltip se positionne au-dessus de l'element parent grace a bottom: calc(100% + 10px). La fleche est creee avec un pseudo-element ::after utilisant la technique des bordures CSS.
Pour un tooltip qui s'adapte automatiquement a la position dans le viewport (en haut, en bas, a gauche ou a droite), il faudra ajouter du JavaScript pour detecter l'espace disponible et ajuster la classe de positionnement.
Bonnes pratiques
Voici les recommandations essentielles pour des composants UI de qualite professionnelle :
Performance
- Preferez
transformetopacitypour les animations : ces proprietes sont optimisees par le GPU et ne declenchent pas de reflow - Evitez d'animer
heightdirectement : utilisezmax-heightoutransform: scaleY()a la place - Utilisez
will-changeavec parcimonie sur les elements frequemment animes
Accessibilite
Les attributs ARIA sont essentiels pour rendre vos composants utilisables par tous :
<!-- Accordion accessible -->
<button
role="button"
aria-expanded="false"
aria-controls="panel-1"
>Titre de la section</button>
<div
id="panel-1"
role="region"
aria-hidden="true"
>Contenu</div>
<!-- Modal accessible -->
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
>
<h2 id="modal-title">Titre</h2>
</div>
<!-- Tabs accessibles -->
<div role="tablist">
<button
role="tab"
aria-selected="true"
aria-controls="tab-panel-1"
>Onglet 1</button>
</div>
UX
- Duree des animations : 200-400ms est la plage ideale. En dessous, c'est trop rapide pour etre percu ; au-dessus, ca ralentit l'utilisateur
- Easing naturel : utilisez
easeoucubic-bezierplutot quelinearpour un mouvement organique - Feedback immediat : le composant doit reagir au clic en moins de 100ms, meme si l'animation complete prend plus de temps
- Respectez
prefers-reduced-motionpour les utilisateurs sensibles aux mouvements
@media (prefers-reduced-motion: reduce) {
.accordion-body,
.tabs-indicator,
.modal-box,
.toast,
.tooltip {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
Clavier et lecteur d'ecran : les oublis frequents
Les attributs ARIA vus plus haut decrivent le composant, mais ils ne suffisent pas : un composant peut etre parfaitement balise et rester inutilisable au clavier. Voici les quatre oublis qu'on retrouve dans la majorite des tutoriels.
La modale qui ne retient pas le focus
C'est le defaut le plus repandu. Sans piege a focus, la touche Tab sort de la modale et fait naviguer l'utilisateur dans la page situee derriere, qu'il ne voit pas. Le comportement attendu tient en quatre gestes :
- Memoriser l'element actif avec
document.activeElementjuste avant d'ouvrir - Deplacer le focus dans la modale a l'ouverture (le titre ou le premier champ)
- Boucler la tabulation : sur le dernier element focusable, Tab renvoie au premier ; sur le premier, Maj+Tab renvoie au dernier. Recuperez-les avec
querySelectorAll('a[href], button, input, select, textarea, [tabindex]:not([tabindex="-1"])') - Restaurer le focus sur l'element memorise a la fermeture, sinon l'utilisateur repart du haut de la page
Le toast que personne n'entend
Un toast qui apparait en CSS est invisible pour un lecteur d'ecran : rien ne signale que le contenu a change. Ajoutez role="status" (equivalent a aria-live="polite") sur le conteneur, present dans le DOM avant l'insertion du message — un aria-live ajoute en meme temps que le texte n'est pas annonce. Reservez aria-live="assertive" aux erreurs bloquantes : il interrompt la lecture en cours.
Le tooltip inaccessible au clavier
Un tooltip declenche uniquement sur :hover n'existe pas pour qui navigue au clavier. Declenchez-le aussi sur :focus-visible, et reliez-le a son declencheur avec aria-describedby pointant vers l'id de la bulle. Si l'information est indispensable, ce n'est pas un tooltip : mettez-la dans la page.
Les onglets qui ignorent les fleches
Avec role="tablist", les lecteurs d'ecran annoncent un groupe d'onglets et l'utilisateur s'attend a naviguer avec les fleches gauche/droite, un seul onglet etant dans l'ordre de tabulation (tabindex="0" sur l'actif, -1 sur les autres). Sans ce comportement, le balisage promet quelque chose que le composant ne tient pas.
Rangez la souris et parcourez le composant uniquement au clavier : Tab, Maj+Tab, Entree, Espace, Echap. Si vous perdez le focus des yeux ou si vous ne pouvez pas fermer, le composant n'est pas pret.
Conclusion
Les composants UI animes sont la cle d'une interface web agreable et professionnelle. En combinant CSS transitions pour la fluidite, @keyframes pour les animations complexes et JavaScript vanille pour la logique, vous obtenez des composants performants et accessibles.
Les techniques presentees dans ce tutoriel -- max-height pour les accordions, indicateur glissant pour les tabs, cubic-bezier pour les modals et translateX pour les toasts -- couvrent la majorite des besoins en composants interactifs. Adaptez les durees, les easing et les couleurs a votre design system pour un resultat coherent.
Decouvrez notre collection de composants prets a l'emploi dans la bibliotheque d'effets, avec code copiable en un clic et demos interactives.