Aller au contenu
Bibliotheque UIJABS

Boutons 102 variantes

Toutes les declinaisons de bouton, du plein classique au relief brutaliste. Chaque variante est un modificateur CSS qui se combine aux autres : une hierarchie (famille A), une forme (famille B) et au besoin un style visuel (famille C). Le code affiche est celui du fichier css/components/buttons.css, tel quel.

A. Hierarchie

Le niveau d'emphase. Premier choix a faire : une seule action pleine par ecran, tout le reste descend d'un cran.

Plein

L'action principale de l'ecran : Enregistrer, Valider, Payer. Une seule par vue, sinon plus rien ne ressort.

Voir le codeMasquer le code
<button class="btn btn--plein">Enregistrer</button>
.btn--plein {
  background: var(--c-primary);
  color: var(--c-primary-text);
  box-shadow: var(--sh-1);
}
.btn--plein:hover  { background: var(--c-primary-hover); }
.btn--plein:active { background: var(--c-primary-press); transform: translateY(1px); }
inline-flex h-10 items-center gap-2 rounded-[10px] bg-blue-600 px-4 text-sm font-semibold text-white shadow-sm transition hover:bg-blue-700 active:translate-y-px
Secondaire

L'action de repli a cote d'un bouton plein : Annuler, Retour, Modifier.

Voir le codeMasquer le code
<button class="btn btn--secondaire">Annuler</button>
.btn--secondaire {
  background: var(--c-surface);
  color: var(--c-text);
  border-color: var(--c-border);
  box-shadow: var(--sh-1);
}
.btn--secondaire:hover  { background: var(--c-surface-2); border-color: var(--c-border-strong); }
.btn--secondaire:active { background: var(--c-surface-3); transform: translateY(1px); }
inline-flex h-10 items-center gap-2 rounded-[10px] border border-slate-300 bg-white px-4 text-sm font-semibold text-slate-900 shadow-sm transition hover:bg-slate-50
Tonal

Entre le plein et le contour. Parfait quand deux actions comptent presque autant, ou pour un "Suivant" d'assistant.

Voir le codeMasquer le code
<button class="btn btn--tonal">Etape suivante</button>
.btn--tonal {
  background: var(--c-primary-soft);
  color: var(--c-primary-ink);
}
.btn--tonal:hover  { filter: brightness(.96); }
.btn--tonal:active { filter: brightness(.92); transform: translateY(1px); }
inline-flex h-10 items-center gap-2 rounded-[10px] bg-blue-100 px-4 text-sm font-semibold text-blue-800 transition hover:bg-blue-200
Contour

Action moyenne emphase sur fond charge (photo, tableau, carte). Le contour tient sans peser.

Voir le codeMasquer le code
<button class="btn btn--contour">Voir le detail</button>
.btn--contour {
  border-color: var(--c-primary);
  color: var(--c-primary-ink);
  background: transparent;
}
.btn--contour:hover  { background: var(--c-primary-soft); }
.btn--contour:active { background: var(--c-primary-soft); filter: brightness(.95); transform: translateY(1px); }
inline-flex h-10 items-center gap-2 rounded-[10px] border border-blue-600 bg-transparent px-4 text-sm font-semibold text-blue-600 transition hover:bg-blue-50
Fantome

Actions de barre d'outils et de menus, la ou une bordure ferait du bruit. Ne se revele qu'au survol.

Voir le codeMasquer le code
<button class="btn btn--fantome">Filtres</button>
.btn--fantome {
  background: transparent;
  color: var(--c-text-soft);
}
.btn--fantome:hover  { background: var(--c-surface-2); color: var(--c-text); }
.btn--fantome:active { background: var(--c-surface-3); }
inline-flex h-10 items-center gap-2 rounded-[10px] px-4 text-sm font-semibold text-slate-700 transition hover:bg-slate-100
Texte

Emphase minimale : "Tout effacer", "Passer cette etape". Toujours accompagne d'une action plus forte.

Voir le codeMasquer le code
<button class="btn btn--texte">Tout effacer</button>
.btn--texte {
  padding: 0 var(--sp-2);
  color: var(--c-primary-ink);
  background: transparent;
}
.btn--texte:hover  { text-decoration: underline; text-underline-offset: 3px; }
.btn--texte:active { opacity: .7; }
inline-flex h-10 items-center gap-2 px-2 text-sm font-semibold text-blue-600 transition hover:underline
Lien

Un vrai lien deguise, dans un paragraphe ou un pied de carte. Souligne par defaut, contrairement au bouton Texte.

Voir le codeMasquer le code
<a href="#" class="btn btn--lien">Consulter les conditions</a>
.btn--lien {
  height: auto;
  padding: 0;
  font-weight: var(--fw-medium);
  color: var(--c-primary-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.btn--lien:hover { text-decoration-thickness: 2px; }
text-sm font-medium text-blue-600 underline underline-offset-2 hover:text-blue-800
Eleve

Quand le bouton doit se detacher d'un fond charge : par-dessus une photo, une carte, un motif.

Voir le codeMasquer le code
<button class="btn btn--eleve">Reserver</button>
.btn--eleve {
  background: var(--c-surface);
  color: var(--c-text);
  box-shadow: var(--sh-3);
}
.btn--eleve:hover  { box-shadow: var(--sh-4); transform: translateY(-2px); }
.btn--eleve:active { box-shadow: var(--sh-2); transform: translateY(0); }
inline-flex h-10 items-center gap-2 rounded-[10px] bg-white px-4 text-sm font-semibold text-slate-900 shadow-lg transition hover:shadow-xl hover:-translate-y-0.5
Destructif plein

Suppression definitive confirmee. A reserver a la confirmation finale, pas au premier clic.

Voir le codeMasquer le code
<button class="btn btn--destructif">Supprimer le dossier</button>
.btn--destructif {
  background: var(--c-danger);
  color: #fff;
  box-shadow: var(--sh-1);
}
.btn--destructif:hover  { background: var(--c-danger-hover); }
.btn--destructif:active { transform: translateY(1px); }
inline-flex h-10 items-center gap-2 rounded-[10px] bg-red-600 px-4 text-sm font-semibold text-white transition hover:bg-red-700
Destructif contour

Le premier clic d'une suppression : signale le risque sans crier. Le plein arrive dans la modale de confirmation.

Voir le codeMasquer le code
<button class="btn btn--destructif-contour">Supprimer</button>
.btn--destructif-contour {
  border-color: var(--c-danger);
  color: var(--c-danger);
  background: transparent;
}
.btn--destructif-contour:hover  { background: var(--c-danger-soft); }
.btn--destructif-contour:active { transform: translateY(1px); }
inline-flex h-10 items-center gap-2 rounded-[10px] border border-red-300 px-4 text-sm font-semibold text-red-600 transition hover:bg-red-50
Succes

Action de validation positive et irreversible dans le bon sens : "Marquer paye", "Valider le chantier".

Voir le codeMasquer le code
<button class="btn btn--succes">Marquer comme paye</button>
.btn--succes {
  background: var(--c-success);
  color: #fff;
  box-shadow: var(--sh-1);
}
.btn--succes:hover  { filter: brightness(1.08); }
.btn--succes:active { transform: translateY(1px); }
inline-flex h-10 items-center gap-2 rounded-[10px] bg-green-600 px-4 text-sm font-semibold text-white transition hover:bg-green-700
Inverse

Bouton clair sur bandeau sombre, ou sombre sur fond clair. Sert de "plein" quand la couleur de marque est deja partout.

Voir le codeMasquer le code
<button class="btn btn--inverse">Demander un devis</button>
.btn--inverse {
  background: var(--c-text);
  color: var(--c-surface);
}
.btn--inverse:hover  { background: var(--c-text-soft); }
.btn--inverse:active { transform: translateY(1px); }
inline-flex h-10 items-center gap-2 rounded-[10px] bg-slate-900 px-4 text-sm font-semibold text-white transition hover:bg-slate-700

B. Formes, tailles, densite

Modificateurs cumulables avec n'importe quelle variante de la famille A.

Echelle de tailles

xs pour les cellules de tableau, sm pour les barres d'outils, md par defaut, lg pour les formulaires, xl pour un CTA de landing page.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein btn--xs">xs</button>
  <button class="btn btn--plein btn--sm">sm</button>
  <button class="btn btn--plein">md</button>
  <button class="btn btn--plein btn--lg">lg</button>
  <button class="btn btn--plein btn--xl">xl</button>
</div>
/* voir .btn--xs .btn--sm .btn--lg .btn--xl dans le socle commun */
h-[26px] text-xs / h-8 text-[13px] / h-10 text-sm / h-12 text-base / h-14 text-lg
Pilule

Chips, filtres, CTA marketing arrondis. Adoucit tout de suite le ton d'une interface.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein btn--pilule">Envoyer</button>
  <button class="btn btn--secondaire btn--pilule">Annuler</button>
</div>
.btn--pilule { border-radius: var(--r-full); padding-left: var(--sp-5); padding-right: var(--sp-5); }
rounded-full
Angles vifs

Registre technique, industriel, editorial. Va de pair avec la palette mono et le style brutaliste.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein btn--vif">Exporter</button>
  <button class="btn btn--secondaire btn--vif">Importer</button>
</div>
.btn--vif { border-radius: 0; }
rounded-none
Coins doux

Registre chaleureux, grand public, mobile. Le rayon large fait paraitre le bouton plus tactile.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein btn--doux btn--lg">Prendre rendez-vous</button>
  <button class="btn btn--tonal btn--doux btn--lg">Plus tard</button>
</div>
.btn--doux { border-radius: var(--r-lg); }
rounded-2xl
Pleine largeur

Formulaires mobiles, modales, colonnes etroites. Toujours en bas du bloc qu'il valide.

Voir le codeMasquer le code
<button class="btn btn--plein btn--bloc btn--lg">Creer mon compte</button>
.btn--bloc { display: flex; width: 100%; }
flex w-full justify-center
Dense (tableau)

Actions de ligne dans une liste : hauteur reduite, poids typo allege, ne casse pas le rythme des lignes.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--secondaire btn--dense">Voir</button>
  <button class="btn btn--secondaire btn--dense">Editer</button>
  <button class="btn btn--destructif-contour btn--dense">Suppr.</button>
</div>
.btn--dense {
  height: 28px;
  padding: 0 var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  border-radius: var(--r-sm);
  gap: var(--sp-1);
}
@media (pointer: coarse) { .btn--dense { min-height: 32px; } }
h-7 rounded px-2 text-xs font-medium
Tuile (icone au-dessus)

Menu d'actions d'un tableau de bord ou d'une page d'accueil d'application. Se range en grille.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--tuile"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6M9 15h6"/></svg><span>Nouveau devis</span></button>
  <button class="btn btn--tuile"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg><span>Planifier</span></button>
  <button class="btn btn--tuile"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2Z"/><circle cx="12" cy="13" r="4"/></svg><span>Photos</span></button>
</div>
.btn--tuile {
  flex-direction: column;
  height: auto;
  width: 96px;
  padding: var(--sp-3) var(--sp-2);
  gap: var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-align: center;
  white-space: normal;
  border-radius: var(--r-lg);
  border-color: var(--c-border);
  background: var(--c-surface);
  color: var(--c-text-soft);
}
.btn--tuile svg { width: 22px; height: 22px; color: var(--c-primary); }
.btn--tuile:hover { border-color: var(--c-primary); background: var(--c-primary-soft); color: var(--c-text); }
flex h-auto w-24 flex-col items-center gap-2 rounded-xl border bg-white p-3 text-xs
Coin coupe

Signature graphique forte, sans image ni police particuliere. Un seul coin coupe suffit a marquer une identite.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein btn--coupe">Telecharger</button>
  <button class="btn btn--secondaire btn--coupe">Apercu</button>
</div>
.btn--coupe {
  border-radius: 0;
  clip-path: polygon(0 0, 100% 0, 100% 62%, calc(100% - 14px) 100%, 0 100%);
  padding-right: var(--sp-6);
}
[clip-path:polygon(0_0,100%_0,100%_65%,88%_100%,0_100%)] rounded-none
Avec compteur

Onglet ou filtre qui porte un nombre : dossiers en attente, messages non lus. Le compteur herite du contraste du bouton.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--secondaire">Dossiers <span class="btn__badge">12</span></button>
  <button class="btn btn--plein">Non lus <span class="btn__badge">3</span></button>
</div>
.btn__badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--r-full);
  background: color-mix(in srgb, currentColor 16%, transparent);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  line-height: 1;
}
inline-flex items-center gap-2 [&>span]:rounded-full [&>span]:bg-black/10 [&>span]:px-1.5 [&>span]:text-xs
Avec raccourci clavier

Applications de bureau et outils internes. Affiche le raccourci pour le faire apprendre aux utilisateurs frequents.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--secondaire">Rechercher <kbd class="btn__kbd">Cmd K</kbd></button>
  <button class="btn btn--plein">Enregistrer <kbd class="btn__kbd">Cmd S</kbd></button>
</div>
.btn__kbd {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: var(--fw-medium);
  padding: 3px 6px;
  border-radius: var(--r-xs);
  background: color-mix(in srgb, currentColor 12%, transparent);
  border: 1px solid color-mix(in srgb, currentColor 20%, transparent);
  line-height: 1;
  opacity: .85;
}
[&>kbd]:rounded [&>kbd]:border [&>kbd]:bg-white/15 [&>kbd]:px-1.5 [&>kbd]:font-mono [&>kbd]:text-[11px]

C. Styles visuels

