Formularios coherentes con la marca

Funciones web: accent-color

Históricamente, personalizar los elementos de formulario HTML estándar, como las casillas de verificación y los botones de selección, ha sido difícil. Los desarrolladores a menudo debían elegir entre usar los valores predeterminados del navegador o compilar componentes personalizados desde cero. Crear controles personalizados lleva mucho tiempo y puede generar fácilmente problemas de accesibilidad o estados faltantes (como el estado indeterminado para las casillas de verificación).

La propiedad de CSS accent-color proporciona una forma sencilla de incorporar el color de tu marca a las entradas de formularios HTML integradas con una sola línea de CSS, sin sacrificar la accesibilidad ni las funciones integradas del navegador.

Cómo implementarla

Para aplicar el color de tu marca a los controles de formulario, haz lo siguiente:

  1. Identifica el color de tu marca: Elige un color que represente tu marca.
  2. Aplica la propiedad accent-color: Agrega accent-color al elemento o a un elemento contenedor (como body o un formulario específico) en tu CSS.
  3. Admite el modo oscuro (opcional, pero recomendado): Usa color-scheme para que el navegador sepa que tu sitio admite el modo oscuro y ajusta el accent-color si es necesario para mejorar el contraste.

Código de ejemplo: Controles de formulario coherentes con la marca

:root {
  --brand-color: #6200ee;
}

/* Apply accent-color to the body or a specific container */
body {
  accent-color: var(--brand-color);
}

/* Optional: Adjust for dark mode if needed */
@media (prefers-color-scheme: dark) {
  :root {
    --brand-color: #bb86fc; /* A lighter shade for dark mode */
  }
}
<form>
  <!-- Checkbox -->
  <label for="subscribe">
    <input type="checkbox" id="subscribe" checked>
    Subscribe to newsletter
  </label>

  <!-- Radio Buttons -->
  <label for="plan-monthly">
    <input type="radio" id="plan-monthly" name="plan" value="monthly">
    Monthly
  </label>
  <label for="plan-yearly">
    <input type="radio" id="plan-yearly" name="plan" value="yearly" checked>
    Yearly
  </label>

  <!-- Range Slider -->
  <label for="volume">Volume:</label>
  <input type="range" id="volume" min="0" max="100" value="70">

  <!-- Progress Bar -->
  <label for="file">Upload Progress:</label>
  <progress id="file" max="100" value="70">70%</progress>
</form>

Implementación estratégica y prácticas recomendadas

  • SÍ usa accent-color para aplicar temas fácilmente a los controles de formulario y que coincidan con tu marca.
  • NO confíes ciegamente en que el navegador controlará el contraste. Si bien se supone que los navegadores determinan automáticamente un color de contraste apto, los errores conocidos en implementaciones como Safari (error 244233 de WebKit) y Chrome para Android (error 343503163 de Chromium) pueden no invertir los colores de las marcas de verificación, lo que genera controles invisibles o difíciles de ver cuando se usan colores que no tienen suficiente contraste con el fondo (p.ej., colores claros en el modo claro o colores oscuros en el modo oscuro).
  • SÍ combina accent-color con color-scheme: light dark para asegurarte de que los controles de formulario se vean bien en los temas claro y oscuro.
  • NO uses un color que sea demasiado similar al color de fondo. Aunque los navegadores intentan garantizar el contraste, es mejor proporcionar un color con un buen contraste de base.
  • NO asumas que accent-color funciona en todos los elementos de formulario. Actualmente, solo se aplica a los elementos checkbox, radio, range y progress.

Estrategia de resguardo

En el caso de los navegadores que no admiten accent-color, los controles de formulario recurren a la apariencia predeterminada del navegador. Para garantizar la coherencia total de la marca y una alta confiabilidad en todos los entornos, DEBES implementar una estrategia de resguardo personalizada con la técnica establecida de "entrada oculta visualmente".

Mejora progresiva con @supports not

DEBES usar la regla @supports not para aplicar estilos de resguardo personalizados solo cuando no se admita accent-color. Esto garantiza que aproveches la simplicidad de accent-color para los navegadores actualizados y, al mismo tiempo, que se garantice una experiencia de marca coherente para los más antiguos.

1. Estructura HTML

Asegúrate de que tus etiquetas encierren el texto en un elemento <span> para permitir selectores hermanos en CSS:

<label for="subscribe-fallback">
  <input type="checkbox" id="subscribe-fallback" class="visually-hidden" checked>
  <span>Subscribe to newsletter</span>
</label>

2. Resguardo de CSS

Aplica estilos personalizados dentro de un bloque @supports not:

/* Fallback for older browsers without accent-color */
@supports not (accent-color: var(--brand-color)) {
  /* Visually hide the native input using the canonical accessible recipe */
  form input[type="checkbox"].visually-hidden {
    position: absolute !important;
    clip-path: inset(50%) !important;
    overflow: hidden !important;
    width: 1px !important;
    height: 1px !important;
    margin: -1px !important;
    padding: 0 !important;
    border: 0 !important;
    white-space: nowrap !important;
  }

  /* Style the wrapper label */
  label {
    position: relative;
    padding-left: 2rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
  }

  /* Custom box for checkbox */
  input[type="checkbox"] + span::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 1.2rem;
    height: 1.2rem;
    border: 2px solid #ccc;
    background: white;
    border-radius: 4px;
    box-sizing: border-box;
    transition: all 0.2s ease;
  }

  /* Ensure custom checkbox shows focus for keyboard users */
  input[type="checkbox"]:focus-visible + span::before {
    outline: 2px solid #000;
    outline-offset: 2px;
  }

  /* Checked State */
  input[type="checkbox"]:checked + span::before {
    background-color: var(--brand-color, #6200ee);
    border-color: var(--brand-color, #6200ee);
  }

  /* Checkmark (Unicode) */
  input[type="checkbox"]:checked + span::after {
    content: "✓";
    position: absolute;
    left: 0.25rem;
    top: 50%;
    transform: translateY(-50%);
    color: white;
    font-weight: bold;
    font-size: 0.9rem;
  }

  /* Fallback for Range Slider */
  input[type="range"] {
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
  }

  /* Webkit (Chrome, Safari, Edge) */
  input[type="range"]::-webkit-slider-runnable-track {
    width: 100%;
    height: 8px;
    /* Use gradient to show progress for a static value (e.g., 70%) or update with JS */
    background: linear-gradient(to right, var(--brand-color, #6200ee) 70%, #ccc 70%);
    border-radius: 4px;
  }

  input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    height: 16px;
    width: 16px;
    border-radius: 50%;
    background: var(--brand-color, #6200ee);
    cursor: pointer;
    margin-top: -4px; /* Center vertically */
  }

  /* Firefox */
  input[type="range"]::-moz-range-track {
    width: 100%;
    height: 8px;
    background: #ccc;
    border-radius: 4px;
  }

  input[type="range"]::-moz-range-thumb {
    height: 16px;
    width: 16px;
    border-radius: 50%;
    background: var(--brand-color, #6200ee);
    cursor: pointer;
  }

  /* Firefox specific progress bar */
  input[type="range"]::-moz-range-progress {
    background-color: var(--brand-color, #6200ee);
    height: 8px;
    border-radius: 4px;
  }

  /* Fallback for Progress Bar */
  progress {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    border: none;
    background: #ccc;
    height: 8px;
    border-radius: 4px;
  }

  progress::-webkit-progress-bar {
    background-color: #ccc;
    border-radius: 4px;
  }

  progress::-webkit-progress-value {
    background-color: var(--brand-color, #6200ee);
    border-radius: 4px;
  }

  progress::-moz-progress-bar {
    background-color: var(--brand-color, #6200ee);
    border-radius: 4px;
  }
}

Progreso del rango dinámico en el resguardo de WebKit

Para que el relleno de progreso se mueva con el control deslizante de rango en los navegadores Webkit (sin accent-color), puedes usar una variable de CSS y una pequeña cantidad de JavaScript.

  1. Actualiza el CSS: Usa una variable de CSS para el punto de degradado:

    input[type="range"]::-webkit-slider-runnable-track {
      background: linear-gradient(to right, var(--brand-color) var(--progress, 0%), #ccc var(--progress, 0%));
    }
    
  2. Agrega JavaScript: Actualiza la variable en el evento input:

if (!CSS.supports('accent-color')) {
  const slider = document.getElementById('volume');
  slider.addEventListener('input', (e) => {
    e.target.style.setProperty('--progress', `${e.target.value}%`);
  });
}