Liquid Fill
Au survol, un remplissage indigo descend du bord supérieur et couvre le bouton en 400 ms pendant que le texte passe au blanc : contour au repos, plein au survol. Un pseudo-élément, une transition, aucun JavaScript.
Mis à jour le
Cet effet fait partie d'Effect.Labs — 840 effets vanilla, une partie gratuite, une partie premium. Boutons comprend 27 effets, dont 2 gratuits. Découvrir la catégorie →
3 exemples d'usage



Comment ça marche
.btn-liquid est un bouton contour : fond transparent, texte et border: 2px solid en #6366f1, et z-index: 1 qui, avec le position: relative de .demo-btn, ouvre un contexte d'empilement. Le remplissage est son ::before : une boîte absolue collée en haut (top: 0; left: 0; width: 100%), de hauteur nulle, fond #6366f1, et z-index: -1 — dans ce contexte, « −1 » la place au-dessus du fond du bouton mais sous son texte.
Au :hover, height passe de 0 à 100% avec transition: height .4s (courbe ease). La boîte étant ancrée par top: 0, elle grandit vers le bas : mesuré dans Chromium, 18 px sur 50 à 86 ms, 36 px à 171 ms, plein à 400 ms. Ce n'est pas un liquide qui monte, malgré le nom — pour qu'il monte, remplacez top: 0 par bottom: 0. Le overflow: hidden et le border-radius: 12px de .demo-btn découpent le remplissage aux angles (rayon intérieur 10 px) : la bordure de 2 px l'encadre proprement.
Le texte passe au blanc par .btn-liquid:hover { color: #fff } — mais aucune transition n'est déclarée sur le bouton lui-même : la couleur bascule à l'instant du survol, alors que le remplissage n'atteint le haut des lettres qu'à ≈ 80 ms et ne les couvre entièrement qu'à ≈ 160 ms. Pendant ce temps, le libellé blanc repose sur le fond de votre page : invisible sur une page claire, sans conséquence sur une page sombre. À la sortie, l'inverse : le texte redevient indigo d'un coup sur un remplissage encore indigo, et disparaît ≈ 80 à 160 ms, le temps que le fond se retire. transition: color .15s .15s sur .btn-liquid supprime les deux artefacts.
À la sortie, height revient à 0 en 400 ms : le remplissage se retire vers le haut, par le bas. Le mouvement anime height, propriété de mise en page recalculée à chaque image ; la version composée (transform: scaleY() avec transform-origin: top) donne le même rendu sans toucher au thread principal (voir Personnaliser). Aucune règle :active : le clic n'ajoute rien.
Accessibilité
- Contraste insuffisant dans les deux états : au repos,
#6366f1sur#0a0a0f= 4,42:1 ; au survol, blanc sur#6366f1= 4,47:1 — les deux juste sous le seuil AA de 4,5:1. Sur ce fond, aucune couleur unique ne peut réussir les deux (le produit des deux rapports plafonne à 19,75, il en faudrait 20,25) : séparez-les. Texte et bordure#818cf8au repos (6,6:1), remplissage#4f46e5(blanc à 6,3:1). Sur une page blanche,#4f46e5partout suffit (6,3:1 dans les deux états). - prefers-reduced-motion absent du code :
@media (prefers-reduced-motion: reduce) { .btn-liquid::before { transition: none } }— le bouton devient plein d'un coup au survol ; l'état reste, seul le mouvement disparaît. - Bascule de couleur non synchronisée : le libellé blanc précède le remplissage d'environ 80 ms (voir Comment ça marche). Sur fond clair, c'est un clignotement du texte à chaque survol. Ajoutez
transition: color .15s .15sà.btn-liquidpour que la couleur attende le remplissage, dans les deux sens. - Clavier : aucun
:hoverau focus. Doublez les deux règles avec:focus-visible—.btn-liquid:focus-visible { color: #fff }et.btn-liquid:focus-visible::before { height: 100% }— pour que Tab remplisse le bouton comme le survol. Le code ne touche pasoutline: l'anneau de focus natif reste, dessiné hors de la boîte et donc non rogné paroverflow: hidden. - Lecteurs d'écran :
content: "", rien n'est annoncé ;<button>natif, nom accessible = libellé. Tactile : le remplissage apparaît au tap et reste (iOS) —@media (hover: hover)pour l'éviter. Cible 54 px de haut (16 + 18 + 16 + 2 × 2), au-dessus du minimum de 44 px.
Compatibilité navigateur
Requiert la transition CSS de height, les pseudo-éléments et un z-index négatif dans un contexte d'empilement — tous les navigateurs depuis 2012. Aucun JavaScript, zéro dépendance.
Sans transitions, le bouton passe du contour au plein instantanément au survol, et inversement ; l'information est identique, seul le mouvement manque.
Le code
Structure HTML à coller dans votre page (le CSS + JS sont inclus avec Premium) :
<button class="demo-btn btn-liquid">Liquid Fill</button>
Le code complet HTML + CSS + JS de Liquid Fill, prêt à coller — et les 840 effets. Ou 299 € TTC une fois, accès à vie.
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
| top: 0 (CSS — ::before) | top: 0 (descend) | Ancre du remplissage. bottom: 0 : il monte. top: 50%; transform: translateY(-50%) : il s'ouvre depuis le centre vers les deux bords. |
| transition: height .4s (CSS — ::before) | .4s ease | Durée et courbe. .25s : réactif ; cubic-bezier(.2, .8, .2, 1) : départ rapide, arrivée douce, plus « fluide ». |
| #6366f1 (CSS — color, border, background du ::before) | #6366f1 × 3 | Trois occurrences. Pour le contraste AA, dissociez-les : #818cf8 texte et bordure, #4f46e5 remplissage (voir Accessibilité). |
| transition: color (CSS — à ajouter sur .btn-liquid) | absente (bascule instantanée) | transition: color .15s .15s : le texte change de couleur pendant que le remplissage le recouvre, et non avant. Indispensable sur fond clair. |
| border: 2px solid (CSS — .btn-liquid) | 2px | 1px : contour fin ; 0 : le remplissage va jusqu'au bord, avec le rayon complet de 12 px — le bouton n'a plus de forme au repos, prévoyez un fond léger. |
| transform: scaleY() (CSS — alternative composée) | height 0 → 100% | Version GPU : ::before { height: 100%; transform: scaleY(0); transform-origin: top; transition: transform .4s } et :hover::before { transform: scaleY(1) }. Même rendu, aucun recalcul de mise en page. |
| z-index: 1 (CSS — .btn-liquid) et -1 (::before) | 1 / -1 | À garder tels quels : le z-index: 1 crée le contexte d'empilement qui retient le ::before sous le texte mais au-dessus des fonds de la page. Sans lui, le remplissage passe derrière les conteneurs parents et peut disparaître. |
FAQ
bottom: 0 au lieu de top: 0 sur .btn-liquid::before. La hauteur grandit alors depuis le bord inférieur. Rien d'autre à toucher : la transition, la découpe et la bordure fonctionnent à l'identique.transition: color .15s .15s à .btn-liquid : la couleur commence à changer quand le remplissage arrive sur le texte, dans les deux sens.::before est libre. background: linear-gradient(180deg, #6366f1, #8b5cf6) donne un remplissage dégradé, une image avec background-size: cover fonctionne aussi. Comme la boîte grandit de 0 à 100 % de hauteur, un dégradé s'étire avec elle pendant les 400 ms ; pour qu'il se révèle sans s'étirer, fixez sa taille à celle du bouton — background-size: 100% 50px; background-position: top.