Le caractere du bouton. On en choisit UN par projet : melanger neomorphisme et brutalisme sur le meme ecran ne pardonne pas.

Degrade

Registre SaaS, application mobile, offre premium. Degrade court (deux teintes proches) pour rester lisible.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--degrade">Passer au Pro</button>
  <button class="btn btn--degrade btn--pilule btn--lg">Essayer 30 jours</button>
</div>
.btn--degrade {
  background: linear-gradient(135deg, var(--c-primary) 0%, var(--c-accent) 100%);
  color: #fff;
  border: none;
  box-shadow: var(--sh-primary);
}
.btn--degrade:hover  { filter: brightness(1.08); box-shadow: var(--sh-3), var(--sh-primary); }
.btn--degrade:active { transform: translateY(1px); filter: brightness(.96); }
bg-gradient-to-br from-blue-500 to-violet-600 text-white shadow-lg shadow-blue-500/30
Degrade glissant

Le degrade se deplace au survol au lieu de changer de luminosite. Effet vivant, zero JavaScript.

Voir le codeMasquer le code
<button class="btn btn--degrade-glissant btn--lg">Decouvrir l'offre</button>
.btn--degrade-glissant {
  background-image: linear-gradient(90deg, var(--c-primary) 0%, var(--c-accent) 45%, var(--c-primary) 100%);
  background-size: 220% auto;
  background-position: 0% center;
  color: #fff;
  border: none;
  box-shadow: var(--sh-primary);
  transition: background-position var(--t-slow), box-shadow var(--t-base), transform var(--t-fast);
}
.btn--degrade-glissant:hover  { background-position: 100% center; }
.btn--degrade-glissant:active { transform: translateY(1px); }
bg-[length:200%_auto] bg-gradient-to-r from-blue-500 via-violet-500 to-blue-500 transition-[background-position] duration-500 hover:bg-right-top
Reflet balaye

Un eclat traverse le bouton au survol. Tres efficace sur un CTA unique, insupportable si tous les boutons le font.

Voir le codeMasquer le code
<button class="btn btn--reflet btn--lg">Obtenir mon devis</button>
.btn--reflet {
  position: relative;
  overflow: hidden;
  background: var(--c-primary);
  color: var(--c-primary-text);
  border: none;
  box-shadow: var(--sh-primary);
}
.btn--reflet::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%, rgba(255,255,255,.42) 50%, transparent 80%);
  transform: translateX(-120%);
  transition: transform 700ms ease;
}
.btn--reflet:hover::before { transform: translateX(120%); }
.btn--reflet:active { transform: translateY(1px); }
relative overflow-hidden before:absolute before:inset-0 before:-translate-x-full before:bg-gradient-to-r before:from-transparent before:via-white/30 before:to-transparent hover:before:translate-x-full before:transition-transform before:duration-700
Verre depoli

Par-dessus une photo, une video ou un degrade. Ne fonctionne QUE sur fond image : sur fond uni il disparait.

Voir le codeMasquer le code
<div class="demo-photo">
  <button class="btn btn--verre">En savoir plus</button>
  <button class="btn btn--verre btn--pilule">Nos realisations</button>
</div>
.btn--verre {
  background: rgba(255,255,255,.14);
  border-color: rgba(255,255,255,.32);
  color: #fff;
  backdrop-filter: blur(12px) saturate(160%);
  -webkit-backdrop-filter: blur(12px) saturate(160%);
  text-shadow: 0 1px 2px rgba(0,0,0,.25);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.28), 0 4px 16px rgba(0,0,0,.18);
}
.btn--verre:hover  { background: rgba(255,255,255,.26); border-color: rgba(255,255,255,.5); }
.btn--verre:active { background: rgba(255,255,255,.34); transform: translateY(1px); }
border border-white/30 bg-white/15 text-white backdrop-blur-md hover:bg-white/25
Verre sombre

Meme principe en registre nocturne : superposition sur photo sombre, lecteur video, application mobile.

Voir le codeMasquer le code
<div class="demo-photo demo-photo--sombre">
  <button class="btn btn--verre-sombre">Lire la video</button>
  <button class="btn btn--verre-sombre btn--pilule">Partager</button>
</div>
.btn--verre-sombre {
  background: rgba(12,14,20,.42);
  border-color: rgba(255,255,255,.14);
  color: #fff;
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1);
}
.btn--verre-sombre:hover  { background: rgba(12,14,20,.62); border-color: rgba(255,255,255,.24); }
.btn--verre-sombre:active { transform: translateY(1px); }
border border-white/10 bg-black/40 text-white backdrop-blur-md hover:bg-black/55
Neomorphisme

Style "sculpte dans la surface". Exige un fond gris moyen identique a la couleur du bouton, jamais du blanc pur.

Voir le codeMasquer le code
<div class="demo-neo">
  <button class="btn btn--neo">Enregistrer</button>
  <button class="btn btn--neo btn--pilule">Annuler</button>
