Lorsque vous créez des composants d'interface utilisateur réutilisables (comme des badges ou des boutons) ou que vous prenez en charge des thèmes dynamiques, il peut être difficile de s'assurer que le texte est lisible sur une couleur d'arrière-plan imprévisible. La fonction CSS contrast-color() renvoie black ou white, ce qui maximise le rapport de contraste par rapport à la couleur d'arrière-plan spécifiée.
Déterminer la couleur de texte optimale
Lorsqu'un élément a une couleur d'arrière-plan variable ou injectée de manière dynamique, vous pouvez compter sur le navigateur pour calculer la couleur de texte la plus sûre.
OBLIGATOIRE : Définissez la propriété color à l'aide de la fonction contrast-color(), en transmettant la couleur d'arrière-plan (ou sa propriété personnalisée) comme argument.
.badge {
/* Define the background color as a custom property */
--badge-bg: #e02424;
background-color: var(--badge-bg);
/* MANDATORY: Dynamic contrasting color */
color: contrast-color(var(--badge-bg));
}
À NE PAS FAIRE : Utiliser contrast-color() pour background-color. Il est spécialement conçu pour que les couleurs de premier plan (comme le texte ou les bordures) contrastent avec une couleur d'arrière-plan donnée.
Intégrer à la thématisation
Comme contrast-color() accepte n'importe quelle valeur <color> valide, y compris les propriétés CSS personnalisées, il est particulièrement efficace dans les systèmes de conception. En centralisant vos thèmes d'arrière-plan sous forme de variables, vous vous assurez que le contraste du contenu de premier plan s'ajuste automatiquement chaque fois que le thème change, même pour les couleurs spécifiées par l'utilisateur.
À FAIRE : Référencez les variables d'arrière-plan partagées dans contrast-color() pour garantir la synchronisation dans votre application.
.theme-card {
background-color: var(--theme-surface-color);
color: contrast-color(var(--theme-surface-color));
}
.theme-container[data-theme="light"] {
--theme-surface-color: #f4f4f4;
}
.theme-container[data-theme="dark"] {
--theme-surface-color: #1a1a1a;
}
.theme-container[data-theme="custom"] {
--theme-surface-color: var(--user-custom-theme);
}
À NE PAS FAIRE : coder en dur une couleur de texte distincte pour chaque état d'interaction lorsque l'arrière-plan est dynamique. Cela réintroduirait l'incohérence que contrast-color() est censé éviter.
Stratégies de remplacement
Pour prendre en charge les navigateurs sans contrast-color(), vous devez fournir une solution de remplacement. Si la couleur d'arrière-plan est connue et fixe, utilisez une couleur contrastante codée en dur. Pour les arrière-plans dynamiques dont la couleur est inconnue, choisissez une stratégie dans le tableau ci-dessous en fonction de vos exigences d'UI et de vos cibles de compatibilité avec les navigateurs.
| Stratégie | Recommandée pour... | Remarques |
|---|---|---|
| Syntaxe de couleur relative | Calcul automatique et de haute qualité du contraste en CSS uniquement. | Création de remplacement CSS uniquement de la plus haute qualité. |
| Ombre du texte | Amélioration rapide de la lisibilité sur n'importe quel arrière-plan. | Peut sembler "sale" ou "lumineux" ; peut ne pas convenir à tous les designs. |
| Trait du texte | Préserver la graisse de la police tout en garantissant le contraste des contours. | Utilisez paint-order pour éviter d'affiner les formes des lettres, si disponible. |
| Superposition translucide | Assurer une zone de contraste minimale derrière le texte. | Modifie l'apparence de la couleur d'arrière-plan sous le texte. Nécessite un élément de texte distinct. |
| Filtres SVG | Contraste réactif qui s'actualise lorsque l'arrière-plan change. | Nécessite un élément de texte distinct ; implémentation complexe. |
Recommandé : syntaxe de couleur relative (RCS)
Pour les navigateurs compatibles, le RCS fournit la solution de remplacement automatisée la plus robuste pour les couleurs dynamiques.
@supports (color: oklch(from red l c h)) {
.badge {
/* Highest threshold that passes WCAG. Higher values may be more legible. */
--threshold: 0.623;
--l: max(0, sign(var(--threshold) - l));
color: oklch(from var(--badge-bg) var(--l) 0 h);
}
}
@supports (color: contrast-color(red)) {
.badge {
color: contrast-color(var(--badge-bg));
}
}
Solutions de remplacement CSS alternatives
À FAIRE : Sélectionnez l'option de remplacement qui correspond le mieux à vos contraintes de conception si le RCS n'est pas suffisant ou n'est pas pris en charge.
Option 1 : Contour du texte
.badge--stroke {
-webkit-text-stroke: 4px black;
}
Option 2 : Contour du texte avec paint-order pour préserver la forme des lettres
.badge--stroke {
-webkit-text-stroke: 4px black;
paint-order: stroke fill;
}
Option 3 : Superposition d'un arrière-plan translucide
.badge--overlay {
position: relative;
color: #fff;
overflow: hidden;
}
.badge--overlay::before {
content: "";
position: absolute;
inset: 0;
background-color: rgb(0 0 0 / 0.4);
z-index: 0;
}
.badge--overlay span {
position: relative;
z-index: 1;
}
Option 4 : Ombre du texte
.badge--shadow {
text-shadow: 0 1px 3px rgb(0 0 0 / 0.8);
}
Option 5 : Filtres SVG
Chargez cette définition de filtre SVG. Il doit être ajouté directement au document HTML si vous devez prendre en charge les versions de Safari concernées par ce bug WebKit, qui empêche les références d'URL externes et de données pour les filtres. Sinon, il doit être chargé à partir d'un fichier externe, soit un fichier SVG, soit un fichier intégré au CSS.
<!-- Technique from https://miunau.com/posts/dynamic-text-contrast-in-css/ --> <svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="0" style="display: none;"> <defs> <filter id="contrast-filter" color-interpolation-filters="sRGB"> <feColorMatrix type="matrix" values="0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"/> <feMorphology operator="dilate" radius="2"/> <feComponentTransfer> <feFuncR type="linear" slope="-255" intercept="128"/> <feFuncG type="linear" slope="-255" intercept="128"/> <feFuncB type="linear" slope="-255" intercept="128"/> </feComponentTransfer> <feComposite operator="in" in2="SourceGraphic"/> </filter> </defs> </svg>Encapsulez le texte dans un
<span>.<div class="badge--svg"> <span>Badge content</span> </div>Appliquez le filtre à
<span>.
.badge--svg span{
color: var(--badge-bg);
filter: url(#contrast-filter);
}