Formulários consistentes com a marca

Recursos da Web: accent-color

Historicamente, tem sido difícil personalizar elementos de formulário HTML padrão, como caixas de seleção e botões de opção. Os desenvolvedores geralmente precisavam escolher entre usar os padrões do navegador ou criar componentes personalizados do zero. Criar controles personalizados é demorado e pode facilmente levar a problemas de acessibilidade ou estados ausentes (como o estado indeterminado para caixas de seleção).

A propriedade CSS accent-color oferece uma maneira simples de aplicar a cor da sua marca às entradas de formulário HTML integradas com uma única linha de CSS, sem sacrificar a acessibilidade ou os recursos integrados do navegador.

Como implementar

Para aplicar a cor da sua marca aos controles de formulário:

  1. Identifique a cor da sua marca: escolha uma cor que represente sua marca.
  2. Aplique a propriedade accent-color: adicione accent-color ao elemento ou a um elemento de contêiner (como body ou um formulário específico) no CSS.
  3. Suporte ao modo escuro (opcional, mas recomendado): use color-scheme para informar ao navegador que seu site é compatível com o modo escuro e ajuste o accent-color, se necessário, para melhorar o contraste.

Exemplo de código: controles de formulário consistentes com a 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>

Implementação estratégica e práticas recomendadas

  • USE accent-color para personalizar facilmente os controles de formulário de acordo com sua marca.
  • NÃO confie cegamente no navegador para lidar com o contraste. Embora os navegadores devam determinar automaticamente uma cor de contraste qualificada, bugs conhecidos em implementações como o Safari (bug 244233 do WebKit) e o Chrome para Android (bug 343503163 do Chromium) podem falhar ao inverter as cores da marca de seleção, resultando em controles invisíveis ou difíceis de ver ao usar cores que não têm contraste suficiente com o plano de fundo (por exemplo, cores claras no modo claro ou cores escuras no modo escuro).
  • FAÇA a combinação de accent-color com color-scheme: light dark para garantir que os controles de formulário fiquem bons nos temas claro e escuro.
  • NÃO use uma cor muito próxima à do plano de fundo. Embora os navegadores tentem garantir o contraste, é melhor fornecer uma cor com um bom contraste de base.
  • NÃO presuma que accent-color funciona em todos os elementos de formulário. No momento, ele só colore os elementos checkbox, radio, range e progress.

Estratégia de fallback

Para navegadores que não oferecem suporte a accent-color, os controles de formulário voltam à aparência padrão do navegador. Para garantir a consistência total da marca e alta confiabilidade em todos os ambientes, você PRECISA implementar uma estratégia de substituição personalizada usando a técnica estabelecida de "entrada visualmente oculta".

Aprimoramento progressivo com @supports not

É OBRIGATÓRIO usar a regra @supports not para aplicar estilos substitutos personalizados somente quando accent-color não for compatível. Isso garante que você aproveite a simplicidade do accent-color para navegadores mais recentes, além de uma experiência de marca consistente para os mais antigos.

1. Estrutura HTML

Verifique se os rótulos envolvem o texto em um <span> para permitir seletores irmãos em CSS:

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

2. CSS substituto

Aplique estilos personalizados em um bloco @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;
  }
}

Progresso do intervalo dinâmico no substituto do Webkit

Para fazer com que o preenchimento de progresso se mova com o controle deslizante em um controle deslizante de intervalo em navegadores Webkit (sem accent-color), use uma variável CSS e uma pequena quantidade de JavaScript.

  1. Atualize o CSS: use uma variável CSS para o ponto de gradiente:

    input[type="range"]::-webkit-slider-runnable-track {
      background: linear-gradient(to right, var(--brand-color) var(--progress, 0%), #ccc var(--progress, 0%));
    }
    
  2. Adicionar JavaScript: atualize a variável no evento input:

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