CTA Gradient Border
Un anneau de 2 px découpé au masque dans un dégradé indigo → rose → ambre qui glisse en boucle sur 4 s autour d'une face sombre ; au survol, il accélère à 1,5 s et un halo indigo apparaît — CSS seul.
Vous aurez accès à la démo interactive avec un compte gratuit.
Cet effet fait partie d'Effect.Labs — 811 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
Le bouton .btn-cta-gradient est plein : background: #0a0a0f, color: #fff, padding: 18px 36px, position: relative; z-index: 1 et, via .demo-btn, overflow: hidden; border-radius: 12px. L'anneau est son ::before : inset: 0 (la taille exacte du bouton), padding: 2px, border-radius: 14px et un fond linear-gradient(135deg, #6366f1, #ec4899, #f59e0b) étiré à 300% 300%.
Le découpage vient de mask : deux calques, linear-gradient(#fff 0, #fff 0) content-box (un rectangle plein limité à la boîte de contenu, c'est-à-dire tout sauf les 2 px de padding) et linear-gradient(#fff 0, #fff 0) (la boîte entière), composés en exclude : là où les deux se recouvrent, le masque est vide. Il ne reste que la couronne de 2 px entre les deux boîtes. Mesuré dans Chromium : pixels colorés à 0 et 1 px du bord, face #0a0a0f à partir de 3 px.
Le mouvement est gradientMove : background-position va de 0 0 à 100% 100% et revient, en 4s ease infinite. Comme l'image fait trois fois la boîte, ce trajet fait défiler les trois couleurs le long de l'anneau — indigo à un coin, ambre à l'autre, rose entre les deux, puis inversion. Les keyframes déclarent deux positions (0 0, 100% 100%) pour un seul calque : la seconde est ignorée par le navigateur.
Au :hover, deux choses : box-shadow: 0 0 30px rgba(99,102,241,.3) sur le bouton (sans transition déclarée, donc instantané) et la même animation en 1.5s sur le ::before. Changer la durée ne redémarre pas l'animation : le navigateur conserve son currentTime. Mesuré : à 1,33 s dans le cycle de 4 s (33 %), le passage à 1,5 s place ce même instant à 89 % du nouveau cycle — le dégradé saute à une autre position à l'entrée du survol, puis à nouveau à la sortie. Voir la FAQ.
Accessibilité
- Contraste du texte : blanc sur
#0a0a0f= 19,8:1, niveau AAA — l'un des rares boutons du catalogue à passer sans retouche. La face est opaque : la couleur de page ne change rien. - Contraste de l'anneau (WCAG 1.4.11, seuil 3:1 pour le contour d'un composant) : sur la face sombre,
#6366f1= 4,4:1,#ec4899= 5,6:1,#f59e0b= 9,2:1 — le contour de 2 px est perceptible à tout moment du cycle, sur un fond de page sombre comme clair. - prefers-reduced-motion absent du code : un dégradé qui glisse en permanence. Ajoutez
@media (prefers-reduced-motion: reduce) { .btn-cta-gradient::before { animation: none } }— l'anneau reste, figé sur la position0 0(indigo → rose). - Repli dangereux sans
mask: le code repose surmasknon préfixé. Sur Chrome avant 120 et Safari avant 15.4, le masque est ignoré, le::beforedevient un rectangle dégradé plein qui se peint au-dessus du texte (il est positionné, donc peint après le contenu) : le bouton n'a plus de libellé visible. Doublez les déclarations en-webkit-mask/-webkit-mask-composite: xor, ou enveloppez le::beforedans@supports (mask-composite: exclude). - Clavier et tactile : rien ne dépend du survol pour l'essentiel — l'anneau tourne pour tout le monde. Le
:hovern'ajoute qu'un halo et une accélération ; l'outlinenatif est conservé et n'est pas rogné paroverflow: hidden. Cible ≈ 54 px de haut, au-dessus du minimum de 44 px.
Compatibilité navigateur
Requiert la propriété raccourcie mask sans préfixe avec mask-composite: exclude, inset et l'animation de background-position. Aucun JavaScript, zéro dépendance.
Sans prise en charge de mask (Chrome avant 120, Safari avant 15.4), le pseudo-élément n'est plus découpé : un rectangle dégradé plein recouvre tout le bouton, libellé compris. Ajoutez les déclarations -webkit-mask et -webkit-mask-composite: xor, ou conditionnez le ::before à @supports (mask-composite: exclude) pour retomber sur un bouton sombre sans anneau.
Le code
Structure HTML à coller dans votre page (CSS + JS disponibles avec un compte premium) :
HTML + CSS + JS complet, prêt à coller — avec des centaines d'effets premium.
Personnaliser
Options passées à l'API ou attributs data-* :
| Option / propriété | Défaut | Effet |
|---|---|---|
| animation-duration (CSS — ::before 4s / :hover::before 1.5s) | 4s / 1.5s | Vitesse du défilement au repos et au survol. Une valeur différente au survol provoque un saut de position (voir FAQ) ; gardez la même pour un mouvement continu. |
| linear-gradient(135deg, #6366f1, #ec4899, #f59e0b) (CSS — ::before) | indigo → rose → ambre | Couleurs de l'anneau. Deux couleurs suffisent ; plus il y a d'arrêts, plus le défilement paraît rapide à durée égale. |
| padding (CSS — ::before) | 2px | Épaisseur de l'anneau : c'est la zone entre la boîte de contenu (exclue par le masque) et le bord. 1px = filet ; 3px = cadre marqué. |
| background-size (CSS — ::before) | 300% 300% | Étirement du dégradé. Plus grand = transitions de couleur plus douces et défilement plus lent à durée égale ; 200% montre plus de couleurs à la fois. |
| background (CSS — .btn-cta-gradient) | #0a0a0f | Couleur de la face. Identique au fond du catalogue ; transparent laisse voir la page à travers, l'anneau seul dessine le bouton. |
| box-shadow (CSS — :hover) | 0 0 30px rgba(99,102,241,.3) | Halo au survol, instantané (aucune transition déclarée). Ajoutez transition: box-shadow .3s au bouton pour un fondu. |
| border-radius (CSS — .demo-btn 12px / ::before 14px) | 12 / 14 px | Arrondi du bouton et de l'anneau. Gardez-les égaux : à 14 px l'arc de l'anneau se creuse plus tôt que celui du bouton et laisse un fin croissant de face nue dans chaque coin. |
FAQ
animation-duration (4 s → 1,5 s) sur une animation qui garde son currentTime : un instant situé à 33 % du cycle lent se retrouve à 89 % du cycle rapide, et la position du dégradé change d'un coup — puis à nouveau au retour. Pour accélérer sans saut, ne changez pas la durée : gardez 4s et ajoutez sur :hover::before une seconde animation superposée (animation: gradientMove 4s ease infinite, gradientMove 1.5s ease infinite ne fonctionne pas sur la même propriété), ou contentez-vous du halo comme retour de survol — c'est le choix le plus simple et le plus propre.::before n'est alors plus découpé : c'est un rectangle dégradé de la taille du bouton, positionné, donc peint au-dessus du texte qu'il recouvre entièrement. Deux protections : dupliquer les déclarations avec préfixe (-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor;), ce qui couvre Chrome 1–119 et Safari 4–15.3 ; et envelopper toute la règle ::before dans @supports (mask-composite: exclude) or (-webkit-mask-composite: xor) pour qu'un navigateur sans masque n'affiche qu'un bouton sombre, lisible, sans anneau..btn-cta-gradient { background: transparent }. Le masque ne concerne que le ::before, l'anneau reste identique et le texte se pose directement sur la page. Vérifiez alors le contraste du libellé blanc avec votre fond réel, et gardez z-index: 1 — il ne sert pas ici à empiler des calques négatifs, mais il isole le bouton et évite qu'un voisin positionné ne passe entre l'anneau et la face.