FormulairesGratuit

Floating Label

Le libellé posé dans le champ remonte sur son bord haut dès qu'on clique ou qu'une valeur est présente, même remplie par l'autocomplétion. Pur CSS, aucun script.

CSSFocusTransform

Mis à jour le

Survolez ou cliquez la scène pour interagir

Cet effet est gratuit — la catégorie Formulaires en contient 20 au total, dont 2 gratuits. Effect.Labs regroupe 840 effets vanilla. Découvrir la catégorie →

Exemples d'usage

Panier✓—Livraison—Paiement

Adresse de livraison

① Étape « Livraison » d'un tunnel de commande — boutique Malvoisy

QuandLe client touche « Utiliser mon adresse enregistrée » : rue, code postal et ville se remplissent d'un coup et les trois libellés montent ensemble, sans qu'aucun champ ait reçu le focus.
PourquoiC'est le cas où un libellé posé dans le champ pose problème d'habitude : une valeur arrive sans clic, et le texte resterait par-dessus. Ici :placeholder-shown suffit à le faire remonter, exactement comme avec l'autoremplissage du navigateur.
RéglagesUne racine <form> qui sert de carte (fond rgb(26, 26, 36) conservé, donc encoche juste sans toucher à --flt-surface), --flt-width: 100%, grille de deux colonnes. Le bouton de la scène écrit les trois valeurs par script.
TirvanneSemaine 46 · Équipe designCliquez un créneau libre
Lun 13Mar 14Mer 15Jeu 16Ven 17 9 hPoint du matinAtelier clientPoint du matin 10 hAtelier client 11 hRevue du portfolioPoint Léa 12 h

Nouveau rendez-vous

② Bulle « Nouveau rendez-vous » d'un agenda d'équipe — Tirvanne

QuandUn clic sur un créneau libre ouvre une petite bulle à côté de la case ; le champ « Titre du rendez-vous » reçoit le focus, son libellé remonte et la saisie peut commencer sans geste de plus.
PourquoiDans une bulle de 250 px, il n'y a pas la place d'un libellé au-dessus du champ. Le texte qui monte dans la bordure garde la consigne lisible pendant la frappe sans agrandir la bulle.
RéglagesLa bulle est elle-même la racine (width: 250px, fond par défaut), --flt-width: 100%. Le focus est donné par focus({ preventScroll: true }) au clic sur le créneau ; « Créer » ajoute le rendez-vous dans la grille.
VENDRAQUEEnvoyerPoints relaisPro

Où en est votre colis ?

Le numéro figure dans l'e-mail d'expédition ou sur le reçu.

③ Champ « Numéro de suivi » du bandeau d'accueil d'un transporteur — Vendraque

QuandPremière chose que voit le visiteur : un grand titre et un champ qui occupe toute la largeur du bandeau, suivi du bouton Suivre. L'état du colis s'affiche sous le champ après l'envoi.
PourquoiUn seul champ, très large, pour une seule action : le libellé dans le cadre suffit, il n'ajoute pas de ligne au-dessus et reste visible quand on revient corriger un chiffre.
Réglages--flt-width: 100% dans une rangée flex avec le bouton ; racine en <form> avec un rayon de 14 px ajouté par la scène. Le champ est en autocomplete="off" et spellcheck="false", adapté à un numéro.

Comment ça marche

Tout repose sur un <label class="flt-field"> qui enveloppe le champ .flt-input puis le texte .flt-label. Cet emballage relie le libellé au champ sans attribut id : on peut poser autant de copies que l'on veut dans une page, chacune garde son propre libellé, et un clic sur le texte place le curseur dans le bon champ.

Au repos, .flt-label est centré verticalement à l'intérieur du cadre (top: 50% puis translateY(-50%)). Deux sélecteurs le font monter : .flt-input:focus + .flt-label et .flt-input:not(:placeholder-shown) + .flt-label. Le second est la clé : le champ porte un placeholder=" " (une espace), invisible, et :placeholder-shown cesse d'être vrai dès qu'une valeur existe, qu'elle soit tapée, collée, posée par un script ou par l'autoremplissage du navigateur.

