0Apoyar

Playground de contrast-color() en CSS

Explora contrast-color() con un selector de color de fondo. El ejemplo aplica el color elegido a un botón y deja que CSS resuelva automáticamente si el texto debe ser blanco o negro, con una salida de código inspirada en MDN.

Leer la guía

Preguntar a la IA

Playground de contrast-color() en CSS

Respuesta generada con openai/gpt-5.4-nano. La IA puede cometer errores. Revisa siempre el resultado.

Tu navegador todavía no soporta contrast-color(). Se muestra un fallback.

HTML
<button class="contrast-color-demo" type="button">
  contrast-color()
</button>
Tailwind CSS
Algunas clases con valores arbitrarios pueden sustituirse por clases nativas, según el valor y la configuración de Tailwind CSS.
<button class="[--button-color:#660066] px-5 py-3 border-0 bg-(--button-color) text-[contrast-color(var(--button-color))]" type="button">
  contrast-color()
</button>
CSS
.contrast-color-demo {
  --button-color: #660066;

  padding: 0.75rem 1.25rem;
  border: 0;
  background-color: var(--button-color);
  color: contrast-color(var(--button-color));
}

Guía educativa de contrast-color()

Elegir texto para un fondo real

Deja que CSS elija entre blanco y negro para un color de fondo. Aprende a mantener el par sincronizado, reconocer los límites con transparencia e imágenes y comprobar el contraste de lo que se pinta.

Volver al playground

Modelo mental

Un argumento de color, dos posibles resultados y una comprobación independiente.

  1. Color de referencia

    El mismo token alimenta background-color y contrast-color().

  2. Elección del navegador

    Devuelve blanco o negro; no inspecciona el contenido visual de la página.

  3. Comprobar el resultado

    Mide el texto contra el fondo compuesto y revisa cada estado.

Esquema conceptual estáticoEl argumento debe representar la superficie detrás del texto. Aceptar la sintaxis no demuestra accesibilidad. Las muestras siguientes aplican la función nativa.

contrast-color(color) devuelve black o white. El algoritmo de elección lo define el navegador; no puedes pasarle una paleta de candidatos, un objetivo de ratio o el tamaño del texto. La función devuelve un color CSS que aquí usamos como primer plano.

Primer plano y fondo sincronizados

Una variable compartida evita elegir texto para otro color.

Define un color opaco de fondo y úsalo también en color: contrast-color(var(--button-color)). Si cambia esa variable, CSS vuelve a resolver ambos valores. No hace falta trasladar la elección de texto a un listener de JavaScript.

La misma regla sobre dos fondos opacos

Fondo #660066 · oscuro

Fallback activo: par de colores explícito; contrast-color() no se aplica.

Fondo #f5d0fe · claro

Fallback activo: par de colores explícito; contrast-color() no se aplica.

Las muestras Aa usan la función nativa cuando está disponible. Sin soporte, conservan un par de respaldo explícito y muestran un aviso. Las etiquetas exteriores permiten identificar cada fondo en ambos temas.
La misma regla sobre dos fondos opacos
<div class="contrast-scene-foreground">
  <div class="dark">
    <p>Fondo #660066 · oscuro</p>
    <div class="stage"><span class="sample" aria-hidden="true">Aa</span></div>
    <p class="fallback">Fallback activo: par de colores explícito; contrast-color() no se aplica.</p>
  </div>
  <div class="light">
    <p>Fondo #f5d0fe · claro</p>
    <div class="stage"><span class="sample" aria-hidden="true">Aa</span></div>
    <p class="fallback">Fallback activo: par de colores explícito; contrast-color() no se aplica.</p>
  </div>
</div>
:root {
  color-scheme: light dark;
}

