Je vous enjoins à lire la « Signalétique des hyperliens » sur le train de 13 h 37, par Romy Duhem-Verdière. On y apprend notamment que — typographiquement parlant — le souligné nʼest dʼaucune utilité, sauf éventuellement à palier lʼabsence dʼitalique (ce qui est somme toute assez rare). En revanche sur le web, cʼest le meilleur moyen de signaler un lien.
Graphiquement grossier
Le souligné ordinaire est effectivement grossier : cʼest un reliquat des toutes premières typographies affichées sur un écran — que je nʼai pas eu la chance de croiser. Vous pouvez le constater avec cet exemple de souligné ordinaire.[1]
Sur le plan visuel, le souligné croise les jambages inférieures, ce qui crée un attroupement inopportun de pixels quʼon pourrait qualifier de « petits pâtés ».[2]
Quelques tentatives existaient déjà il y a fort longtemps, comme en témoigne lʼarticle de Susan Robertson sur A List Apart qui utilisait background-image
pour mettre un une image répétée en guise de soulignement. Ça peut sembler étrange — notamment quand vous aurez vu les exemples animés — mais en 2004, cʼétait parfaitement fabuleux.
Le truchement CSS
On ne peut malheureusement pas encore intervenir sur la propriété text-decoration: underline;
de façon satisfaisante. Ce sera en revanche le cas un jour avec text-decoration-skip: ink;
qui est à lʼétat de brouillon au W3C (en anglais). Il nʼexiste actuellement aucun support de cette propriété, cependant celle-ci est émulée sur les engins Apple omme le mentionne Chris Coyier sur CSS-Tricks (en anglais). En attendant, vous allez voir que Susan Robertson était déjà sur la bonne piste il y a 10 ans.
Lʼastuce — a priori trouvée par Marcin Wichary pour Medium et expliquée dans « Crafting link underlines on Medium » — consiste à supprimer le text-decoration
et le trucher à lʼaide dʼun dégradé pour la ligne, et dʼune ombre portée sur le texte pour « nettoyer les jambages ». Comme Susan Robertson, nous allons nous servir de la propriété background-image
, mais au lieu de charger un gif nous utiliserons la fonction linear-gradient();
. Si vous nʼêtes pas encore familier avec les dégradés, prenez le temps de lire la page dédiée aux dégradés en CSS sur MDN.
Du code, du code !
Jʼai croisé plusieurs implémentations de cette solution, toutes très intéressantes mais aucune ne fonctionnant « telle quelle ». En voici deux notables :
- la méthode utilisée par tufte-CSS (sur Github) ;
- la réflexion menée par Adam Schwartz pour Eager (en anglais) avec une illustration interactive particulièrement réussie.
Ça donne suffisamment de matière pour avancer, donc jʼai trituré tout ça jusquʼà obtenir un résultat satisfaisant mes besoins. Vous en trouverez un aperçu rigolo sur CodePen. Pour les feignasses, voici le CSS incriminé :
.underline {
text-decoration: underline;
}
.no-highcontrast.cssgradients .underline {
background: white 0 88% / 100% 0.1rem no-repeat content-box content-box;
background-image:
linear-gradient(
rgba(255, 255, 255, .67),
rgba(255, 255, 255, .67)
),
linear-gradient(
currentColor,
currentColor
);
display: inline;
text-decoration: none;
text-shadow:
.05rem 0 0 white,
-.05rem 0 0 white,
.1rem 0 0 white,
-.1rem 0 0 white,
.2rem 0 0 white,
-.2rem 0 0 white;
width: auto;
}
/* Pour la sélection */
/* https://developer.mozilla.org/en-US/docs/Web/CSS/::selection */
.no-highcontrast.cssgradients .underline::-moz-selection {
background-color: black;
color: white;
text-shadow: none;
}
.no-highcontrast.cssgradients .underline::selection {
background-color: black;
color: white;
text-shadow: none;
}
@media screen and (-ms-high-contrast: active) {
.underline {
background: none !important;
text-decoration: underline !important;
}
}
Vous noterez les occurrences multiples de la couleur blanche — tant sous la forme du mot-clé white
que de sa notation rgba()
. Simplement parce que mon exemple à un arrière-plan blanc. Évidemment si votre texte repose sur un fond coloré, ces valeurs doivent respecter la couleur dʼarrière-plan.[3]
Petite astuce à prendre en considération, le second paramètre du mixin concerne la position du soulignement par rapport à la hauteur de lʼélément. Je ne suis pas parvenu à rendre ça dispensable, le changement de corps et de caractères entraînant une trop grande variation sur la hauteur de ligne. Dans mes différents cas, la position varie entre 88% et 96%.
Cʼest pas un peu verbeux ?
Si. Pour plusieurs raisons que je tente de vous expliquer :
- les propriétés dʼarrière-plan sont très riches, avec dans la notation raccourcie :
background-color
background-position
background-size
background-repeat
background-origin
background-clip
.
- deux dégradés, dont le premier (couleur dʼarrière-plan en semi-transparent) sert à alléger visuellement le second — qui utilise
currentColor
pour conserver la couleur du texte sur lequel il est appliqué ; - lʼombre sur le texte, dont le seul et unique objectif est dʼéviter la collision entre les jambages inférieurs et le soulignement ;
- et deux propriétés auxquelles vous ne vous attendiez peut-être pas :
display: inline;
etwidth: auto;
dont lʼintérêt est de sʼassurer que notre charmant souligné suive bien le texte et uniquement le texte.[4]
- les classes de tests du support des dégradés CSS et de lʼactivation du mode de contrastes élevés.
Ça fait beaucoup de code, mais tout est indispensable.
Et lʼaccessibilité ?
Pour commencer, il est nécessaire de mettre en place deux scripts fonctionnant sur le même principe :
- un test de fonctionnalité tel que Modernizr — dont vous pouvez générer une version uniquement pour les dégradés CSS — afin dʼappliquer ce CSS de soulignement uniquement si le navigateur supporte les dégradés ;
- un second test pour détecter lʼactivation du mode de contrastes élevés — qui supprime les arrières-plans et laisserait donc nos éléments soulignés complètement nus et sans soulignement — mais qui détecte également si lʼutilisateur a personnalisé la couleur des liens : vous trouverez la source sur un JSFiddle de Karl Groves du Paciello Group (quʼil a lui-même récupéré de Hans Hillen) que jʼai modifié afin de tester un lien plutôt quʼune division.
Les navigateurs ne supportant pas les dégradés ou étant utilisé conjointement au mode de contrastes élevés resteront avec leur bon vieux text-decoration: underline;
.
Un autre cas pointu est à signaler : la sélection du texte en question. Il est impossible à ma connaissance de simplement supprimer les styles ajoutés (unset
sert a priori à ça, mais son fonctionnement et son support ne sont pas convaincants). Il faut donc personnaliser la sélection, ce que je fais en inversant couleur dʼarrière-plan et couleur de texte et en supprimant lʼombre sous le texte à lʼaide du pseudo-élément dédié (en anglais).
Merci à Johan Ramon pour mʼavoir rappelé à lʼordre avec ces détails croustillants !
Modifications
27 octobre 2016
Une petite amélioration est apportée, de la manière suivante :
- si le navigateur gère les requêtes de fonctionnalités via
@supports
et qu’il supportetext-decoration-skip: ink;
, on applique cette propriété ; - si le navigateur gère les requêtes de fonctionnalité mais pas la propriété, on applique le
background-image
lorsque le mode de contrastes élevés n’est pas activé.
Si ces ajouts vous intéressent, voici un peu de lecture supplémentaire :
Tout peut se résumer comme suit :
@supports (text-decoration-skip: ink) {
text-decoration-skip: ink;
}
Le CodePen est à jour pour que vous jetiez un œil, et je l’ai exporté dans un Gist également.
Cet ajout a quelques effets notables :
- certains navigateurs sont désormais exclus par le manque de support de
@supports
, tels que le Stock browser Adnroid, Blackberry Browser ou IE Mobile. Ça me semble une bonne chose puisque ces styles sont relativement coûteux, ces vieux navigateurs mobiles seront épargnés ; - seul Safari 9 supporte
text-decoration-skip: ink;
, et je ne suis pas certain que le rendu soit le même. Ce serait à tester et ajuster au cas par cas.
Mais nous voilà avec un pied dans le futur !
20 janvier 2017
Xavier Zalawa rencontre un bug de Blink gênant impliquant currentColor
dans un linear-gradient
en cas de changement d’état. C’est le cas dans ce soulignement factice : currentColor
utilisé dans le background n’est pas mis à jour lors du survol ou de la prise de focus, sans être répété— ce qui ruine littéralement l’intérêt de currentColor…
Le bug est ouvert chez Chromium.
Vincent De Oliveira a cependant trouvé une astuce pour le faire fonctionner sans devoir répéter la propriété et ça, c’est cool.
En repassant donc sur le CodePen pour le mettre à jour, je me suis également rendu compte que la media query -ms-high-contrast: active
était inutile ; je l’ai donc supprimée. Cette requête n’est comprise que par IE 11 et inférieur — cependant comme elle était imbriquée dans une règle @supports ces derniers ne pouvaient pas la lire. Hop, un peu de code en moins !
Percevez-vous comme ce soulignement est affordant ? On a envie de cliquer dessus, pas vrai ? ↩︎
Terme technique issu dʼune longue histoire de la calligraphie à la plume (ou de lʼapprentissage de lʼécriture avec un stylo plume pour les plus jeunes dʼentre vous). ↩︎
Vous avez probablement déjà compris que si votre texte repose sur une image ou un dégradé, il nʼy aura probablement pas de salut pour votre soulignement. ↩︎
Sans cette astuce, certains éléments auraient un soulignement sur toute la largeur, même si le texte ne la remplit pas — et dʼautres éléments auraient un soulignement uniquement sur la dernière ligne, si dʼaventures ils fussent sur plusieurs lignes. ↩︎
Très sympa ! J’aurais cependant légèrement modifié la méthode HCTest pour qu’elle renvoie une fonction renvoyant toujours true ou false. Ainsi, si je l’appelle une seconde fois ailleurs pour un autre besoin, pas besoin d’exécuter à nouveau l’ensemble du test 😉
Héhé c’est en cours, nous créons un test Modernizr à plugger tout bêtement 😎
Ça devrait arriver sur GitHub dans pas longtemps ! J’avoue avoir un peu bricolé pour l’exemple 😇
Hello ! J’utilise ta méthode depuis quelques semaines sur un projet perso (qui n’est pas encore en ligne). Cela fonctionne globalement bien, mais je note quelques trucs :
Mais bon, l’effet permis par le text-shadow sur les descendantes est vraiment appréciable. Je pense qu’on se rapproche de la vérité 🙂 Encore merci pour ce partage.
Coucou Marie, merci pour ce commentaire 😄
Effectivement rien nʼest parfait, sinon cet effet serait ultra-répandu ! Jʼessaie de répondre point par point :
<head>
— serait de renverser lʼeffet : lʼappliquer par défaut et ne remettretext-decoration
que si le test est positif. Le seul effet de bord que je devine serait que les soulignements pourraient apparaître avec un léger décalage en mode de contrastes élevés. Pourquoi pas ? Mon premier contre-argument est que ça me semble aller à lʼencontre de lʼamélioration progressive, mais je suppose que ça se discute.repeating-linear-gradient
mais cʼest beaucoup plus fin à gérer.em
, et le passage enrem
a grandement amélioré les choses. Et je tʼavoue ne pas y avoir accordé plus dʼimportance que ça, chercher à obtenir la même épaisseur sur tous les navigateurs me semble une perte de temps. Du moment que le rendu soit agréable à lʼœil chez tout le monde, ça me va 😇Voilà, assez peu de solutions concrètes jʼen ai peur mais il sʼagit bien dʼun artifice cosmétique. Je mʼen sers tel quel car je ne me heurte pas aux problèmes que tu évoques, mais je pense en effet que ces derniers peuvent être rédhibitoires.
Me demande en sus si je ne vais pas y ajouter (en prévision) le support de
text-decoration-skip
.À noter, Vincent Valentin a proposé une variante bien plus intuitive à manipuler, en faisant concorder la hauteur de ligne avec la taille de l’arrière-plan. Vous trouverez une démonstration adaptée sur ce codePen.