</div>
.btn--neo {
  background: var(--neo-bg, #e8ebf0);
  color: #414a58;
  border: none;
  border-radius: var(--r-lg);
  box-shadow: 6px 6px 12px var(--neo-ombre, #c6cad3), -6px -6px 12px var(--neo-lumiere, #ffffff);
  transition: box-shadow var(--t-base), transform var(--t-fast);
}
.btn--neo:hover  { box-shadow: 8px 8px 16px var(--neo-ombre, #c6cad3), -8px -8px 16px var(--neo-lumiere, #ffffff); }
.btn--neo:active {
  box-shadow: inset 4px 4px 9px var(--neo-ombre, #c6cad3), inset -4px -4px 9px var(--neo-lumiere, #ffffff);
  transform: translateY(1px);
}
rounded-xl bg-[#e8ebf0] text-slate-700 shadow-[6px_6px_12px_#c8ccd4,-6px_-6px_12px_#ffffff] active:shadow-[inset_4px_4px_8px_#c8ccd4,inset_-4px_-4px_8px_#ffffff]
Neomorphisme enfonce

L'etat "actif" ou "selectionne" du style precedent : le bouton reste creuse tant que l'option est retenue.

Voir le codeMasquer le code
<div class="demo-neo">
  <button class="btn btn--neo-enfonce">Option retenue</button>
  <button class="btn btn--neo">Option libre</button>
</div>
.btn--neo-enfonce {
  background: var(--neo-bg, #e8ebf0);
  color: var(--c-primary);
  border: none;
  border-radius: var(--r-lg);
  box-shadow: inset 5px 5px 10px var(--neo-ombre, #c6cad3), inset -5px -5px 10px var(--neo-lumiere, #ffffff);
}
shadow-[inset_5px_5px_10px_#c8ccd4,inset_-5px_-5px_10px_#ffffff] text-blue-700
Brutaliste

Ombre dure decalee, bordure noire epaisse, aucune transition molle. Portfolio, marque jeune, produit qui assume.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--brut">Voir le projet</button>
  <button class="btn btn--brut btn--brut-jaune">Nous ecrire</button>
</div>
.btn--brut {
  background: var(--c-surface);
  color: #0a0a0a;
  border: 2px solid #0a0a0a;
  border-radius: 0;
  font-weight: var(--fw-bold);
  box-shadow: 4px 4px 0 #0a0a0a;
  transition: transform 80ms linear, box-shadow 80ms linear;
}
.btn--brut:hover  { transform: translate(2px, 2px); box-shadow: 2px 2px 0 #0a0a0a; }
.btn--brut:active { transform: translate(4px, 4px); box-shadow: 0 0 0 #0a0a0a; }
.btn--brut-jaune  { background: #ffe14d; }
[data-theme="dark"] .btn--brut { background: #fafafa; }
rounded-none border-2 border-black bg-white shadow-[4px_4px_0_#000] transition-none hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-[2px_2px_0_#000]
Relief 3D

Le bouton s'enfonce physiquement au clic grace a un bord bas epais. Tres lisible pour un public peu technophile.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--relief">Valider</button>
  <button class="btn btn--relief btn--relief-vert">Confirmer</button>
</div>
.btn--relief {
  --relief-base: var(--c-primary-press);
  background: var(--c-primary);
  color: var(--c-primary-text);
  border: none;
  border-radius: var(--r-md);
  box-shadow: 0 5px 0 var(--relief-base);
  transition: transform 90ms ease, box-shadow 90ms ease;
}
.btn--relief:hover  { filter: brightness(1.06); }
.btn--relief:active { transform: translateY(5px); box-shadow: 0 0 0 var(--relief-base); }
.btn--relief-vert   { --relief-base: #0d5c2b; background: var(--c-success); }
rounded-xl bg-blue-500 text-white shadow-[0_5px_0_#1e40af] active:translate-y-[5px] active:shadow-none
Neon

Registre nocturne : jeu, streaming, technologie. Ne tient que sur fond tres sombre.

Voir le codeMasquer le code
<div class="demo-photo demo-photo--noir">
  <button class="btn btn--neon">Lancer la partie</button>
  <button class="btn btn--neon btn--neon-rose">Rejoindre</button>
</div>
.btn--neon {
  --neon: #22d3ee;
  background: transparent;
  color: var(--neon);
  border: 1px solid var(--neon);
  border-radius: var(--r-md);
  text-shadow: 0 0 8px color-mix(in srgb, var(--neon) 65%, transparent);
  box-shadow: 0 0 14px color-mix(in srgb, var(--neon) 40%, transparent),
              inset 0 0 14px color-mix(in srgb, var(--neon) 16%, transparent);
}
.btn--neon:hover {
  background: color-mix(in srgb, var(--neon) 12%, transparent);
  box-shadow: 0 0 26px color-mix(in srgb, var(--neon) 60%, transparent),
              inset 0 0 20px color-mix(in srgb, var(--neon) 24%, transparent);
}
.btn--neon:active   { transform: translateY(1px); }
.btn--neon-rose     { --neon: #f472b6; }
border border-cyan-400 bg-transparent text-cyan-300 shadow-[0_0_12px_rgba(34,211,238,.5),inset_0_0_12px_rgba(34,211,238,.2)] hover:bg-cyan-400/10
Contour degrade

Bordure en degrade avec interieur neutre. Utile quand le plein degrade serait trop lourd sur la page.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--contour-degrade">Comparer les offres</button>
  <button class="btn btn--contour-degrade btn--pilule">Nous contacter</button>
</div>
.btn--contour-degrade {
  position: relative;
  background: var(--c-surface);
  color: var(--c-text);
  border: none;
  z-index: 0;
}
.btn--contour-degrade::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  margin: -1.5px;
  border-radius: inherit;
  background: linear-gradient(120deg, var(--c-primary), var(--c-accent));
}
.btn--contour-degrade:hover { color: var(--c-primary-ink); }
.btn--contour-degrade:hover::before { filter: brightness(1.15); }
.btn--contour-degrade:active { transform: translateY(1px); }
relative border-0 bg-white before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] before:bg-gradient-to-r before:from-blue-500 before:to-violet-500 before:p-px
Remplissage glissant

La couleur monte du bas au survol. Registre agence, portfolio, site vitrine soigne.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--remplissage">Nos services</button>
  <button class="btn btn--remplissage btn--remplissage-cote">Nos tarifs</button>
</div>
.btn--remplissage {
  position: relative;
  z-index: 0;
  overflow: hidden;
  background: transparent;
  border-color: var(--c-primary);
  color: var(--c-primary-ink);
}
.btn--remplissage::before {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 0;
  z-index: -1;
  background: var(--c-primary);
  transition: height var(--t-base);
}
.btn--remplissage:hover { color: var(--c-primary-text); }
.btn--remplissage:hover::before { height: 100%; }
.btn--remplissage-cote::before { inset: 0 auto 0 0; height: 100%; width: 0; transition: width var(--t-base); }
.btn--remplissage-cote:hover::before { width: 100%; height: 100%; }
relative z-0 overflow-hidden border border-slate-900 text-slate-900 before:absolute before:inset-x-0 before:bottom-0 before:-z-10 before:h-0 before:bg-slate-900 before:transition-all hover:text-white hover:before:h-full
Soulignement anime

Navigation de site vitrine et liens de pied de page. Le trait se deploie depuis la gauche.

Voir le codeMasquer le code
<div class="demo-row">
  <a href="#" class="btn btn--souligne">Realisations</a>
  <a href="#" class="btn btn--souligne">Devis gratuit</a>
</div>
.btn--souligne {
  position: relative;
  background: transparent;
  color: var(--c-text);
  font-weight: var(--fw-medium);
}
.btn--souligne::after {
  content: "";
  position: absolute;
  left: var(--sp-4);
  bottom: 8px;
  height: 1.5px;
  width: 0;
  background: currentColor;
  transition: width var(--t-base);
}
.btn--souligne:hover::after { width: calc(100% - var(--sp-8)); }
relative after:absolute after:bottom-1 after:left-4 after:h-px after:w-0 after:bg-current after:transition-[width] hover:after:w-[calc(100%-2rem)]
Halo pulse

Un seul par page, sur l'action qu'on veut vraiment faire cliquer : "Appelez-nous", "Reserver". Attire l'oeil en peripherie.

Voir le codeMasquer le code
<button class="btn btn--pulse btn--lg btn--pilule">Demander un rappel</button>
.btn--pulse {
  position: relative;
  background: var(--c-primary);
  color: var(--c-primary-text);
  border: none;
}
.btn--pulse::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid var(--c-primary);
  animation: btn-pulse 2s cubic-bezier(.4,0,.6,1) infinite;
  pointer-events: none;
}
@keyframes btn-pulse {
  0%   { opacity: .7; transform: scale(1); }
  70%  { opacity: 0;  transform: scale(1.28); }
  100% { opacity: 0;  transform: scale(1.28); }
}
.btn--pulse:hover  { background: var(--c-primary-hover); }
.btn--pulse:active { transform: translateY(1px); }
relative after:absolute after:inset-0 after:animate-ping after:rounded-full after:bg-blue-500/40
Ondulation

L'onde partant du point de clic, signature Material. Retour tactile tres net sur mobile. Necessite 5 lignes de JS.

Voir le codeMasquer le code
<button class="btn btn--plein btn--onde" data-onde>Appliquer</button>
.btn--onde { position: relative; overflow: hidden; }
.btn__onde {
  position: absolute;
  border-radius: 50%;
  transform: scale(0);
  background: rgba(255,255,255,.55);
  pointer-events: none;
  animation: btn-onde 600ms linear;
}
@keyframes btn-onde { to { transform: scale(4); opacity: 0; } }
relative overflow-hidden  (+ JS : voir l'onglet JavaScript)
Levitation

Le bouton se souleve et son ombre s'etale. Le plus discret des effets de survol, donc le plus reutilisable.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein btn--levite">Continuer</button>
  <button class="btn btn--secondaire btn--levite">Revenir</button>
</div>
.btn--levite { transition: transform var(--t-base), box-shadow var(--t-base), background-color var(--t-fast); }
.btn--levite:hover  { transform: translateY(-3px); box-shadow: var(--sh-3); }
.btn--levite:active { transform: translateY(-1px); box-shadow: var(--sh-1); }
transition-all duration-200 hover:-translate-y-1 hover:shadow-xl
Contour pointille

Action d'ajout dans une grille : "Ajouter une photo", "Nouvelle ligne". Le pointille dit "emplacement vide".

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--pointille"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg> Ajouter une piece</button>
  <button class="btn btn--pointille btn--pointille-bloc"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg> Nouvelle ligne</button>
</div>
.btn--pointille {
  background: transparent;
  border: 1.5px dashed var(--c-border-strong);
  color: var(--c-text-muted);
  font-weight: var(--fw-medium);
}
.btn--pointille:hover  { border-color: var(--c-primary); color: var(--c-primary-ink); background: var(--c-primary-soft); }
.btn--pointille-bloc   { width: 260px; height: 64px; }
border-2 border-dashed border-slate-300 text-slate-500 hover:border-blue-500 hover:text-blue-600 hover:bg-blue-50
Relief metallique

Registre skeuomorphe assume : jeu, application retro, marque artisanale. Degrade vertical + liseres clair et sombre.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--metal">Telecharger</button>
  <button class="btn btn--metal btn--metal-or">Version Or</button>
</div>
.btn--metal {
  background: linear-gradient(180deg, #ffffff 0%, #eef0f4 48%, #dfe3ea 52%, #cdd3dd 100%);
  color: #2c3340;
  border: 1px solid #a9b1bf;
  border-radius: var(--r-sm);
  text-shadow: 0 1px 0 rgba(255,255,255,.85);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.9), 0 1px 2px rgba(0,0,0,.18);
}
.btn--metal:hover  { filter: brightness(1.03); }
.btn--metal:active { background: linear-gradient(180deg, #cdd3dd 0%, #dfe3ea 100%); box-shadow: inset 0 2px 4px rgba(0,0,0,.2); }
.btn--metal-or {
  background: linear-gradient(180deg, #fff6d8 0%, #f2dc9b 48%, #e6c96f 52%, #cfae4c 100%);
  border-color: #b3913a;
  color: #52400f;
}
bg-gradient-to-b from-white to-slate-200 border border-slate-400 shadow-[inset_0_1px_0_#fff,0_1px_2px_rgba(0,0,0,.2)]
Contour tournant

Un liser lumineux tourne autour du bouton. A garder pour une seule action "IA", "nouveaute", "en direct".

Voir le codeMasquer le code
<button class="btn btn--tournant btn--lg">Generer avec l'IA</button>
.btn--tournant {
  position: relative;
  z-index: 0;
  overflow: hidden;
  background: transparent;  /* le fond est peint par ::after, sinon il masque le liser */
  color: var(--c-text);
  border: none;
  padding: 0 var(--sp-5);
}
.btn--tournant::before {
  content: "";
  position: absolute;
  inset: -150%;
  z-index: -1;
  background: conic-gradient(from 0deg, transparent 0 260deg, var(--c-primary) 320deg, var(--c-accent) 360deg);
  animation: btn-rotation 3.2s linear infinite;
}
.btn--tournant::after {
  content: "";
  position: absolute;
  inset: 2px;
  z-index: -1;
  border-radius: calc(var(--r-md) - 1px);
  background: var(--c-surface);
}
@keyframes btn-rotation { to { transform: rotate(360deg); } }
.btn--tournant:hover { color: var(--c-primary-ink); }
relative overflow-hidden before:absolute before:inset-[-100%] before:animate-[spin_3s_linear_infinite] before:bg-[conic-gradient(from_0deg,transparent_0_300deg,#7c3aed_360deg)]
Minimal souligne epais

Registre editorial et typographique. Le fond colore n'occupe que le bas du texte, puis remonte au survol.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--marqueur">Lire l'article</button>
  <button class="btn btn--marqueur">Voir le dossier</button>
</div>
.btn--marqueur {
  background-image: linear-gradient(var(--c-primary-soft), var(--c-primary-soft));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 38%;
  color: var(--c-text);
  border-radius: 0;
  padding: 0 var(--sp-2);
  transition: background-size var(--t-base), color var(--t-base);
}
.btn--marqueur:hover { background-size: 100% 100%; }
bg-[linear-gradient(transparent_60%,#fde68a_60%)] bg-[length:100%_100%] transition-[background-size] hover:bg-[linear-gradient(transparent_0,#fde68a_0)]

D. Boutons a icone

Regle absolue : un bouton sans texte visible porte toujours un aria-label.

Icone seule carree

Barre d'outils dense, actions de ligne de tableau. Carre parfait, l'icone centree.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--icone btn--secondaire" aria-label="Modifier"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.8 2.8 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg></button>
  <button class="btn btn--icone btn--fantome" aria-label="Telecharger"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
  <button class="btn btn--icone btn--plein" aria-label="Ajouter"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg></button>
</div>
.btn--icone { width: var(--h-md); padding: 0; }
.btn--icone.btn--xs { width: var(--h-xs); }
.btn--icone.btn--sm { width: var(--h-sm); }
.btn--icone.btn--lg { width: var(--h-lg); }
.btn--icone.btn--xl { width: var(--h-xl); }
@media (pointer: coarse) { .btn--icone { min-width: 44px; } }
inline-flex h-10 w-10 items-center justify-center rounded-[10px] p-0
Icone seule ronde

Avatar d'action, lecteur multimedia, barre flottante. Le rond adoucit et signale "action isolee".

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--icone btn--rond btn--secondaire" aria-label="Precedent"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg></button>
  <button class="btn btn--icone btn--rond btn--plein btn--lg" aria-label="Lecture"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 4.5v15l13-7.5Z"/></svg></button>
  <button class="btn btn--icone btn--rond btn--secondaire" aria-label="Suivant"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></button>
</div>
.btn--rond { border-radius: var(--r-full); }
h-10 w-10 rounded-full p-0
Icone a gauche

Le cas le plus courant. L'icone precise le verbe : disquette pour enregistrer, imprimante pour imprimer.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2Z"/><path d="M17 21v-8H7v8M7 3v5h8"/></svg> Enregistrer</button>
  <button class="btn btn--secondaire"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9V2h12v7M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2M6 14h12v8H6Z"/></svg> Imprimer</button>
  <button class="btn btn--destructif-contour"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/></svg> Supprimer</button>
</div>
/* aucun style specifique : le gap du socle .btn suffit */
inline-flex items-center gap-2
Site officiel
Icone a droite

L'icone indique une suite : fleche pour avancer, chevron pour ouvrir un menu, carre pour un lien externe.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein">Continuer <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 5l7 7-7 7"/></svg></button>
  <button class="btn btn--secondaire">Options <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
  <a href="#" class="btn btn--texte">Site officiel <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14 21 3"/></svg></a>
</div>
/* aucun style specifique : le socle .btn suffit */
inline-flex items-center gap-2 [&>svg]:order-last
Bouton flottant (FAB)

Action unique et permanente d'un ecran mobile : creer un dossier, ajouter une photo. En bas a droite, au-dessus du contenu.

Voir le codeMasquer le code
<div class="demo-canevas">
  <button class="btn btn--fab" aria-label="Nouveau dossier"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg></button>
</div>
.btn--fab {
  width: 56px;
  height: 56px;
  padding: 0;
  border-radius: var(--r-lg);
  background: var(--c-primary);
  color: var(--c-primary-text);
  border: none;
  box-shadow: var(--sh-3);
}
.btn--fab svg { width: 24px; height: 24px; }
.btn--fab:hover  { background: var(--c-primary-hover); box-shadow: var(--sh-4); }
.btn--fab:active { transform: scale(.94); }
fixed bottom-6 right-6 h-14 w-14 rounded-full bg-blue-600 text-white shadow-xl
FAB etendu

Meme role, mais le libelle leve l'ambiguite. A preferer au FAB rond quand l'icone seule n'est pas evidente.

Voir le codeMasquer le code
<div class="demo-canevas">
  <button class="btn btn--fab btn--fab-etendu"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg> Nouveau devis</button>
</div>
.btn--fab-etendu { width: auto; padding: 0 var(--sp-6); border-radius: var(--r-full); font-size: var(--fs-lg); }
h-14 gap-2 rounded-full px-6 shadow-xl
Icone avec pastille

Cloche de notifications, panier, messagerie. La pastille ne porte jamais d'info vitale seule (couleur + nombre).

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--icone btn--fantome btn--pastille" data-pastille="7" aria-label="7 notifications"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9M13.7 21a2 2 0 0 1-3.4 0"/></svg></button>
  <button class="btn btn--icone btn--secondaire btn--pastille btn--pastille-point" aria-label="Messages non lus"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m2 7 10 6 10-6"/></svg></button>
</div>
.btn--pastille { position: relative; overflow: visible; }
.btn--pastille::after {
  content: attr(data-pastille);
  position: absolute;
  top: -4px; right: -4px;
  min-width: 18px; height: 18px;
  padding: 0 4px;
  border-radius: var(--r-full);
  background: var(--c-danger);
  color: #fff;
  font-size: 10px;
  font-weight: var(--fw-bold);
  line-height: 18px;
  text-align: center;
  border: 2px solid var(--c-surface);
  box-sizing: content-box;
}
.btn--pastille-point::after { content: ""; min-width: 8px; width: 8px; height: 8px; padding: 0; top: 0; right: 0; }
relative after:absolute after:right-1 after:top-1 after:h-4 after:w-4 after:rounded-full after:bg-red-600 after:text-[10px] after:text-white
Devis enregistre.
Fermeture

Coin de modale, de bandeau, de chip. Zone cliquable elargie pour rester attrapable au doigt.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--fermer" aria-label="Fermer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button>
  <div class="demo-bandeau">Devis enregistre. <button class="btn btn--fermer btn--fermer-clair" aria-label="Fermer"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button></div>
</div>
.btn--fermer {
  width: 32px; height: 32px;
  padding: 0;
  border-radius: var(--r-full);
  color: var(--c-text-muted);
  background: transparent;
}
.btn--fermer:hover  { background: var(--c-surface-3); color: var(--c-text); }
.btn--fermer-clair  { color: inherit; }
.btn--fermer-clair:hover { background: rgba(255,255,255,.2); }
h-8 w-8 rounded-full p-0 text-slate-500 hover:bg-slate-100 hover:text-slate-900
Icone avec info-bulle

Quand la place manque pour un libelle. L'info-bulle CSS apparait au survol ET au focus clavier.

Voir le codeMasquer le code
<div class="demo-row demo-row--haute">
  <button class="btn btn--icone btn--secondaire btn--bulle" data-bulle="Exporter en PDF" aria-label="Exporter en PDF"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
  <button class="btn btn--icone btn--secondaire btn--bulle" data-bulle="Dupliquer le dossier" aria-label="Dupliquer le dossier"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
</div>
.btn--bulle { position: relative; overflow: visible; }
.btn--bulle::before {
  content: attr(data-bulle);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  padding: 6px 10px;
  border-radius: var(--r-sm);
  background: var(--c-text);
  color: var(--c-surface);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-fast), transform var(--t-fast);
  z-index: 10;
}
.btn--bulle:hover::before,
.btn--bulle:focus-visible::before { opacity: 1; transform: translateX(-50%) translateY(0); }
group relative [&:hover>span]:opacity-100
Avatar cliquable

Menu de compte en haut a droite. Photo ou initiales, avec anneau au survol.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--avatar" aria-label="Mon compte"><span>JK</span></button>
  <button class="btn btn--avatar btn--avatar-large" aria-label="Mon compte"><span>JK</span> Julien K. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
</div>
.btn--avatar {
  width: 40px; height: 40px;
  padding: 0;
  border-radius: var(--r-full);
  background: transparent;
  color: var(--c-text);
}
.btn--avatar > span {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; flex: none;
  border-radius: var(--r-full);
  background: var(--c-primary-soft);
  color: var(--c-primary-ink);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}
.btn--avatar:hover > span { box-shadow: 0 0 0 3px var(--c-primary-soft); }
.btn--avatar-large { width: auto; padding: 0 var(--sp-3) 0 2px; gap: var(--sp-2); font-weight: var(--fw-medium); }
.btn--avatar-large:hover { background: var(--c-surface-2); }
h-10 w-10 rounded-full bg-blue-100 text-sm font-bold text-blue-700 ring-2 ring-transparent hover:ring-blue-300

E. Boutons composes

Plusieurs actions dans un meme objet visuel.

Groupe attache

Actions de meme nature qu'on veut lire comme un bloc : Jour / Semaine / Mois, Copier / Coller / Couper.

Voir le codeMasquer le code
<div class="btn-groupe">
  <button class="btn btn--secondaire">Jour</button>
  <button class="btn btn--secondaire">Semaine</button>
  <button class="btn btn--secondaire">Mois</button>
</div>
.btn-groupe { display: inline-flex; }
.btn-groupe > .btn { border-radius: 0; }
.btn-groupe > .btn:first-child { border-top-left-radius: var(--r-md); border-bottom-left-radius: var(--r-md); }
.btn-groupe > .btn:last-child  { border-top-right-radius: var(--r-md); border-bottom-right-radius: var(--r-md); }
.btn-groupe > .btn + .btn { margin-left: -1px; }
.btn-groupe > .btn:hover, .btn-groupe > .btn:focus-visible { position: relative; z-index: 1; }
inline-flex [&>button]:rounded-none [&>button:first-child]:rounded-l-lg [&>button:last-child]:rounded-r-lg [&>button+button]:-ml-px
Controle segmente

Choix exclusif entre 2 a 4 options courtes. La pastille glisse sur l'option retenue.

Voir le codeMasquer le code
<div class="btn-segmente" data-segmente>
  <button class="btn is-actif">Tous</button>
  <button class="btn">En cours</button>
  <button class="btn">Termines</button>
</div>
.btn-segmente {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border-radius: var(--r-md);
  background: var(--c-surface-2);
  border: 1px solid var(--c-border);
}
.btn-segmente > .btn {
  height: 32px;
  padding: 0 var(--sp-4);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  border-radius: calc(var(--r-md) - 4px);
  color: var(--c-text-soft);
}
.btn-segmente > .btn:hover { color: var(--c-text); }
.btn-segmente > .btn.is-actif {
  background: var(--c-surface);
  color: var(--c-text);
  font-weight: var(--fw-semi);
  box-shadow: var(--sh-1);
}
inline-flex rounded-lg bg-slate-100 p-1 [&>button]:rounded-md [&>button.is-active]:bg-white [&>button.is-active]:shadow
Bouton scinde

Une action par defaut plus un menu de variantes : "Enregistrer" avec "Enregistrer et fermer", "Enregistrer comme modele".

Voir le codeMasquer le code
<div class="btn-scinde">
  <button class="btn btn--plein">Enregistrer</button>
  <button class="btn btn--plein btn--scinde-menu" aria-label="Autres options"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
</div>
.btn-scinde { display: inline-flex; }
.btn-scinde > .btn:first-child { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.btn-scinde > .btn:last-child  {
  border-top-left-radius: 0; border-bottom-left-radius: 0;
  width: 34px; padding: 0;
  border-left: 1px solid rgba(255,255,255,.28);
}
.btn-scinde > .btn:last-child svg { width: 16px; height: 16px; }
inline-flex [&>button:first-child]:rounded-r-none [&>button:last-child]:w-9 [&>button:last-child]:rounded-l-none [&>button:last-child]:border-l [&>button:last-child]:border-white/25
Bouton menu

Ouvre une liste d'actions. Le chevron pivote quand le menu est ouvert : etat visible sans lire le menu.

Voir le codeMasquer le code
<button class="btn btn--secondaire btn--menu" aria-expanded="false">Actions <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
.btn--menu > svg { transition: transform var(--t-base); }
.btn--menu[aria-expanded="true"] > svg { transform: rotate(180deg); }
[&>svg]:transition-transform [&[aria-expanded=true]>svg]:rotate-180
Bascule

Etat binaire qui reste : "Favori", "Notifications". Porte aria-pressed, pas seulement une classe.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--secondaire btn--bascule" aria-pressed="false"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1Z"/></svg> Favori</button>
  <button class="btn btn--secondaire btn--bascule" aria-pressed="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1Z"/></svg> Favori</button>
</div>
.btn--bascule[aria-pressed="true"] {
  background: var(--c-primary-soft);
  border-color: var(--c-primary);
  color: var(--c-primary-ink);
}
.btn--bascule[aria-pressed="true"] svg { fill: currentColor; }
aria-pressed:bg-blue-50 aria-pressed:border-blue-500 aria-pressed:text-blue-700
Interrupteur

Reglage qui s'applique immediatement, sans bouton Valider. Si un Valider est necessaire, prendre une case a cocher.

Voir le codeMasquer le code
<div class="demo-col">
  <label class="btn-interrupteur"><input type="checkbox"><span class="btn-interrupteur__piste"></span> Recevoir les alertes</label>
  <label class="btn-interrupteur"><input type="checkbox" checked><span class="btn-interrupteur__piste"></span> Mode sombre automatique</label>
</div>
.btn-interrupteur { display: inline-flex; align-items: center; gap: var(--sp-3); cursor: pointer; font-size: var(--fs-md); color: var(--c-text); }
.btn-interrupteur input { position: absolute; opacity: 0; width: 0; height: 0; }
.btn-interrupteur__piste {
  position: relative;
  width: 44px; height: 26px; flex: none;
  border-radius: var(--r-full);
  background: var(--c-border-strong);
  transition: background-color var(--t-base);
}
.btn-interrupteur__piste::after {
  content: "";
  position: absolute;
  top: 3px; left: 3px;
  width: 20px; height: 20px;
  border-radius: var(--r-full);
  background: #fff;
  box-shadow: var(--sh-1);
  transition: transform var(--t-spring);
}
.btn-interrupteur input:checked + .btn-interrupteur__piste { background: var(--c-primary); }
.btn-interrupteur input:checked + .btn-interrupteur__piste::after { transform: translateX(18px); }
.btn-interrupteur input:focus-visible + .btn-interrupteur__piste { outline: 2px solid var(--c-focus); outline-offset: 2px; }
peer sr-only + peer-checked:bg-blue-600 peer-checked:[&>span]:translate-x-5
Compteur

Quantite dans un panier, nombre de radiateurs, metres de gaine. Champ central editable au clavier.

Voir le codeMasquer le code
<div class="btn-compteur">
  <button class="btn" aria-label="Retirer un"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/></svg></button>
  <input type="text" value="3" aria-label="Quantite" inputmode="numeric">
  <button class="btn" aria-label="Ajouter un"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg></button>
</div>
.btn-compteur {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  overflow: hidden;
}
.btn-compteur > .btn { width: 38px; height: 38px; padding: 0; border-radius: 0; color: var(--c-text-soft); }
.btn-compteur > .btn:hover { background: var(--c-surface-2); color: var(--c-text); }
.btn-compteur > input {
  width: 48px; height: 38px;
  border: none;
  border-left: 1px solid var(--c-border);
  border-right: 1px solid var(--c-border);
  background: transparent;
  text-align: center;
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  color: var(--c-text);
}
.btn-compteur > input:focus { outline: none; background: var(--c-primary-soft); }
inline-flex items-center rounded-lg border [&>input]:w-12 [&>input]:border-x [&>input]:text-center
4 dossiers
Barre d'actions flottante

Apparait quand des lignes sont cochees dans une liste. Regroupe les actions de masse sans encombrer l'ecran au repos.

Voir le codeMasquer le code
<div class="btn-barre">
  <span class="btn-barre__compte">4 dossiers</span>
  <button class="btn btn--fantome btn--dense"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg> Exporter</button>
  <button class="btn btn--fantome btn--dense"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> Dupliquer</button>
  <span class="btn-barre__sep"></span>
  <button class="btn btn--fantome btn--dense btn-barre__danger"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/></svg> Supprimer</button>
</div>
.btn-barre {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 6px 10px;
  border-radius: var(--r-full);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  box-shadow: var(--sh-3);
}
.btn-barre__compte { font-size: var(--fs-xs); font-weight: var(--fw-bold); color: var(--c-text-muted); padding: 0 var(--sp-2); }
.btn-barre__sep { width: 1px; height: 20px; background: var(--c-border); margin: 0 4px; }
.btn-barre__danger { color: var(--c-danger); }
.btn-barre__danger:hover { background: var(--c-danger-soft); color: var(--c-danger); }
inline-flex items-center gap-1 rounded-full border bg-white px-2 py-1.5 shadow-xl

F. Etats

Un bouton sans etat de chargement produit toujours un double envoi.

defaut
:hover
:active
:focus-visible
:disabled
chargement
Les six etats

Grille de reference. Tout composant livre doit couvrir ces six cas, y compris le focus clavier.

Voir le codeMasquer le code
<div class="demo-etats">
  <div><button class="btn btn--plein">Normal</button><em>defaut</em></div>
  <div><button class="btn btn--plein demo-hover">Survol</button><em>:hover</em></div>
  <div><button class="btn btn--plein demo-active">Presse</button><em>:active</em></div>
  <div><button class="btn btn--plein demo-focus">Focus</button><em>:focus-visible</em></div>
  <div><button class="btn btn--plein" disabled>Desactive</button><em>:disabled</em></div>
  <div><button class="btn btn--plein btn--chargement" data-libelle="Envoi"><span class="btn__spin"></span>Envoi</button><em>chargement</em></div>
</div>
.demo-hover  { background: var(--c-primary-hover) !important; }
.demo-active { background: var(--c-primary-press) !important; transform: translateY(1px); }
.demo-focus  { outline: 2px solid var(--c-focus); outline-offset: 2px; }
hover: / active: / focus-visible: / disabled: / aria-busy
Chargement

Des le clic : desactiver, afficher le tourniquet, garder la largeur. Sinon la mise en page saute et l'utilisateur reclique.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein btn--chargement" aria-busy="true"><span class="btn__spin"></span> Envoi en cours</button>
  <button class="btn btn--secondaire btn--chargement" aria-busy="true"><span class="btn__spin"></span> Chargement</button>
</div>
.btn__spin {
  width: 15px; height: 15px; flex: none;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
  border-radius: 50%;
  animation: btn-spin .7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
.btn--chargement { pointer-events: none; opacity: .85; }
aria-busy:pointer-events-none aria-busy:opacity-80 + spinner animate-spin
Chargement avec progression

Pour les traitements longs et mesurables : envoi de fichiers, generation d'un export. Une barre remplit le bouton.

Voir le codeMasquer le code
<button class="btn btn--plein btn--progression" style="--progression: 62%">Envoi du PDF... 62 %</button>
.btn--progression { position: relative; overflow: hidden; pointer-events: none; }
.btn--progression::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--progression, 0%);
  background: rgba(255,255,255,.26);
  transition: width var(--t-slow);
}
relative overflow-hidden before:absolute before:inset-y-0 before:left-0 before:bg-white/25 before:[width:var(--p)]
Confirmation

Apres une action reussie, le bouton devient vert 2 secondes avant de revenir. Evite un message de plus a l'ecran.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--plein btn--confirme"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m20 6-11 11-5-5"/></svg> Enregistre</button>
  <button class="btn btn--secondaire btn--confirme"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m20 6-11 11-5-5"/></svg> Copie</button>
</div>
.btn--confirme {
  background: var(--c-success) !important;
  border-color: var(--c-success) !important;
  color: #fff !important;
  pointer-events: none;
}
bg-green-600 text-white transition-colors
Desactive explique

Un bouton grise sans explication bloque l'utilisateur. L'enveloppe porte l'info-bulle car un element desactive ne recoit pas le survol.

Voir le codeMasquer le code
<span class="btn-explique" data-bulle="Renseignez d'abord le client">
  <button class="btn btn--plein" disabled>Generer le devis</button>
</span>
.btn-explique { position: relative; display: inline-flex; }
.btn-explique::before {
  content: attr(data-bulle);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  padding: 6px 10px;
  border-radius: var(--r-sm);
  background: var(--c-text);
  color: var(--c-surface);
  font-size: var(--fs-xs);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-fast), transform var(--t-fast);
  z-index: 10;
}
.btn-explique:hover::before { opacity: 1; transform: translateX(-50%) translateY(0); }
group relative [&>button]:pointer-events-none
Selectionne persistant

Element de navigation ou filtre actif. Le repere ne doit pas etre uniquement la couleur : ici couleur + trait.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--fantome btn--onglet is-actif">Dossiers</button>
  <button class="btn btn--fantome btn--onglet">Devis</button>
  <button class="btn btn--fantome btn--onglet">Factures</button>
</div>
.btn--onglet { position: relative; border-radius: var(--r-sm) var(--r-sm) 0 0; }
.btn--onglet.is-actif { color: var(--c-primary-ink); background: transparent; }
.btn--onglet.is-actif::after {
  content: "";
  position: absolute;
  left: var(--sp-3); right: var(--sp-3); bottom: 0;
  height: 2px;
  border-radius: 2px 2px 0 0;
  background: var(--c-primary);
}
relative aria-selected:text-blue-600 after:absolute after:inset-x-2 after:bottom-0 after:h-0.5 aria-selected:after:bg-blue-600

G. Cas metier

Situations concretes qui reviennent dans les projets : artisan, CRM, e-commerce.

CTA de landing page

L'unique bouton de la banniere d'accueil. Grand, contraste, avec sous-titre rassurant sous le libelle principal.

Voir le codeMasquer le code
<button class="btn btn--cta">
  <span class="btn__cta-titre">Demander mon devis gratuit</span>
  <span class="btn__cta-sous">Reponse sous 24 h, sans engagement</span>
</button>
.btn--cta {
  flex-direction: column;
  gap: 3px;
  height: auto;
  padding: var(--sp-4) var(--sp-8);
  border-radius: var(--r-lg);
  background: var(--c-primary);
  color: var(--c-primary-text);
  border: none;
  box-shadow: var(--sh-3), var(--sh-primary);
  transition: transform var(--t-base), box-shadow var(--t-base), background-color var(--t-fast);
}
.btn__cta-titre { font-size: var(--fs-lg); font-weight: var(--fw-bold); }
.btn__cta-sous  { font-size: var(--fs-xs); font-weight: var(--fw-normal); opacity: .85; }
.btn--cta:hover  { background: var(--c-primary-hover); transform: translateY(-3px); box-shadow: var(--sh-4), var(--sh-primary); }
.btn--cta:active { transform: translateY(-1px); }
flex h-auto flex-col gap-1 rounded-2xl bg-blue-600 px-8 py-4 text-white shadow-xl hover:-translate-y-1
Appel telephonique

Le bouton le plus rentable d'un site d'artisan. Numero visible en clair : beaucoup d'utilisateurs le composent a la main.

Voir le codeMasquer le code
<div class="demo-row">
  <a href="tel:+33612345678" class="btn btn--tel"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2 4.2 2 2 0 0 1 4 2h3a2 2 0 0 1 2 1.7c.1 1 .4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8 9.8a16 16 0 0 0 6 6l1.2-1.2a2 2 0 0 1 2.1-.5c.9.3 1.8.6 2.8.7A2 2 0 0 1 22 16.9Z"/></svg> <span>06 12 34 56 78</span></a>
  <a href="tel:+33612345678" class="btn btn--tel btn--tel-compact" aria-label="Appeler"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2 4.2 2 2 0 0 1 4 2h3a2 2 0 0 1 2 1.7c.1 1 .4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8 9.8a16 16 0 0 0 6 6l1.2-1.2a2 2 0 0 1 2.1-.5c.9.3 1.8.6 2.8.7A2 2 0 0 1 22 16.9Z"/></svg></a>
</div>
.btn--tel {
  height: var(--h-lg);
  padding: 0 var(--sp-6);
  border-radius: var(--r-full);
  background: var(--c-text);
  color: var(--c-surface);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: .02em;
  border: none;
  box-shadow: var(--sh-2);
}
.btn--tel:hover  { background: var(--c-text-soft); transform: translateY(-2px); box-shadow: var(--sh-3); }
.btn--tel-compact { width: var(--h-lg); padding: 0; }
inline-flex h-12 items-center gap-2 rounded-full bg-slate-900 px-6 text-base font-bold text-white
WhatsApp

Canal prefere pour un premier contact avec photos. Couleur de marque WhatsApp imposee, pas la palette du site.

Voir le codeMasquer le code
<div class="demo-row">
  <a href="https://wa.me/33664986941" class="btn btn--whatsapp"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2a10 10 0 0 0-8.5 15.2L2 22l4.9-1.5A10 10 0 1 0 12 2Zm5.3 14.1c-.2.6-1.3 1.2-1.8 1.2-.5.1-1 .1-1.7-.1-.4-.1-.9-.3-1.5-.6a11 11 0 0 1-4.3-4c-.3-.5-.7-1.2-.7-2.2 0-1 .5-1.5.7-1.7.2-.2.5-.3.6-.3h.5c.2 0 .4 0 .6.4l.7 1.8c.1.1.1.3 0 .5l-.3.4-.3.3c-.1.1-.2.3 0 .5.2.3.7 1.1 1.4 1.7.9.8 1.6 1 1.9 1.2.2.1.4 0 .5-.1l.7-.8c.2-.2.3-.2.5-.1l1.7.8c.2.1.4.2.4.3.1.1.1.5-.1 1Z"/></svg> Discuter sur WhatsApp</a>
  <a href="https://wa.me/33664986941" class="btn btn--whatsapp btn--icone btn--rond" aria-label="WhatsApp"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2a10 10 0 0 0-8.5 15.2L2 22l4.9-1.5A10 10 0 1 0 12 2Zm5.3 14.1c-.2.6-1.3 1.2-1.8 1.2-.5.1-1 .1-1.7-.1-.4-.1-.9-.3-1.5-.6a11 11 0 0 1-4.3-4c-.3-.5-.7-1.2-.7-2.2 0-1 .5-1.5.7-1.7.2-.2.5-.3.6-.3h.5c.2 0 .4 0 .6.4l.7 1.8c.1.1.1.3 0 .5l-.3.4-.3.3c-.1.1-.2.3 0 .5.2.3.7 1.1 1.4 1.7.9.8 1.6 1 1.9 1.2.2.1.4 0 .5-.1l.7-.8c.2-.2.3-.2.5-.1l1.7.8c.2.1.4.2.4.3.1.1.1.5-.1 1Z"/></svg></a>
</div>
.btn--whatsapp {
  background: #25d366;
  color: #05341a;
  border: none;
  font-weight: var(--fw-bold);
  box-shadow: 0 4px 14px rgba(37,211,102,.35);
}
.btn--whatsapp:hover  { background: #1fbe5b; }
.btn--whatsapp:active { transform: translateY(1px); }
bg-[#25D366] text-white hover:bg-[#1eb955]
Telechargement de document

Ligne de galerie de documents : nom du fichier, poids, action. Le poids evite les mauvaises surprises en 4G.

Voir le codeMasquer le code
<div class="demo-col">
  <a href="#" class="btn btn--fichier"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z"/><path d="M14 2v6h6"/><path d="M8.5 17v-4h1.2a1.2 1.2 0 0 1 0 2.4H8.5M13 17v-4h1a2 2 0 0 1 0 4Z" stroke-width="1.4"/></svg><span class="btn__fichier-nom">Devis LTP-2026-103.pdf</span><span class="btn__fichier-taille">248 Ko</span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></a>
  <a href="#" class="btn btn--fichier"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg><span class="btn__fichier-nom">Photo avant travaux.jpg</span><span class="btn__fichier-taille">1,8 Mo</span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></a>
</div>
.btn--fichier {
  display: flex;
  width: 100%;
  max-width: 420px;
  height: auto;
  padding: var(--sp-3);
  gap: var(--sp-3);
  justify-content: flex-start;
  text-align: left;
  border-radius: var(--r-md);
  border-color: var(--c-border);
  background: var(--c-surface);
  color: var(--c-text);
  font-weight: var(--fw-medium);
}
.btn--fichier > svg:first-child { width: 22px; height: 22px; color: var(--c-danger); }
.btn__fichier-nom    { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-md); }
.btn__fichier-taille { font-size: var(--fs-xs); color: var(--c-text-muted); font-weight: var(--fw-normal); }
.btn--fichier > svg:last-child { color: var(--c-text-muted); }
.btn--fichier:hover { border-color: var(--c-primary); background: var(--c-primary-soft); }
.btn--fichier:hover > svg:last-child { color: var(--c-primary); }
flex w-full items-center gap-3 rounded-lg border bg-white px-3 py-2 text-left hover:border-blue-400 hover:bg-blue-50/50
Zone de depot

Ajout de pieces jointes. La zone entiere est cliquable ET recoit le glisser-deposer. Etat de survol de fichier inclus.

Voir le codeMasquer le code
<label class="btn btn--depot">
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12"/></svg>
  <span class="btn__depot-titre">Deposer les photos ici</span>
  <span class="btn__depot-sous">ou cliquer pour parcourir - JPG, PNG, PDF - 10 Mo max</span>
  <input type="file" multiple hidden>
</label>
.btn--depot {
  flex-direction: column;
  gap: var(--sp-2);
  width: 100%;
  max-width: 420px;
  height: auto;
  padding: var(--sp-8) var(--sp-6);
  border: 2px dashed var(--c-border-strong);
  border-radius: var(--r-lg);
  background: var(--c-surface-2);
  color: var(--c-text-soft);
  text-align: center;
  white-space: normal;
}
.btn--depot svg { width: 30px; height: 30px; color: var(--c-text-muted); }
.btn__depot-titre { font-size: var(--fs-md); font-weight: var(--fw-semi); color: var(--c-text); }
.btn__depot-sous  { font-size: var(--fs-xs); font-weight: var(--fw-normal); color: var(--c-text-muted); }
.btn--depot:hover, .btn--depot.is-survol {
  border-color: var(--c-primary);
  background: var(--c-primary-soft);
}
.btn--depot:hover svg, .btn--depot.is-survol svg { color: var(--c-primary); }
flex h-auto w-full flex-col gap-2 rounded-xl border-2 border-dashed p-8 hover:border-blue-500 hover:bg-blue-50
LTP-2026-103
Copier

A cote d'une reference, d'un IBAN, d'un lien de partage. Bascule sur "Copie" pendant 2 secondes apres le clic.

Voir le codeMasquer le code
<div class="btn-copier">
  <code>LTP-2026-103</code>
  <button class="btn btn--icone btn--fantome btn--sm" aria-label="Copier la reference"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
</div>
.btn-copier {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 4px 4px 4px var(--sp-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface-2);
}
.btn-copier code { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--c-text); }
inline-flex items-center gap-1 rounded-lg border bg-slate-50 py-1 pl-3 pr-1 font-mono text-sm
Connexion externe

Boutons Google, Apple, Microsoft. Les chartes des fournisseurs imposent un fond neutre et le logo d'origine.

Voir le codeMasquer le code
<div class="demo-col">
  <button class="btn btn--social btn--bloc"><svg viewBox="0 0 24 24" aria-hidden="true"><path fill="#4285F4" d="M23 12.3c0-.8-.1-1.6-.2-2.3H12v4.5h6.2a5.3 5.3 0 0 1-2.3 3.5v2.9h3.7c2.2-2 3.4-5 3.4-8.6Z"/><path fill="#34A853" d="M12 23.5c3.1 0 5.7-1 7.6-2.8l-3.7-2.9c-1 .7-2.3 1.1-3.9 1.1-3 0-5.5-2-6.4-4.7H1.8v3A11.5 11.5 0 0 0 12 23.5Z"/><path fill="#FBBC05" d="M5.6 14.2a6.9 6.9 0 0 1 0-4.4v-3H1.8a11.5 11.5 0 0 0 0 10.4Z"/><path fill="#EA4335" d="M12 5.1c1.7 0 3.2.6 4.4 1.7l3.3-3.3A11.5 11.5 0 0 0 1.8 6.8l3.8 3a6.9 6.9 0 0 1 6.4-4.7Z"/></svg> Continuer avec Google</button>
  <button class="btn btn--social btn--social-noir btn--bloc"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M16.4 12.7c0-2.6 2.1-3.9 2.2-4-1.2-1.8-3.1-2-3.8-2-1.6-.2-3.1 1-3.9 1-.8 0-2-1-3.3-1-1.7 0-3.3 1-4.2 2.5-1.8 3.1-.5 7.7 1.3 10.2.9 1.3 1.9 2.7 3.2 2.6 1.3 0 1.8-.8 3.3-.8 1.5 0 2 .8 3.3.8 1.4 0 2.2-1.2 3.1-2.5 1-1.4 1.4-2.8 1.4-2.9-.1 0-2.6-1-2.6-3.9ZM14 4.4c.7-.9 1.2-2.1 1-3.4-1 0-2.3.7-3 1.6-.7.8-1.3 2-1.1 3.2 1.2.1 2.4-.6 3.1-1.4Z"/></svg> Continuer avec Apple</button>
</div>
.btn--social {
  height: 44px;
  background: var(--c-surface);
  border-color: var(--c-border-strong);
  color: var(--c-text);
  font-weight: var(--fw-medium);
}
.btn--social:hover { background: var(--c-surface-2); }
.btn--social-noir { background: #0a0a0a; border-color: #0a0a0a; color: #fff; }
.btn--social-noir:hover { background: #262626; }
flex h-11 w-full items-center justify-center gap-3 rounded-lg border bg-white font-medium
Chip de filtre

Filtres d'une liste. Le chip retenu porte une coche : lisible meme en noir et blanc ou en cas de daltonisme.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--chip" aria-pressed="false">Tous</button>
  <button class="btn btn--chip" aria-pressed="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m20 6-11 11-5-5"/></svg> En cours</button>
  <button class="btn btn--chip" aria-pressed="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m20 6-11 11-5-5"/></svg> Urgents</button>
  <button class="btn btn--chip" aria-pressed="false">Archives</button>
</div>
.btn--chip {
  height: 32px;
  padding: 0 var(--sp-3);
  border-radius: var(--r-full);
  border-color: var(--c-border);
  background: var(--c-surface);
  color: var(--c-text-soft);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
}
.btn--chip:hover { border-color: var(--c-border-strong); color: var(--c-text); }
.btn--chip[aria-pressed="true"] {
  border-color: var(--c-primary);
  background: var(--c-primary-soft);
  color: var(--c-primary-ink);
  font-weight: var(--fw-semi);
}
.btn--chip svg { width: 14px; height: 14px; }
h-8 rounded-full border px-3 text-[13px] aria-pressed:border-blue-600 aria-pressed:bg-blue-50 aria-pressed:text-blue-700
Departement 93 Pompe a chaleur
Etiquette retirable

Filtres actifs affiches au-dessus d'une liste. La croix est un vrai bouton imbrique, avec son propre libelle accessible.

Voir le codeMasquer le code
<div class="demo-row">
  <span class="btn-etiquette">Departement 93 <button aria-label="Retirer le filtre departement 93"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button></span>
  <span class="btn-etiquette btn-etiquette--accent">Pompe a chaleur <button aria-label="Retirer le filtre pompe a chaleur"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg></button></span>
</div>
.btn-etiquette {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  height: 28px;
  padding: 0 4px 0 var(--sp-3);
  border-radius: var(--r-full);
  background: var(--c-surface-3);
  color: var(--c-text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
}
.btn-etiquette button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px;
  border: none; padding: 0;
  border-radius: var(--r-full);
  background: transparent;
  color: var(--c-text-muted);
  cursor: pointer;
}
.btn-etiquette button svg { width: 12px; height: 12px; }
.btn-etiquette button:hover { background: color-mix(in srgb, currentColor 18%, transparent); color: var(--c-text); }
.btn-etiquette--accent { background: var(--c-primary-soft); color: var(--c-primary-ink); }
inline-flex items-center gap-1 rounded-full bg-slate-100 py-1 pl-3 pr-1 text-[13px]
Pagination

Bas de liste. La page courante porte aria-current, les fleches sont desactivees aux extremites.

Voir le codeMasquer le code
<nav class="btn-pagination" aria-label="Pagination">
  <button class="btn btn--icone btn--secondaire btn--sm" disabled aria-label="Page precedente"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg></button>
  <button class="btn btn--sm is-actif" aria-current="page">1</button>
  <button class="btn btn--sm">2</button>
  <button class="btn btn--sm">3</button>
  <span class="btn-pagination__points">...</span>
  <button class="btn btn--sm">12</button>
  <button class="btn btn--icone btn--secondaire btn--sm" aria-label="Page suivante"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></button>
</nav>
.btn-pagination { display: inline-flex; align-items: center; gap: var(--sp-1); }
.btn-pagination > .btn { min-width: 32px; padding: 0 var(--sp-2); color: var(--c-text-soft); }
.btn-pagination > .btn:hover { background: var(--c-surface-2); color: var(--c-text); }
.btn-pagination > .btn.is-actif {
  background: var(--c-primary);
  color: var(--c-primary-text);
  font-weight: var(--fw-bold);
}
.btn-pagination__points { color: var(--c-text-muted); padding: 0 var(--sp-1); }
inline-flex items-center gap-1 [&>button]:h-8 [&>button]:min-w-8 aria-current:bg-blue-600 aria-current:text-white
Retour en haut

Page longue sur mobile. Apparait apres 600 px de defilement, en bas a droite, au-dessus du contenu.

Voir le codeMasquer le code
<div class="demo-canevas">
  <button class="btn btn--haut" aria-label="Revenir en haut de page"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M5 12l7-7 7 7"/></svg></button>
</div>
.btn--haut {
  width: 44px; height: 44px;
  padding: 0;
  border-radius: var(--r-full);
  background: color-mix(in srgb, var(--c-surface) 88%, transparent);
  backdrop-filter: blur(8px);
  border-color: var(--c-border);
  color: var(--c-text-soft);
  box-shadow: var(--sh-2);
}
.btn--haut:hover { background: var(--c-surface); color: var(--c-text); transform: translateY(-2px); box-shadow: var(--sh-3); }
fixed bottom-6 right-6 h-11 w-11 rounded-full border bg-white/90 shadow-lg backdrop-blur
Suppression a maintenir

Alternative a la modale de confirmation : il faut garder le doigt appuye 1,5 s. Un clic accidentel ne detruit rien.

Voir le codeMasquer le code
<button class="btn btn--destructif btn--maintenir"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6"/></svg> Maintenir pour supprimer</button>
.btn--maintenir { position: relative; overflow: hidden; }
.btn--maintenir::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 0;
  background: rgba(255,255,255,.34);
  transition: width 60ms linear;
}
.btn--maintenir:active::before { width: 100%; transition: width 1500ms linear; }
relative overflow-hidden before:absolute before:inset-y-0 before:left-0 before:w-0 before:bg-white/30 active:before:w-full active:before:duration-[1500ms]
En-tete de tri

En-tete de colonne cliquable. La fleche indique le sens actif ; les colonnes inactives montrent un chevron pale.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--tri" aria-sort="none">Client <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m8 9 4-4 4 4M16 15l-4 4-4-4"/></svg></button>
  <button class="btn btn--tri is-actif" aria-sort="ascending">Montant <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M5 12l7-7 7 7"/></svg></button>
  <button class="btn btn--tri" aria-sort="none">Date <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m8 9 4-4 4 4M16 15l-4 4-4-4"/></svg></button>
</div>
.btn--tri {
  height: 32px;
  padding: 0 var(--sp-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--c-text-muted);
  border-radius: var(--r-sm);
}
.btn--tri svg { width: 13px; height: 13px; opacity: .55; }
.btn--tri:hover { background: var(--c-surface-2); color: var(--c-text); }
.btn--tri.is-actif { color: var(--c-primary-ink); }
.btn--tri.is-actif svg { opacity: 1; }
inline-flex h-8 items-center gap-1 px-2 text-xs font-bold uppercase tracking-wide text-slate-500 hover:text-slate-900
Total TTC4 280,00 EUR
Barre d'action mobile

Collee en bas de l'ecran sur mobile, avec le montant a gauche et l'action a droite. Respecte l'encoche via safe-area.

Voir le codeMasquer le code
<div class="btn-mobile">
  <div class="btn-mobile__info"><small>Total TTC</small><strong>4 280,00 EUR</strong></div>
  <button class="btn btn--plein btn--lg">Signer le devis</button>
</div>
.btn-mobile {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  width: 100%;
  max-width: 420px;
  padding: var(--sp-3) var(--sp-4);
  padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
  background: var(--c-surface);
  border-top: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: 0 -4px 16px rgba(16,24,40,.08);
}
.btn-mobile__info { display: flex; flex-direction: column; gap: 2px; }
.btn-mobile__info small  { font-size: var(--fs-xs); color: var(--c-text-muted); }
.btn-mobile__info strong { font-size: var(--fs-lg); color: var(--c-text); white-space: nowrap; }
fixed inset-x-0 bottom-0 flex items-center justify-between gap-4 border-t bg-white p-4 pb-[calc(1rem+env(safe-area-inset-bottom))]

H. Liquid Glass

Le materiau d'Apple (iOS 26, macOS Tahoe) porte en CSS : flou du fond, teinte, liser speculaire, ombre douce. Il n'existe que par-dessus du contenu : sur un aplat uni il disparait.

Liquid Glass regulier

Le materiau par defaut d'Apple : barres d'outils, boutons flottants, superpositions sur photo. S'adapte au fond, clair comme sombre.

Voir le codeMasquer le code
<div class="demo-verre">
  <button class="btn btn--lg-verre">En savoir plus</button>
  <button class="btn btn--lg-verre btn--pilule">Nos realisations</button>
</div>
.btn--lg-verre {
  position: relative;
  isolation: isolate;
  background: rgba(255,255,255,.13);
  border: none;
  border-radius: var(--r-md);
  color: #fff;
  font-weight: var(--fw-semi);
  text-shadow: 0 1px 3px rgba(0,0,0,.35);
  backdrop-filter: blur(14px) saturate(180%) brightness(1.08);
  -webkit-backdrop-filter: blur(14px) saturate(180%) brightness(1.08);
  box-shadow:
    0 2px 8px rgba(0,0,0,.14),
    0 8px 28px rgba(0,0,0,.18);
  transition: background-color var(--t-base), box-shadow var(--t-base), transform var(--t-fast);
}
/* Le liser speculaire : un anneau de 1,5 px en degre, evide au centre par
   un masque. C'est lui qui donne l'impression de tranche de verre. */
.btn--lg-verre::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  padding: 1.5px;
  background: linear-gradient(140deg,
    rgba(255,255,255,.92) 0%,
    rgba(255,255,255,.28) 26%,
    rgba(255,255,255,.05) 48%,
    rgba(255,255,255,.30) 74%,
    rgba(255,255,255,.75) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
.btn--lg-verre:hover  { background: rgba(255,255,255,.22); box-shadow: 0 3px 10px rgba(0,0,0,.16), 0 12px 34px rgba(0,0,0,.22); }
.btn--lg-verre:active { background: rgba(255,255,255,.28); transform: scale(.97); }
relative rounded-xl bg-white/12 backdrop-blur-xl backdrop-saturate-150 shadow-lg + anneau speculaire en ::after
Liquid Glass teinte

L'equivalent de glassProminent : l'action principale d'une superposition. La teinte traverse le verre au lieu de le recouvrir.

Voir le codeMasquer le code
<div class="demo-verre">
  <button class="btn btn--lg-verre btn--lg-teinte">Reserver</button>
  <button class="btn btn--lg-verre btn--lg-teinte btn--lg-teinte-danger btn--pilule">Supprimer</button>
</div>
.btn--lg-teinte {
  background: color-mix(in srgb, var(--c-primary) 52%, transparent);
  backdrop-filter: blur(14px) saturate(200%) brightness(1.05);
  -webkit-backdrop-filter: blur(14px) saturate(200%) brightness(1.05);
  box-shadow:
    0 2px 8px rgba(0,0,0,.16),
    0 10px 30px color-mix(in srgb, var(--c-primary) 32%, transparent);
}
.btn--lg-teinte:hover  { background: color-mix(in srgb, var(--c-primary) 66%, transparent); }
.btn--lg-teinte:active { background: color-mix(in srgb, var(--c-primary) 74%, transparent); transform: scale(.97); }
.btn--lg-teinte-danger        { background: color-mix(in srgb, var(--c-danger) 52%, transparent); box-shadow: 0 2px 8px rgba(0,0,0,.16), 0 10px 30px color-mix(in srgb, var(--c-danger) 32%, transparent); }
.btn--lg-teinte-danger:hover  { background: color-mix(in srgb, var(--c-danger) 66%, transparent); }
bg-blue-500/45 backdrop-blur-xl backdrop-saturate-200
Liquid Glass transparent

L'equivalent de Glass.clear : quand le fond doit rester lisible a travers le bouton. Photos, lecteur video, plein ecran.

Voir le codeMasquer le code
<div class="demo-verre">
  <button class="btn btn--lg-verre btn--lg-clair">Passer</button>
  <button class="btn btn--lg-verre btn--lg-clair btn--icone btn--rond" aria-label="Lecture"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 4.5v15l13-7.5Z"/></svg></button>
</div>
.btn--lg-clair {
  background: rgba(255,255,255,.05);
  backdrop-filter: blur(8px) saturate(150%) brightness(1.12);
  -webkit-backdrop-filter: blur(8px) saturate(150%) brightness(1.12);
  box-shadow: 0 2px 12px rgba(0,0,0,.18);
}
.btn--lg-clair::after { background: linear-gradient(140deg, rgba(255,255,255,.7), rgba(255,255,255,.08) 45%, rgba(255,255,255,.45)); }
.btn--lg-clair:hover  { background: rgba(255,255,255,.14); }
bg-white/5 backdrop-blur-md backdrop-brightness-110
Liquid Glass interactif

L'equivalent du modificateur .interactive() d'Apple : le verre se gonfle au survol, s'enfonce au clic et un eclat le traverse. A garder pour une action isolee.

Voir le codeMasquer le code
<div class="demo-verre">
  <button class="btn btn--lg-verre btn--lg-vivant btn--lg">Commencer</button>
</div>
.btn--lg-vivant { overflow: hidden; transition: transform var(--t-spring), background-color var(--t-base), box-shadow var(--t-base); }
.btn--lg-vivant::before {
  content: "";
  position: absolute;
  inset: -40% -10%;
  z-index: -1;
  background: linear-gradient(100deg, transparent 30%, rgba(255,255,255,.55) 50%, transparent 70%);
  transform: translateX(-120%);
  transition: transform 900ms cubic-bezier(.4,0,.2,1);
  pointer-events: none;
}
.btn--lg-vivant:hover  { transform: scale(1.05); background: rgba(255,255,255,.2); }
.btn--lg-vivant:hover::before { transform: translateX(120%); }
.btn--lg-vivant:active { transform: scale(.95); }
hover:scale-105 active:scale-95 + eclat balaye en ::before
Barre Liquid Glass

L'equivalent de GlassEffectContainer : plusieurs actions dans UNE seule piece de verre. C'est la regle d'Apple, un bouton de verre par-dessus un autre devient illisible.

Voir le codeMasquer le code
<div class="demo-verre">
  <div class="lg-barre">
    <button class="btn btn--icone" aria-label="Precedent"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg></button>
    <button class="btn btn--icone" aria-label="Lecture"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6 4.5v15l13-7.5Z"/></svg></button>
    <button class="btn btn--icone" aria-label="Suivant"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></button>
    <span class="lg-barre__sep"></span>
    <button class="btn btn--icone" aria-label="Partager"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14 21 3"/></svg></button>
  </div>
</div>
.lg-barre {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 6px;
  border-radius: var(--r-full);
  background: rgba(255,255,255,.13);
  backdrop-filter: blur(16px) saturate(180%) brightness(1.08);
  -webkit-backdrop-filter: blur(16px) saturate(180%) brightness(1.08);
  box-shadow: 0 2px 8px rgba(0,0,0,.14), 0 10px 32px rgba(0,0,0,.2);
}
.lg-barre::after {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  border-radius: inherit; padding: 1.5px;
  background: linear-gradient(140deg, rgba(255,255,255,.9), rgba(255,255,255,.2) 40%, rgba(255,255,255,.05) 55%, rgba(255,255,255,.7));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
.lg-barre > .btn {
  width: 38px; height: 38px;
  border-radius: var(--r-full);
  color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,.4);
}
.lg-barre > .btn:hover  { background: rgba(255,255,255,.22); }
.lg-barre > .btn:active { background: rgba(255,255,255,.3); transform: scale(.92); }
.lg-barre__sep { width: 1px; height: 22px; margin: 0 4px; background: rgba(255,255,255,.28); }
inline-flex gap-1 rounded-full bg-white/12 p-1.5 backdrop-blur-xl + anneau speculaire
Liquid Glass avec refraction

La deformation optique du fond par la tranche du verre, le detail qui distingue le vrai materiau d'Apple d'un simple flou. Chromium uniquement : ailleurs le repli @supports rend un H1 classique, sans casse.

Voir le codeMasquer le code
<svg width="0" height="0" style="position:absolute" aria-hidden="true"><filter id="lg-refraction" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB"><feImage href="data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='72'%3E%3Cdefs%3E%3ClinearGradient id='x' x1='0' x2='1'%3E%3Cstop offset='0' stop-color='%23000'/%3E%3Cstop offset='.13' stop-color='%23800000'/%3E%3Cstop offset='.87' stop-color='%23800000'/%3E%3Cstop offset='1' stop-color='%23f00'/%3E%3C/linearGradient%3E%3ClinearGradient id='y' y1='0' y2='1'%3E%3Cstop offset='0' stop-color='%23000'/%3E%3Cstop offset='.2' stop-color='%23008000'/%3E%3Cstop offset='.8' stop-color='%23008000'/%3E%3Cstop offset='1' stop-color='%230f0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='240' height='72' fill='url(%23x)'/%3E%3Crect width='240' height='72' fill='url(%23y)' style='mix-blend-mode:screen'/%3E%3C/svg%3E" result="carte" x="0" y="0" width="100%" height="100%"/><feDisplacementMap in="SourceGraphic" in2="carte" scale="52" xChannelSelector="R" yChannelSelector="G"/></filter></svg>
<div class="demo-verre">
  <button class="btn btn--lg-verre btn--lg-refraction btn--lg">Voir a travers</button>
</div>
@supports (backdrop-filter: url(#lg-refraction)) {
  .btn--lg-refraction {
    /* La carte de deplacement est neutre (128,128) au centre et saturee sur les
       bords : le fond n'est deforme que sur la tranche, comme un verre biseaute. */
    backdrop-filter: blur(2px) saturate(180%) url(#lg-refraction);
  }
}
pas d'equivalent Tailwind : le filtre SVG doit etre declare dans le document

I. Bascules et interrupteurs

Trois objets a ne pas confondre : l'interrupteur applique le reglage immediatement, la bascule est un bouton qui reste enfonce (aria-pressed), la case et le radio attendent la validation du formulaire. Se tromper d'objet est la premiere source de malentendu sur un reglage.

Interrupteur iOS

Le reglage s'applique au relachement du doigt. Poignee ronde, piste qui se colore. Deux tailles, plus l'etat desactive.

Voir le codeMasquer le code
<div class="demo-col">
  <label class="bascule"><input type="checkbox"><span class="bascule__piste"></span> Recevoir les alertes</label>
  <label class="bascule"><input type="checkbox" checked><span class="bascule__piste"></span> Rappel avant intervention</label>
  <label class="bascule"><input type="checkbox" checked disabled><span class="bascule__piste"></span> Impose par l'administrateur</label>
  <label class="bascule bascule--sm"><input type="checkbox" checked><span class="bascule__piste"></span> Version compacte</label>
</div>
.bascule {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-ui);
  font-size: var(--fs-md);
  color: var(--c-text);
  cursor: pointer;
  user-select: none;
}
.bascule input { position: absolute; opacity: 0; width: 0; height: 0; }
.bascule__piste {
  position: relative;
  width: 46px; height: 28px;
  flex: none;
  border-radius: var(--r-full);
  background: var(--c-border-strong);
  transition: background-color var(--t-base);
}
.bascule__piste::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 24px; height: 24px;
  border-radius: var(--r-full);
  background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.28);
  transition: transform var(--t-spring), width var(--t-fast);
}
input:checked + .bascule__piste { background: var(--c-primary); }
input:checked + .bascule__piste::after { transform: translateX(18px); }
/* Etirement de la poignee pendant l'appui : le detail qui rend l'objet tangible */
.bascule:active .bascule__piste::after { width: 29px; }
.bascule input:checked:active + .bascule__piste::after { transform: translateX(13px); }
input:focus-visible + .bascule__piste { outline: 2px solid var(--c-focus); outline-offset: 3px; }
.bascule:has(input:disabled), .bascule-ligne:has(input:disabled) { opacity: .5; cursor: not-allowed; }
.bascule--sm .bascule__piste { width: 36px; height: 22px; }
.bascule--sm .bascule__piste::after { width: 18px; height: 18px; }
.bascule--sm input:checked + .bascule__piste::after { transform: translateX(14px); }
peer sr-only + peer-checked:bg-blue-600 peer-checked:[&+span]:after:translate-x-5
Interrupteur Material 3

Registre Android. Piste plus haute que la poignee au repos, coche a l'etat actif : l'etat reste lisible sans la couleur.

Voir le codeMasquer le code
<div class="demo-col">
  <label class="bascule bascule--m3"><input type="checkbox"><span class="bascule__piste"></span> Synchronisation automatique</label>
  <label class="bascule bascule--m3"><input type="checkbox" checked><span class="bascule__piste"></span> Mode economie de donnees</label>
</div>
.bascule--m3 .bascule__piste {
  width: 52px; height: 32px;
  background: var(--c-surface-3);
  border: 2px solid var(--c-border-strong);
}
.bascule--m3 .bascule__piste::after {
  top: 6px; left: 6px;
  width: 16px; height: 16px;
  background: var(--c-text-muted);
  box-shadow: none;
  transition: transform var(--t-spring), width var(--t-base), height var(--t-base), top var(--t-base), left var(--t-base), background-color var(--t-base);
}
.bascule--m3 input:checked + .bascule__piste { background: var(--c-primary); border-color: var(--c-primary); }
.bascule--m3 input:checked + .bascule__piste::after {
  top: 2px; left: 2px;
  width: 24px; height: 24px;
  background: var(--c-surface);
  transform: translateX(20px);
}
/* La coche, dessinee en CSS : deux traits pivotes dans la poignee */
.bascule--m3 .bascule__piste::before {
  content: "";
  position: absolute;
  top: 11px; left: 9px;
  width: 9px; height: 5px;
  border-left: 2px solid var(--c-primary);
  border-bottom: 2px solid var(--c-primary);
  transform: rotate(-45deg) scale(0);
  transition: transform var(--t-base) 60ms;
  z-index: 1;
}
.bascule--m3 input:checked + .bascule__piste::before { transform: translateX(20px) rotate(-45deg) scale(1); }
Switch de Material 3, colors = SwitchDefaults.colors()
Interrupteur a icones

L'icone dit ce que fait chaque position, sans lire le libelle. Bascule de theme, son coupe, mode nuit.

Voir le codeMasquer le code
<div class="demo-col">
  <label class="bascule bascule--icones" data-off="&#9788;" data-on="&#9789;"><input type="checkbox"><span class="bascule__piste"></span> Theme sombre</label>
  <label class="bascule bascule--icones" data-off="&#10006;" data-on="&#10004;"><input type="checkbox" checked><span class="bascule__piste"></span> Notifications par courriel</label>
</div>
.bascule--icones .bascule__piste { width: 56px; height: 30px; background: var(--c-text-muted); }
.bascule--icones .bascule__piste::after { width: 26px; height: 26px; }
.bascule--icones input:checked + .bascule__piste::after { transform: translateX(26px); }
.bascule--icones { position: relative; }
.bascule--icones::before {
  content: attr(data-off);
  position: absolute;
  left: 34px; top: 50%;
  transform: translateY(-50%);
  font-size: 14px;
  color: #fff;
  z-index: 1;                        /* sinon la piste, enfant, le recouvre */
  pointer-events: none;
  transition: opacity var(--t-fast);
}
.bascule--icones::after {
  content: attr(data-on);
  position: absolute;
  left: 9px; top: 50%;
  transform: translateY(-50%);
  font-size: 14px;
  color: #fff;
  opacity: 0;
  z-index: 1;
  pointer-events: none;
  transition: opacity var(--t-fast);
}
.bascule--icones:has(input:checked)::before { opacity: 0; }
.bascule--icones:has(input:checked)::after  { opacity: 1; }
relative before:absolute before:left-2 after:absolute after:right-2 (icones dans la piste)
Interrupteur avec libelles

Quand la position doit rester comprehensible sans contexte : OUI / NON, EUR / USD, TTC / HT. Les deux libelles sont visibles en permanence.

Voir le codeMasquer le code
<div class="demo-col">
  <label class="bascule bascule--libelles" data-off="NON" data-on="OUI"><input type="checkbox"><span class="bascule__piste"></span> Facture acquittee</label>
  <label class="bascule bascule--libelles" data-off="HT" data-on="TTC"><input type="checkbox" checked><span class="bascule__piste"></span> Affichage des montants</label>
</div>
.bascule--libelles { position: relative; }
.bascule--libelles .bascule__piste { width: 78px; height: 30px; background: var(--c-text-muted); }
.bascule--libelles .bascule__piste::after { width: 26px; height: 26px; }
.bascule--libelles input:checked + .bascule__piste::after { transform: translateX(48px); }
.bascule--libelles::before,
.bascule--libelles::after {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  font-size: 10px;
  font-weight: var(--fw-bold);
  letter-spacing: .05em;
  color: #fff;
  z-index: 1;                        /* voir la note sur l'ordre de peinture */
  pointer-events: none;
  transition: opacity var(--t-fast);
}
/* Eteint : la piste est claire, le libelle doit etre sombre. Allume : l'inverse. */
.bascule--libelles::before { content: attr(data-off); left: 36px; }
.bascule--libelles::after  { content: attr(data-on);  left: 10px; opacity: 0; }
.bascule--libelles:has(input:checked)::before { opacity: 0; }
.bascule--libelles:has(input:checked)::after  { opacity: 1; }
w-20 [&>span]:before:content-[attr(data-off)] [&>span]:after:content-[attr(data-on)]
Interrupteur anguleux

Registre technique ou brutaliste, coherent avec BTN-B3 et BTN-C8. Aucun rayon, poignee carree, transition seche.

Voir le codeMasquer le code
<div class="demo-col">
  <label class="bascule bascule--vif"><input type="checkbox"><span class="bascule__piste"></span> Journalisation detaillee</label>
  <label class="bascule bascule--vif"><input type="checkbox" checked><span class="bascule__piste"></span> Mode maintenance</label>
</div>
.bascule--vif .bascule__piste {
  border-radius: 0;
  background: var(--c-surface-3);
  border: 2px solid var(--c-text);
}
.bascule--vif .bascule__piste::after {
  border-radius: 0;
  background: var(--c-text);
  box-shadow: none;
  top: 2px; left: 2px;
  width: 20px; height: 20px;
  transition: transform 90ms linear;
}
.bascule--vif input:checked + .bascule__piste { background: var(--c-primary); border-color: var(--c-primary); }
.bascule--vif input:checked + .bascule__piste::after { background: #fff; transform: translateX(18px); }
rounded-none [&_span]:rounded-none [&_span]:after:rounded-none
Interrupteur neomorphique

Assorti a BTN-C6. La piste est creusee dans la surface, la poignee en relief. Exige un fond gris moyen.

Voir le codeMasquer le code
<div class="demo-neo">
  <label class="bascule bascule--neo"><input type="checkbox"><span class="bascule__piste"></span> Sourdine</label>
  <label class="bascule bascule--neo"><input type="checkbox" checked><span class="bascule__piste"></span> Vibration</label>
</div>
.bascule--neo { color: #414a58; }
.bascule--neo .bascule__piste {
  background: var(--neo-bg, #e8ebf0);
  box-shadow: inset 3px 3px 6px var(--neo-ombre, #c6cad3), inset -3px -3px 6px var(--neo-lumiere, #fff);
}
.bascule--neo .bascule__piste::after {
  background: var(--neo-bg, #e8ebf0);
  box-shadow: 3px 3px 6px var(--neo-ombre, #c6cad3), -3px -3px 6px var(--neo-lumiere, #fff);
}
.bascule--neo input:checked + .bascule__piste { background: var(--neo-bg, #e8ebf0); }
.bascule--neo input:checked + .bascule__piste::after { background: var(--c-primary); }
shadow-[inset_3px_3px_6px_#c8ccd4,inset_-3px_-3px_6px_#fff]
Interrupteur Liquid Glass

Assorti a la famille H, pour un panneau de reglages pose sur une photo ou une video.

Voir le codeMasquer le code
<div class="demo-verre">
  <label class="bascule bascule--lg"><input type="checkbox"><span class="bascule__piste"></span> Sous-titres</label>
  <label class="bascule bascule--lg"><input type="checkbox" checked><span class="bascule__piste"></span> Lecture automatique</label>
</div>
.bascule--lg { color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,.4); }
.bascule--lg .bascule__piste {
  background: rgba(255,255,255,.16);
  backdrop-filter: blur(12px) saturate(180%);
  -webkit-backdrop-filter: blur(12px) saturate(180%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.45), 0 2px 10px rgba(0,0,0,.2);
}
.bascule--lg .bascule__piste::after { background: rgba(255,255,255,.95); }
.bascule--lg input:checked + .bascule__piste {
  background: color-mix(in srgb, var(--c-primary) 62%, transparent);
}
bg-white/15 backdrop-blur-lg + anneau speculaire
Interrupteur en ligne de reglage

La forme reelle d'un ecran de parametres : libelle, explication, interrupteur a droite. Toute la ligne est cliquable.

Voir le codeMasquer le code
<div class="demo-col demo-col--large">
  <label class="bascule-ligne">
    <span class="bascule-ligne__texte"><strong>Relances automatiques</strong><small>Un courriel part 7 jours apres l'envoi du devis</small></span>
    <input type="checkbox" checked><span class="bascule__piste"></span>
  </label>
  <label class="bascule-ligne">
    <span class="bascule-ligne__texte"><strong>Signature obligatoire</strong><small>Le client doit signer avant de telecharger le PDF</small></span>
    <input type="checkbox"><span class="bascule__piste"></span>
  </label>
</div>
.bascule-ligne {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  width: 100%;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  cursor: pointer;
  transition: background-color var(--t-fast), border-color var(--t-fast);
}
.bascule-ligne:hover { background: var(--c-surface-2); border-color: var(--c-border-strong); }
.bascule-ligne input { position: absolute; opacity: 0; width: 0; height: 0; }
.bascule-ligne__texte { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.bascule-ligne__texte strong { font-size: var(--fs-md); font-weight: var(--fw-semi); color: var(--c-text); }
.bascule-ligne__texte small  { font-size: var(--fs-xs); color: var(--c-text-muted); line-height: 1.45; }
flex w-full items-center justify-between gap-4 rounded-lg border p-4 hover:bg-slate-50
Bascule icone

Un bouton qui reste enfonce : favori, epingle, sourdine. L'icone se remplit a l'etat actif, elle ne change pas seulement de couleur.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--icone btn--secondaire btn--presse" aria-pressed="false" aria-label="Mettre en favori"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1Z"/></svg></button>
  <button class="btn btn--icone btn--secondaire btn--presse" aria-pressed="true" aria-label="Retirer des favoris"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12 2 3.1 6.3 6.9 1-5 4.9 1.2 6.8L12 17.8 5.8 21l1.2-6.8-5-4.9 6.9-1Z"/></svg></button>
  <button class="btn btn--secondaire btn--presse" aria-pressed="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9M13.7 21a2 2 0 0 1-3.4 0"/></svg> Suivi</button>
</div>
.btn--presse[aria-pressed="true"] {
  background: var(--c-warn-soft);
  border-color: var(--c-warn);
  color: var(--c-warn);
}
.btn--presse[aria-pressed="true"] svg { fill: currentColor; }
.btn--presse svg { transition: fill var(--t-fast), transform var(--t-spring); }
.btn--presse[aria-pressed="true"]:active svg { transform: scale(1.25); }
aria-pressed:bg-amber-50 aria-pressed:border-amber-400 aria-pressed:text-amber-600 [&[aria-pressed=true]>svg]:fill-current
Bascule pleine

Bascule d'emphase forte, quand l'etat actif doit se voir de loin : filtre applique, mode selectionne dans une barre d'outils.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--bascule-pleine" aria-pressed="false">Gras</button>
  <button class="btn btn--bascule-pleine" aria-pressed="true">Italique</button>
  <button class="btn btn--bascule-pleine" aria-pressed="true">Souligne</button>
</div>
.btn--bascule-pleine {
  background: var(--c-surface-2);
  color: var(--c-text-soft);
  border-color: transparent;
}
.btn--bascule-pleine:hover { background: var(--c-surface-3); color: var(--c-text); }
.btn--bascule-pleine[aria-pressed="true"] {
  background: var(--c-primary);
  color: var(--c-primary-text);
  box-shadow: var(--sh-1);
}
aria-pressed:bg-blue-600 aria-pressed:text-white
Bascule a deux libelles

Le bouton dit ce qu'il fera au clic, pas l'etat courant : Suivre / Ne plus suivre, S'abonner / Abonne. Le libelle change au survol.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--secondaire btn--deux-libelles" aria-pressed="true" data-actif="Abonne" data-survol="Se desabonner">Abonne</button>
  <button class="btn btn--plein" aria-pressed="false">S'abonner</button>
</div>
.btn--deux-libelles[aria-pressed="true"]::before { content: attr(data-actif); }
.btn--deux-libelles[aria-pressed="true"]:hover {
  background: var(--c-danger-soft);
  border-color: var(--c-danger);
  color: var(--c-danger);
}
.btn--deux-libelles[aria-pressed="true"]:hover::before { content: attr(data-survol); }
.btn--deux-libelles { font-size: 0; }
.btn--deux-libelles::before { font-size: var(--fs-md); }
group aria-pressed:hover:bg-red-50 aria-pressed:hover:border-red-300 aria-pressed:hover:text-red-600
Bascule de theme

Le cas particulier du clair / sombre : l'icone pivote et se change, la piste passe du ciel a la nuit. Un seul bouton, pas un interrupteur.

Voir le codeMasquer le code
<div class="demo-row">
  <button class="btn btn--theme" aria-pressed="false" aria-label="Passer en theme sombre"><span class="btn__theme-astre"></span></button>
  <button class="btn btn--theme" aria-pressed="true" aria-label="Passer en theme clair"><span class="btn__theme-astre"></span></button>
</div>
.btn--theme {
  position: relative;
  width: 58px; height: 30px;
  padding: 0;
  border: none;
  border-radius: var(--r-full);
  background: linear-gradient(180deg, #7dd3fc, #38bdf8);
  transition: background var(--t-slow);
  overflow: hidden;
}
.btn__theme-astre {
  position: absolute;
  top: 3px; left: 3px;
  width: 24px; height: 24px;
  border-radius: var(--r-full);
  background: #fde68a;
  box-shadow: 0 0 12px rgba(253,230,138,.9);
  transition: transform var(--t-spring), background-color var(--t-base), box-shadow var(--t-base);
}
/* Le croissant est obtenu par une ombre portee interne decalee */
.btn__theme-astre::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 8px -3px 0 0 transparent;
  transition: box-shadow var(--t-base);
}
.btn--theme[aria-pressed="true"] { background: linear-gradient(180deg, #1e1b4b, #312e81); }
.btn--theme[aria-pressed="true"] .btn__theme-astre {
  transform: translateX(28px);
  background: #e2e8f0;
  box-shadow: 0 0 10px rgba(226,232,240,.5);
}
.btn--theme[aria-pressed="true"] .btn__theme-astre::after { box-shadow: inset 8px -3px 0 0 #312e81; }
relative h-8 w-14 rounded-full bg-sky-300 aria-pressed:bg-indigo-950 transition-colors
Case a cocher

Choix multiple dans un formulaire, valide par un bouton. Ne jamais utiliser un interrupteur ici : il promet un effet immediat.

Voir le codeMasquer le code
<div class="demo-col">
  <label class="coche"><input type="checkbox"><span class="coche__boite"></span> Chauffage</label>
  <label class="coche"><input type="checkbox" checked><span class="coche__boite"></span> Eau chaude sanitaire</label>
  <label class="coche"><input type="checkbox" checked disabled><span class="coche__boite"></span> Deja installe</label>
</div>
.coche {
  display: inline-flex;
  align-items: flex-start;      /* un libelle long passe a la ligne sans decaler la boite */
  gap: var(--sp-3);
  font-size: var(--fs-md);
  line-height: 1.4;
  color: var(--c-text);
  cursor: pointer;
}
.coche input { position: absolute; opacity: 0; width: 0; height: 0; }
.coche__boite {
  position: relative;
  width: 20px; height: 20px;
  flex: none;
  margin-top: 1px;
  border: 2px solid var(--c-border-strong);
  border-radius: var(--r-xs);
  background: var(--c-surface);
  transition: background-color var(--t-fast), border-color var(--t-fast);
}
.coche__boite::after {
  content: "";
  position: absolute;
  top: 2px; left: 5.5px;
  width: 5px; height: 10px;
  border: solid #fff;
  border-width: 0 2.5px 2.5px 0;
  transform: rotate(45deg) scale(0);
  transition: transform var(--t-spring);
}
.coche input:checked + .coche__boite { background: var(--c-primary); border-color: var(--c-primary); }
.coche input:checked + .coche__boite::after { transform: rotate(45deg) scale(1); }
.coche input:indeterminate + .coche__boite { background: var(--c-primary); border-color: var(--c-primary); }
.coche input:indeterminate + .coche__boite::after {
  top: 8px; left: 3px; width: 10px; height: 0;
  border-width: 0 0 2.5px 0; transform: none;
}
.coche input:focus-visible + .coche__boite { outline: 2px solid var(--c-focus); outline-offset: 2px; }
.coche:has(input:disabled) { opacity: .5; cursor: not-allowed; }
peer sr-only + peer-checked:bg-blue-600 peer-checked:border-blue-600
Carte a cocher

Choix multiple ou l'option merite une explication : options d'un devis, modules d'un abonnement. Toute la carte est cliquable.

Voir le codeMasquer le code
<div class="demo-col demo-col--large">
  <label class="carte-choix">
    <input type="checkbox" checked><span class="coche__boite"></span>
    <span class="carte-choix__texte"><strong>Pose comprise</strong><small>Intervention par notre equipe, sous 3 semaines</small></span>
    <span class="carte-choix__prix">+ 1 200 EUR</span>
  </label>
  <label class="carte-choix">
    <input type="checkbox"><span class="coche__boite"></span>
    <span class="carte-choix__texte"><strong>Extension de garantie</strong><small>5 ans pieces et main d'oeuvre</small></span>
    <span class="carte-choix__prix">+ 340 EUR</span>
  </label>
</div>
.carte-choix {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-4);
  border: 1.5px solid var(--c-border);
  border-radius: var(--r-lg);
  background: var(--c-surface);
  cursor: pointer;
  transition: border-color var(--t-fast), background-color var(--t-fast), box-shadow var(--t-fast);
}
.carte-choix input { position: absolute; opacity: 0; width: 0; height: 0; }
.carte-choix:hover { border-color: var(--c-border-strong); }
.carte-choix:has(input:checked) {
  border-color: var(--c-primary);
  background: color-mix(in srgb, var(--c-primary-soft) 55%, var(--c-surface));
  box-shadow: 0 0 0 1px var(--c-primary);
}
.carte-choix__texte { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.carte-choix__texte strong { font-size: var(--fs-md); font-weight: var(--fw-semi); color: var(--c-text); }
.carte-choix__texte small  { font-size: var(--fs-xs); color: var(--c-text-muted); line-height: 1.45; }
.carte-choix__prix { font-size: var(--fs-md); font-weight: var(--fw-bold); color: var(--c-text); white-space: nowrap; }
flex cursor-pointer items-center gap-3 rounded-xl border p-4 has-[:checked]:border-blue-600 has-[:checked]:bg-blue-50/60
Radio en cartes

Choix exclusif entre des options qui demandent une description : formule, mode de paiement, creneau. Remplace avantageusement une liste deroulante.

Voir le codeMasquer le code
<div class="demo-col demo-col--large">
  <label class="carte-choix carte-choix--radio">
    <input type="radio" name="formule" checked><span class="radio__cercle"></span>
    <span class="carte-choix__texte"><strong>Paiement comptant</strong><small>Remise de 3 % appliquee au total</small></span>
  </label>
  <label class="carte-choix carte-choix--radio">
    <input type="radio" name="formule"><span class="radio__cercle"></span>
    <span class="carte-choix__texte"><strong>En 3 fois sans frais</strong><small>Trois prelevements de 1 426,67 EUR</small></span>
  </label>
</div>
.radio__cercle {
  position: relative;
  width: 20px; height: 20px;
  flex: none;
  border: 2px solid var(--c-border-strong);
  border-radius: var(--r-full);
  background: var(--c-surface);
  transition: border-color var(--t-fast);
}
.radio__cercle::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  width: 10px; height: 10px;
  border-radius: var(--r-full);
  background: var(--c-primary);
  transform: translate(-50%, -50%) scale(0);
  transition: transform var(--t-spring);
}
.carte-choix input:checked ~ .radio__cercle,
.carte-choix input:checked + .radio__cercle { border-color: var(--c-primary); }
.carte-choix input:checked + .radio__cercle::after { transform: translate(-50%, -50%) scale(1); }
has-[:checked]:border-blue-600 + cercle en ::after
Choix en tuiles

Choix exclusif court et visuel : nombre de pieces, puissance, couleur. Se range en grille et tient sur mobile.

Voir le codeMasquer le code
<div class="demo-row">
  <label class="tuile-choix"><input type="radio" name="pieces" checked><span>1</span></label>
  <label class="tuile-choix"><input type="radio" name="pieces"><span>2</span></label>
  <label class="tuile-choix"><input type="radio" name="pieces"><span>3</span></label>
  <label class="tuile-choix tuile-choix--large"><input type="radio" name="pieces"><span>4 et plus</span></label>
</div>
.tuile-choix { display: inline-flex; cursor: pointer; }
.tuile-choix input { position: absolute; opacity: 0; width: 0; height: 0; }
.tuile-choix > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 48px;
  height: 44px;
  padding: 0 var(--sp-3);
  border: 1.5px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-surface);
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  color: var(--c-text-soft);
  transition: all var(--t-fast);
}
.tuile-choix:hover > span { border-color: var(--c-border-strong); color: var(--c-text); }
.tuile-choix input:checked + span {
  border-color: var(--c-primary);
  background: var(--c-primary);
  color: var(--c-primary-text);
}
.tuile-choix input:focus-visible + span { outline: 2px solid var(--c-focus); outline-offset: 2px; }
.tuile-choix--large > span { min-width: 90px; }
has-[:checked]:border-blue-600 has-[:checked]:bg-blue-600 has-[:checked]:text-white