.contrast-scene-foreground {

  display: flex;
  flex-wrap: wrap;
  gap: 1rem;

  > div {
    flex: 1 1 12rem;
    min-inline-size: 0;
    display: grid;
    align-content: start;
    gap: 0.5rem;
  }

  p {
    margin: 0;
    color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
    font: 1rem/1.6 system-ui, sans-serif;
    overflow-wrap: anywhere;
  }

  .stage {
    display: grid;
    border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  }

  .sample {
    display: grid;
    place-items: center;
    min-block-size: 6rem;
    padding: min(1rem, 5vi);
    font: 600 1.5rem/1.6 system-ui, sans-serif;
  }

  @media (forced-colors: active) {
    .stage, .sample {
      color: CanvasText;
      background: Canvas;
      border-color: CanvasText;
    }
  }

  .dark .sample {
    --button-color: #660066;
    --fallback-text-color: white;
    background-color: var(--button-color);
    color: var(--fallback-text-color);
  }

  @supports (color: contrast-color(#660066)) {
    .dark .sample {
      color: contrast-color(var(--button-color));
    }

    .dark .fallback {
      display: none;
    }
  }

  .light .sample {
    --button-color: #f5d0fe;
    --fallback-text-color: black;
    background-color: var(--button-color);
    color: var(--fallback-text-color);
  }

  @supports (color: contrast-color(#f5d0fe)) {
    .light .sample {
      color: contrast-color(var(--button-color));
    }

    .light .fallback {
      display: none;
    }
  }
}

El primer fondo usa el estado por defecto y las declaraciones compartidas del lab. El segundo cambia solo el color. La guía permanece fuera del controlador: mover el selector del playground no modifica estas muestras.

Transparencia y composición

La misma entrada no describe dos fondos finales distintos.

Un color con alfa deja participar a las capas inferiores. contrast-color() recibe el color, no el fondo compuesto de la página. En estas muestras la entrada es siempre rgb(255 255 255 / 0.2), pero se pinta sobre dos bases diferentes. La función no recibe esa base como segundo argumento.

El mismo blanco al 20% sobre blanco y negro suave

Base #fff · blanco al 20%

Fallback activo: par de colores explícito; contrast-color() no se aplica.

Base #111 · blanco al 20%

Fallback activo: par de colores explícito; contrast-color() no se aplica.

La decisión nativa recibe exactamente el mismo argumento en ambas muestras; el contraste visual cambia con la base. Aa es una muestra de diagnóstico, no información esencial. Una etiqueta legible fuera de cada superficie explica el caso.
El mismo blanco al 20% sobre blanco y negro suave
<div class="contrast-scene-backgrounds">
  <div class="light">
    <p>Base #fff · blanco al 20%</p>
    <div class="stage"><span class="sample" aria-hidden="true">Aa</span></div>
    <p class="fallback">Fallback activo: par de colores explícito; contrast-color() no se aplica.</p>
  </div>
  <div class="dark">
    <p>Base #111 · blanco al 20%</p>
    <div class="stage"><span class="sample" aria-hidden="true">Aa</span></div>
    <p class="fallback">Fallback activo: par de colores explícito; contrast-color() no se aplica.</p>
  </div>
</div>
:root {
  color-scheme: light dark;
}

.contrast-scene-backgrounds {

  display: flex;
  flex-wrap: wrap;
  gap: 1rem;

  > div {
    flex: 1 1 12rem;
    min-inline-size: 0;
    display: grid;
    align-content: start;
    gap: 0.5rem;
  }

  p {
    margin: 0;
    color: light-dark(oklch(0.22 0.01 95), oklch(0.95 0.01 95));
    font: 1rem/1.6 system-ui, sans-serif;
    overflow-wrap: anywhere;
  }

  .stage {
    display: grid;
    border: 1px solid light-dark(oklch(0.84 0.01 95), oklch(0.36 0.024 260));
  }

  .sample {
    display: grid;
    place-items: center;
    min-block-size: 6rem;
    padding: min(1rem, 5vi);
    font: 600 1.5rem/1.6 system-ui, sans-serif;
  }

  @media (forced-colors: active) {
    .stage, .sample {
      color: CanvasText;
      background: Canvas;
      border-color: CanvasText;
    }
  }

  --wash: rgb(255 255 255 / 0.2);

  .light .stage {
    background-color: #fff;
  }

  .dark .stage {
    background-color: #111;
  }

  .sample {
    background-color: var(--wash);
    color: #000;
  }

  @supports (color: contrast-color(rgb(255 255 255 / 0.2))) {
    .sample {
      color: contrast-color(var(--wash));
    }

    .fallback {
      display: none;
    }
  }
}

Para texto de producción, usa una superficie opaca conocida o determina el color compuesto con todas sus capas y valida el par resultante. No calcules el contraste contra los canales RGB del color transparente aislado; cambiar la base, el tema o la opacidad del grupo exige revisar el resultado.

Imágenes, gradientes y límites

Un argumento no representa todos los píxeles de una imagen.

La función admite un valor de color, no una imagen ni un gradiente. Un color medio o tomado de una esquina no describe cada zona situada detrás del texto. Esta receta coloca una superficie opaca bajo el contenido y usa esa superficie como referencia.

Texto protegido de un fondo variable

Una superficie opaca define el fondo real de este texto.

Fallback activo: par de colores explícito; contrast-color() no se aplica.

El gradiente pasa de oscuro a claro. El texto siempre se pinta sobre #fef3c7 opaco: ese es el argumento de contrast-color(). La base del texto permanece válida aunque cambie la imagen exterior.
Texto protegido de un fondo variable
<div class="contrast-scene-limits">
  <p class="label">Una superficie opaca define el fondo real de este texto.</p>
  <p class="fallback">Fallback activo: par de colores explícito; contrast-color() no se aplica.</p>
</div>
.contrast-scene-limits {
  padding: min(1rem, 5vi);
  background: linear-gradient(110deg, #111 50%, #fff 50%);
  font: 1rem/1.6 system-ui, sans-serif;
  overflow-wrap: anywhere;

  .label {
    --surface: #fef3c7;
    margin: 0;
    padding: min(1rem, 5vi);
    background-color: var(--surface);
    color: #000;
  }

  .fallback {
    margin: 0;
    padding: min(1rem, 5vi);
    background-color: #fff;
    color: #000;
  }

  @supports (color: contrast-color(#fef3c7)) {
    .label {
      color: contrast-color(var(--surface));
    }

    .fallback {
      display: none;
    }
  }

  @media (forced-colors: active) {
    background: Canvas;

    .label, .fallback {
      background: Canvas;
      color: CanvasText;
    }
  }
}

Blanco o negro también limita la dirección visual: no devuelve un color de marca ni comprueba foco, bordes o iconos por ti. En fondos de tono medio, revisa tamaño, peso y legibilidad real aunque una medición alcance su umbral. Elige otra superficie si el texto resulta difícil de leer.

Comprobar el contraste

Separar la elección del navegador de la evaluación WCAG 2.2.

Para texto normal, WCAG 2.2 AA pide al menos 4.5:1; para texto grande, 3:1. La referencia de texto grande parte de 18 pt (24 CSS px) o 14 pt en negrita (aproximadamente 18.67 CSS px). Evalúa el tamaño efectivo y la fuente; ser un encabezado no basta.

Referencia de pares opacos sRGB · no predice la elección de tu navegador
FondoCon negroCon blanco
#6600661,76:1 Por debajo de 4.5:111,95:1 Alcanza 4.5:1
#f5d0fe15,34:1 Alcanza 4.5:11,37:1 Por debajo de 4.5:1
#7777774,69:1 Alcanza 4.5:14,48:1 Por debajo de 4.5:1

Los ratios se calculan en servidor con luminancia relativa sRGB. Se muestran con dos decimales, pero el umbral se compara sin redondear. #777777 ilustra un resultado cercano al mínimo: no hay margen para ignorar transparencia, estados o cambios de superficie.

Ratio = (luminancia más clara + 0.05) / (luminancia más oscura + 0.05). Esta tabla solo calcula pares opacos #RRGGBB; no analiza píxeles, imágenes, alfa ni otros espacios de color.

  1. Obtén el color de texto computado y el fondo final detrás de él, incluidas las capas transparentes. Un background-color computado con alfa todavía necesita composición.
  2. Mide el par en una herramienta de contraste y repite la revisión con hover, foco, selección y cada tema. Con imágenes, revisa la zona menos favorable o protege el texto con una superficie conocida.
  3. Comprueba también teclado, foco visible, ampliación de texto y colores forzados. La información visual necesaria para identificar controles y estados tiene su propia exigencia de contraste; elegir el texto no resuelve toda la interfaz.

Una receta con fallback y estados

Conservar pares conocidos cuando falta la función.

El botón tiene pares de fondo y texto explícitos para reposo y hover/foco. @supports activa la elección nativa; sin ella, siguen aplicándose los pares revisados. El contorno de foco tiene su propio color y espacio, y los colores forzados conservan valores de sistema.

Un botón con dos estados de color

Fallback activo: par de colores explícito; contrast-color() no se aplica.

Pasa el puntero o enfoca el botón con Tab: el fondo cambia de #660066 a #f5d0fe. El texto se adapta con la función o con los pares de respaldo. La muestra no ejecuta una acción al pulsarla.
Un botón con dos estados de color
<div class="contrast-scene-recipe">
  <button type="button">Probar hover y foco</button>
  <p class="fallback">Fallback activo: par de colores explícito; contrast-color() no se aplica.</p>
</div>
.contrast-scene-recipe {
  padding: min(1rem, 5vi);
  background-color: #fff;
  color: #111;
  font: 1rem/1.6 system-ui, sans-serif;
  overflow-wrap: anywhere;

  button {
    --button-bg: #660066;
    --button-fg: #fff;
    min-block-size: 2.75rem;
    max-inline-size: 100%;
    padding: 0.75rem 1.25rem;
    border: 2px solid #660066;
    border-radius: 16px;
    corner-shape: squircle;
    background-color: var(--button-bg);
    color: var(--button-fg);
    font: inherit;
    white-space: normal;
    overflow-wrap: anywhere;

    &:hover, &:focus-visible {
      --button-bg: #f5d0fe;
      --button-fg: #000;
    }

    &:focus-visible {
      outline: 3px solid #111;
      outline-offset: 3px;
    }

    @supports (color: contrast-color(#660066)) {
      color: contrast-color(var(--button-bg));
    }
  }

  .fallback {
    margin-block: 1rem 0;
  }

  @supports (color: contrast-color(#660066)) {
    .fallback {
      display: none;
    }
  }

  @media (forced-colors: active) {
    background: Canvas;
    color: CanvasText;

    button {
      background: ButtonFace;
      color: ButtonText;
      border-color: ButtonText;

      &:focus-visible {
        outline-color: Highlight;
      }
    }
  }
}

Si permites colores arbitrarios, los fallbacks fijos dejan de ser suficientes: limita la paleta, resuelve y verifica pares en servidor o añade una lógica que contemple el fondo real. var(--color, respaldo) no valida un token definido; protege funciones avanzadas con @supports y conserva valores válidos.

Errores frecuentes

Revisa primero la entrada, las capas y el par final.

Texto elegido para otro fondo

Resultado esperado
El argumento representa la superficie que hay detrás del texto.
Causa
El estado cambia background-color, pero la función conserva otro token.
Solución
Usa una variable compartida para ambas propiedades y revisa todos los estados.

El resultado falla sobre transparencia

Resultado esperado
Contraste suficiente contra el fondo compuesto.
Causa
La entrada transparente no incluye las capas inferiores ni la opacidad del grupo.
Solución
Fija una superficie opaca o calcula la composición real antes de validar el par.

Una parte del texto desaparece sobre la imagen

Resultado esperado
Lectura en toda la zona ocupada por el texto.
Causa
Un color de referencia no describe todas las zonas del gradiente o la fotografía.
Solución
Pon una superficie opaca detrás del texto o verifica la combinación más desfavorable.

Soporte confundido con accesibilidad

Resultado esperado
Contraste medido y contenido operable.
Causa
Se interpreta un @supports positivo como una auditoría de la interfaz.
Solución
Mide los colores finales y revisa tamaño, foco, teclado, estados y preferencias.

Soporte y compatibilidad

Compatibilidad de la función y validez del ejemplo son comprobaciones distintas.

En la consulta del 14 de octubre de 2026, MDN sitúa contrast-color() como Baseline Newly Available desde abril de 2026. BCD registra Chrome/Edge 147, Firefox 146 y Safari 26. Conserva fallback si tu público usa versiones anteriores.

La tarjeta Baseline consulta el estado del catálogo. @supports (color: contrast-color(#660066)) comprueba la sintaxis en el navegador actual; no comprueba tus tokens ni el contraste final. La especificación deja el algoritmo al navegador: una selección observada en un motor no certifica los demás.

Una sombra, un contorno de texto o un umbral de luminosidad aislado no garantizan contraste suficiente. Si usas otra estrategia de respaldo, comprueba el resultado sobre el fondo real con los mismos criterios.