Cuando creas componentes de IU reutilizables (como insignias o botones) o admites temas dinámicos, puede ser difícil garantizar que el texto sea legible con un color de fondo impredecible. La función de CSS contrast-color() se resuelve en black o white, lo que maximiza la relación de contraste con el color de fondo especificado.
Determina el color de texto óptimo
Cuando un elemento tiene un color de fondo variable o insertado de forma dinámica, puedes confiar en que el navegador calculará el color de texto más seguro.
OBLIGATORIO: Establece la propiedad color con la función contrast-color() y pasa el color de fondo (o su propiedad personalizada) como argumento.
.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));
}
NO USES contrast-color() para background-color. Está diseñado específicamente para que los colores de primer plano (como el texto o los bordes) contrasten con un color de fondo determinado.
Cómo realizar la integración con los temas
Como contrast-color() acepta cualquier valor de <color> válido, incluidas las propiedades personalizadas de CSS, es especialmente potente dentro de los sistemas de diseño. Si centralizas tus temas de fondo como variables, te aseguras de que el contenido en primer plano actualice automáticamente su contraste cada vez que cambie el tema, incluso a los colores especificados por el usuario.
SÍ: Haz referencia a las variables de fondo compartidas dentro de contrast-color() para garantizar la sincronización en toda la aplicación.
.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);
}
NO: Codifiques de forma fija un color de texto independiente para cada estado de interacción cuando el fondo sea dinámico. Si lo haces, se volverá a producir la discrepancia que contrast-color() busca evitar.
Estrategias de resguardo
Para admitir navegadores sin contrast-color(), debes proporcionar un resguardo. Si el color de fondo es conocido y fijo, usa un color de contraste hard-coded. En el caso de los fondos dinámicos en los que se desconoce el color, elige una estrategia de la siguiente tabla según los requisitos de tu IU y los objetivos de compatibilidad con navegadores.
| Estrategia | Ideal para… | Consideraciones |
|---|---|---|
| Sintaxis de color relativo | Cálculo de contraste automatizado y de alta calidad solo con CSS. | Es el resguardo de solo CSS de mayor calidad. |
| Sombra de texto | Mejora rápida de la legibilidad en cualquier fondo. | Puede verse "sucio" o "brillante"; puede no ser adecuado para todos los diseños. |
| Trazo del texto | Preservar el grosor de la fuente y, al mismo tiempo, garantizar el contraste de los bordes | Usa paint-order para evitar que se reduzcan las formas de las letras, si está disponible. |
| Superposición translúcida | Garantizar un área de contraste mínima detrás del texto | Cambia el aspecto del color de fondo debajo del texto y requiere un elemento de texto independiente. |
| Filtros SVG | Contraste reactivo que se actualiza a medida que cambia el fondo | Requiere un elemento de texto independiente; implementación poco elegante. |
Recomendado: Sintaxis de color relativo (RCS)
En los navegadores compatibles, RCS proporciona el resguardo automatizado más sólido para los colores dinámicos.
@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));
}
}
Resguardos de CSS alternativos
SÍ: Selecciona el resguardo que mejor se adapte a tus restricciones de diseño si RCS no es suficiente o no es compatible.
Opción 1: Trazo de texto
.badge--stroke {
-webkit-text-stroke: 4px black;
}
Opción 2: Trazo de texto con paint-order para conservar las formas de las letras
.badge--stroke {
-webkit-text-stroke: 4px black;
paint-order: stroke fill;
}
Opción 3: Superposición de fondo translúcido
.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;
}
Opción 4: Sombra de texto
.badge--shadow {
text-shadow: 0 1px 3px rgb(0 0 0 / 0.8);
}
Opción 5: Filtros SVG
Carga esta definición de filtro SVG. Se debe agregar directamente al documento HTML si necesitas admitir versiones de Safari afectadas por este error de WebKit, que impide las referencias de URL externas y de datos para los filtros. De lo contrario, se debe cargar desde un archivo externo, ya sea un archivo SVG o intercalado en 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>Une el texto en un
<span>.<div class="badge--svg"> <span>Badge content</span> </div>Aplica el filtro a
<span>.
.badge--svg span{
color: var(--badge-bg);
filter: url(#contrast-filter);
}