Une fois en haut, le libellé passe à 0.75rem, prend la couleur d'accent (#818cf8 par défaut pour le texte) et reçoit un fond égal à --flt-surface (par défaut #1a1a24, la couleur du cadre de démo) : ce fond masque le trait de bordure derrière le texte, d'où l'effet d'encoche. Le cadre passe lui aussi en indigo et, uniquement pendant le focus, un anneau de 3 px rgba(99, 102, 241, .25) distingue le champ actif d'un champ simplement rempli. Les deux déplacements durent 0,3 s.

La racine .fx-0281-root n'est qu'un cadre de présentation : fond sombre, 250 px de haut au minimum, champ centré. Dans votre formulaire, vous pouvez la retirer et garder seulement le <label class="flt-field">.

Accessibilité

  • Nom du champ : le texte de .flt-label est le vrai libellé du champ (association implicite par le <label> englobant). Les lecteurs d'écran annoncent « Votre email, zone de saisie » même quand le texte est remonté.
  • Formulaire réel : le champ fourni est type="email" avec name="email" et autocomplete="email". Dans un <form>, il est envoyé, validé par le navigateur (format d'adresse) et proposé à l'autoremplissage ; sur mobile, le clavier e-mail s'ouvre.
  • Focus visible : le cadre indigo seul ne suffirait pas à repérer le champ actif quand plusieurs champs sont remplis ; l'anneau de 3 px n'apparaît qu'au focus.
  • Mouvement réduit : avec prefers-reduced-motion: reduce, les transitions sont supprimées. Le libellé se retrouve directement en haut, rien n'est caché.
  • Contraste : libellé au repos #909098 sur le fond du champ (blanc à 5 % posé sur #1a1a24) : 4,7 à 4,8:1 ; libellé remonté #818cf8 sur #1a1a24 : 5,8:1. Mesuré dans Chromium, Firefox et WebKit, au repos, au focus et rempli. Si vous posez votre propre --flt-accent, gardez au moins 4,5:1 sur --flt-surface : la variable colore aussi le texte remonté.

Compatibilité navigateur

Ne demande que :placeholder-shown (Chrome 47, Firefox 51, Safari 9), les variables CSS (Chrome 49, Firefox 31, Safari 9.1) et des transitions. Vérifié dans Chromium, Firefox et WebKit (Playwright) : deux copies indépendantes dans un même formulaire, clic, Tab, toucher, valeur posée par script, envoi des données, largeur 390 px, mouvement réduit.

Chrome 49+✓ Complet (mesuré)
Firefox 51+✓ Complet (mesuré)
Safari 9.1+✓ Complet (moteur WebKit mesuré)
Edge 79+✓ Complet (moteur Chromium)
Mobile iOS 9.3+✓ Complet
Android Chrome 49+✓ Complet (moteur Chromium)

Un navigateur qui ignore :placeholder-shown laisse le libellé à l'intérieur dès que le champ perd le focus, posé sur la valeur tapée : réservez ce cas aux navigateurs de plus de huit ans. Sans variables CSS, l'encoche perd sa couleur de fond.

Le code

Copiez les deux blocs (HTML et CSS) dans votre page. Aucun JavaScript, aucune dépendance.

HTML
<div class="fx-0281-root">
  <label class="flt-field">
    <input class="flt-input" type="email" name="email" autocomplete="email" placeholder=" ">
    <span class="flt-label">Votre email</span>
  </label>
</div>
CSS
.fx-0281-root,
.fx-0281-root *,
.fx-0281-root *::before,
.fx-0281-root *::after {
  box-sizing: border-box
}

.fx-0281-root {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 250px;
  padding: 24px 16px;
  position: relative;
  background: rgb(26, 26, 36);
  color: rgb(255, 255, 255);
  font-family: sans-serif;
}

.flt-field {
  position: relative;
  display: block;
  width: var(--flt-width, 280px);
  max-width: 100%;
}

.flt-input {
  width: 100%;
  padding: 16px 12px 8px;
  font-size: 1rem;
  font-family: inherit;
  background: rgba(255, 255, 255, 0.05);
  border: 2px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  color: white;
  outline: none;
  transition: all 0.3s ease;
}

.flt-label {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--flt-muted, #909098);
  pointer-events: none;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  background: transparent;
  padding: 0 4px;
}

.flt-input:focus,
.flt-input:not(:placeholder-shown) {
  border-color: var(--flt-accent, #6366f1);
  background: rgba(99, 102, 241, 0.05);
}

.flt-input:focus {
  box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);
}

.flt-input:focus + .flt-label,
.flt-input:not(:placeholder-shown) + .flt-label {
  top: 0;
  font-size: 0.75rem;
  color: var(--flt-accent, #818cf8);
  background: var(--flt-surface, #1a1a24);
}

@media (prefers-reduced-motion: reduce) {
  .flt-input,
  .flt-label {
    transition: none;
  }
}
JavaScript

CSS pur — aucun JavaScript requis.

Personnaliser

Options passées à l'API ou attributs data-* :

Option / propriétéDéfautEffet
Largeur (CSS — --flt-width) 280 px Posez --flt-width: 100% sur le champ ou un parent pour qu'il prenne la largeur de sa colonne. max-width: 100% l'empêche de déborder sur un petit écran.
Fond de l'encoche (CSS — --flt-surface) #1a1a24 Doit être la couleur exacte de ce qui se trouve derrière le champ : sur une carte blanche, mettez #fff, sinon une pastille sombre apparaît sous le libellé remonté.
Couleur d'accent (CSS — --flt-accent) #6366f1 (cadre) · #818cf8 (libellé remonté) Sans réglage, le cadre est en #6366f1 et le libellé remonté dans l'indigo plus clair #818cf8, lisible sur fond sombre. Posée, la variable colore les deux. Le fond indigo léger du champ et l'anneau de focus restent en rgba(99, 102, 241, …) : changez-les aussi pour une autre teinte.
Couleur du libellé au repos (CSS — --flt-muted) #909098 Gris du texte quand il est dans le champ. Gardez un contraste d'au moins 4,5:1 avec le fond du champ.
Type et nom du champ (HTML) type="email" name="email" autocomplete="email" Pour un nom, un téléphone ou une adresse, changez les trois attributs ensemble (tel / tel, text / street-address…). Gardez placeholder=" " : sans lui, le libellé ne saurait plus si le champ est vide.
Durée (CSS — transition) 0,3 s Réglée deux fois, sur .flt-input et .flt-label. Entre 0,15 et 0,4 s le geste reste lisible ; au-delà, le libellé traîne derrière la frappe.

FAQ

Le libellé reste-t-il en haut quand le navigateur remplit le champ tout seul ?

Oui. L'autoremplissage pose une valeur, :placeholder-shown devient faux et le libellé monte, sans script. Même chose pour une valeur écrite par votre JavaScript (input.value = …) ou restaurée par le bouton Précédent.

Pourquoi le libellé n'utilise-t-il pas d'attribut for/id ?

Un id doit être unique dans la page : avec for="email", une deuxième copie du champ pointerait sur la première et resterait sans nom. Le <label> qui enveloppe le champ fait la même liaison sans identifiant.

Puis-je afficher un exemple de saisie en plus du libellé ?

Pas dans le placeholder : il doit rester une simple espace pour que le sélecteur fonctionne. Mettez l'exemple dans un petit texte sous le champ relié par aria-describedby, il restera lisible pendant la frappe.