品牌一致的表单

Web 功能: accent-color

过去,自定义标准 HTML 表单元素(例如复选框和单选按钮)一直很困难。开发者通常需要在以下两种方案之间做出选择:使用浏览器默认设置,或从头开始构建自定义组件。构建自定义控件非常耗时,并且很容易导致无障碍功能问题或状态缺失(例如复选框的不确定状态)。

CSS 属性 accent-color 提供了一种简单的方法,只需一行 CSS 代码,即可将您的品牌颜色应用到内置 HTML 表单输入,而不会牺牲无障碍功能或内置浏览器功能。

如何实现

如需将品牌颜色应用于表单控件,请执行以下操作:

  1. 确定品牌颜色:选择一种代表您品牌的颜色。
  2. 应用 accent-color 属性:在 CSS 中,将 accent-color 添加到元素或容器元素(例如 body 或特定表单)中。
  3. 支持深色模式(可选,但建议采用):使用 color-scheme 让浏览器知道您的网站支持深色模式,并根据需要调整 accent-color 以获得更好的对比度。

示例代码:品牌一致的表单控件

: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>

策略性实施和最佳实践

  • 务必使用 accent-color 轻松设置表单控件的主题,使其与您的品牌相符。
  • 请勿盲目信任浏览器来处理对比度。虽然浏览器应该会自动确定符合条件的对比色,但 Safari(WebKit bug 244233)和 Android Chrome(Chromium bug 343503163)等实现中存在的已知 bug 可能会导致无法反转对勾标记颜色,从而导致在使用与背景对比度不足的颜色(例如浅色模式下的浅色或深色模式下的深色)时,控件不可见或难以看清。
  • 务必将 accent-color 与 color-scheme: light dark 结合使用,以确保表单控件在浅色和深色主题下都能呈现良好的视觉效果。
  • 请勿使用与背景颜色过于接近的颜色,尽管浏览器会尝试保证对比度,但最好还是提供具有良好基础对比度的颜色。
  • 请勿假设 accent-color 适用于所有表单元素。目前,它仅为 checkbox、radio、range 和 progress 元素着色。

后备策略

对于不支持 accent-color 的浏览器,表单控件会回退到浏览器的默认外观。为确保在所有环境中实现完全的品牌一致性和高可靠性,您必须使用既定的“视觉隐藏输入”技术实现自定义后备策略。

使用 @supports not 渐进增强

您必须使用 @supports not 规则,以便仅在不支持 accent-color 时应用自定义后备样式。这样可以确保您在现代浏览器中充分利用 accent-color 的简便性,同时在旧版浏览器中提供一致的品牌体验。

1. HTML 结构

确保标签将文本封装在 <span> 中,以便在 CSS 中使用同级选择器:

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

2. CSS 后备

在 @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;
  }
}

Webkit 后备方案中的动态范围进度

如需在 Webkit 浏览器中让进度填充条随范围滑块上的滑块移动(不使用 accent-color),您可以使用 CSS 变量和少量 JavaScript。

  1. 更新 CSS:为渐变色标使用 CSS 变量:

    input[type="range"]::-webkit-slider-runnable-track {
      background: linear-gradient(to right, var(--brand-color) var(--progress, 0%), #ccc var(--progress, 0%));
    }
    
  2. 添加 JavaScript:更新 input 事件中的变量:

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