liquid-theme-a11y

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Accessibility for Shopify Liquid Themes

Shopify Liquid主题无障碍设计指南

Core Principle

核心原则

Every interactive component must work with keyboard only, screen readers, and reduced-motion preferences. Start with semantic HTML — add ARIA only when native semantics are insufficient.
所有交互式组件必须支持纯键盘操作、屏幕阅读器,并且适配减少动画的偏好设置。优先使用语义化HTML——仅当原生语义不足时才添加ARIA属性。

Decision Table: Which Pattern?

决策表:选择哪种模式?

ComponentHTML ElementARIA PatternReference
Expandable content
<details>/<summary>
None neededAccordion
Modal/dialog
<dialog>
aria-modal="true"
Modal
Tooltip/popup
[popover]
attribute
role="tooltip"
fallback
Tooltip
Dropdown menu
<nav>
+
<ul>
aria-expanded
on triggers
Navigation
Tab interface
<div>
role="tablist/tab/tabpanel"
Tabs
Carousel/slider
<div>
role="region"
+
aria-roledescription
Carousel
Product card
<article>
aria-labelledby
Product card
Form
<form>
aria-invalid
,
aria-describedby
Forms
Cart drawer
<dialog>
Focus trapCart drawer
Price display
<span>
aria-label
for context
Prices
Filters
<form>
+
<fieldset>
aria-expanded
for disclosures
Filters
组件HTML元素ARIA模式参考
可展开内容
<details>/<summary>
无需额外设置折叠面板
模态框/对话框
<dialog>
aria-modal="true"
模态框
提示框/弹出层
[popover]
属性
role="tooltip"
降级方案
提示框
下拉菜单
<nav>
+
<ul>
触发器添加
aria-expanded
导航
标签页界面
<div>
role="tablist/tab/tabpanel"
标签页
轮播图/滑块
<div>
role="region"
+
aria-roledescription
轮播图
产品卡片
<article>
aria-labelledby
产品卡片
表单
<form>
aria-invalid
,
aria-describedby
表单
购物车抽屉
<dialog>
焦点捕获购物车抽屉
价格展示
<span>
aria-label
补充上下文
价格展示
筛选器
<form>
+
<fieldset>
折叠面板添加
aria-expanded
产品筛选器

Page Structure

页面结构

Landmarks

地标区域

html
<body>
  <a href="#main-content" class="skip-link">{{ 'accessibility.skip_to_content' | t }}</a>
  <header role="banner">
    <nav aria-label="{{ 'accessibility.main_navigation' | t }}">...</nav>
  </header>
  <main id="main-content">
    <!-- All page content inside main -->
  </main>
  <footer role="contentinfo">
    <nav aria-label="{{ 'accessibility.footer_navigation' | t }}">...</nav>
  </footer>
</body>
  • Single
    <header>
    ,
    <main>
    ,
    <footer>
    per page
  • Multiple
    <nav>
    elements must have distinct
    aria-label
  • All content must live inside a landmark
html
<body>
  <a href="#main-content" class="skip-link">{{ 'accessibility.skip_to_content' | t }}</a>
  <header role="banner">
    <nav aria-label="{{ 'accessibility.main_navigation' | t }}">...</nav>
  </header>
  <main id="main-content">
    <!-- 所有页面内容放在main标签内 -->
  </main>
  <footer role="contentinfo">
    <nav aria-label="{{ 'accessibility.footer_navigation' | t }}">...</nav>
  </footer>
</body>
  • 每页仅包含一个
    <header>
    <main>
    <footer>
  • 多个
    <nav>
    元素必须设置不同的
    aria-label
  • 所有内容必须包含在某个地标区域内

Skip Link

跳转链接

css
.skip-link {
  position: absolute;
  inset-inline-start: -999px;
  z-index: 999;
}
.skip-link:focus {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: 0;
  padding: 1rem;
  background: var(--color-background);
  color: var(--color-foreground);
}
css
.skip-link {
  position: absolute;
  inset-inline-start: -999px;
  z-index: 999;
}
.skip-link:focus {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: 0;
  padding: 1rem;
  background: var(--color-background);
  color: var(--color-foreground);
}

Headings

标题

  • One
    <h1>
    per page, never skip levels (h1 → h3)
  • Use real heading elements, not styled divs
  • Template:
    <h1>
    is typically the page/product title
  • 每页仅一个
    <h1>
    ,不要跳过标题层级(如从h1直接跳到h3)
  • 使用真实的标题元素,而非样式化的div
  • 模板规范:
    <h1>
    通常为页面/产品标题

Focus Management

焦点管理

Focus Indicators

焦点指示器

css
/* All interactive elements */
:focus-visible {
  outline: 2px solid rgb(var(--color-focus));
  outline-offset: 2px;
}

/* High contrast mode */
@media (forced-colors: active) {
  :focus-visible {
    outline: 3px solid LinkText;
  }
}
  • Minimum 3:1 contrast ratio for focus indicators
  • Use
    :focus-visible
    (not
    :focus
    ) to avoid showing on click
  • Never
    outline: none
    without a visible replacement
css
/* 所有交互式元素 */
:focus-visible {
  outline: 2px solid rgb(var(--color-focus));
  outline-offset: 2px;
}

/* 高对比度模式 */
@media (forced-colors: active) {
  :focus-visible {
    outline: 3px solid LinkText;
  }
}
  • 焦点指示器的对比度至少达到3:1
  • 使用
    :focus-visible
    (而非
    :focus
    )避免点击时显示
  • 禁止使用
    outline: none
    ,除非有可见的替代样式

Focus Trapping (Modals/Drawers)

焦点捕获(模态框/抽屉)

  • Trap focus inside modals, drawers, and dialogs
  • Return focus to trigger element on close
  • First focusable element gets focus on open
  • Query all focusable elements:
    a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])
See focus and keyboard patterns for full FocusTrap implementation.
  • 将焦点捕获在模态框、抽屉和对话框内
  • 关闭时将焦点返回至触发元素
  • 打开时自动聚焦第一个可交互元素
  • 查询所有可聚焦元素:
    a[href], button:not([disabled]), input:not([disabled]), select, textarea, [tabindex]:not([tabindex="-1"])
完整的FocusTrap实现请参考焦点与键盘模式

Component Patterns

组件模式

Product Card

产品卡片

html
<article class="product-card" aria-labelledby="ProductTitle-{{ product.id }}">
  <a href="{{ product.url }}" class="product-card__link" aria-labelledby="ProductTitle-{{ product.id }}">
    <img
      src="{{ product.featured_image | image_url: width: 400 }}"
      alt="{{ product.featured_image.alt | escape }}"
      loading="lazy"
      width="{{ product.featured_image.width }}"
      height="{{ product.featured_image.height }}"
    >
  </a>
  <h3 id="ProductTitle-{{ product.id }}">
    <a href="{{ product.url }}">{{ product.title }}</a>
  </h3>
  <div class="product-card__price" aria-label="{{ 'products.price_label' | t: price: product.price | money }}">
    {{ product.price | money }}
  </div>
  <button
    class="product-card__quick-add"
    tabindex="-1"
    aria-label="{{ 'products.quick_add' | t: title: product.title }}"
  >
    {{ 'products.add_to_cart' | t }}
  </button>
</article>
Rules:
  • Single tab stop per card (the main link)
  • tabindex="-1"
    on mouse-only shortcuts (quick add)
  • aria-labelledby
    on
    <article>
    pointing to the title
  • Descriptive alt text on images; empty
    alt=""
    if decorative
html
<article class="product-card" aria-labelledby="ProductTitle-{{ product.id }}">
  <a href="{{ product.url }}" class="product-card__link" aria-labelledby="ProductTitle-{{ product.id }}">
    <img
      src="{{ product.featured_image | image_url: width: 400 }}"
      alt="{{ product.featured_image.alt | escape }}"
      loading="lazy"
      width="{{ product.featured_image.width }}"
      height="{{ product.featured_image.height }}"
    >
  </a>
  <h3 id="ProductTitle-{{ product.id }}">
    <a href="{{ product.url }}">{{ product.title }}</a>
  </h3>
  <div class="product-card__price" aria-label="{{ 'products.price_label' | t: price: product.price | money }}">
    {{ product.price | money }}
  </div>
  <button
    class="product-card__quick-add"
    tabindex="-1"
    aria-label="{{ 'products.quick_add' | t: title: product.title }}"
  >
    {{ 'products.add_to_cart' | t }}
  </button>
</article>
规则:
  • 每张卡片仅保留一个制表位(主链接)
  • 纯鼠标操作的快捷按钮(如快速加入)设置
    tabindex="-1"
  • <article>
    标签使用
    aria-labelledby
    指向标题
  • 图片添加描述性alt文本;装饰性图片设置
    alt=""

Carousel

轮播图

html
<div
  role="region"
  aria-roledescription="carousel"
  aria-label="{{ section.settings.heading | escape }}"
>
  <div class="carousel__controls">
    <button
      aria-label="{{ 'accessibility.previous_slide' | t }}"
      aria-controls="CarouselSlides-{{ section.id }}"
    >{% render 'icon-chevron-left' %}</button>
    <button
      aria-label="{{ 'accessibility.next_slide' | t }}"
      aria-controls="CarouselSlides-{{ section.id }}"
    >{% render 'icon-chevron-right' %}</button>
    <button
      aria-label="{{ 'accessibility.pause_slideshow' | t }}"
      aria-pressed="false"
    >{% render 'icon-pause' %}</button>
  </div>

  <div id="CarouselSlides-{{ section.id }}" aria-live="polite">
    {% for slide in section.blocks %}
      <div
        role="group"
        aria-roledescription="slide"
        aria-label="{{ 'accessibility.slide_n_of_total' | t: n: forloop.index, total: forloop.length }}"
        {% unless forloop.first %}aria-hidden="true"{% endunless %}
      >
        {{ slide.settings.content }}
      </div>
    {% endfor %}
  </div>
</div>
Rules:
  • Auto-rotation minimum 5 seconds, pause on hover/focus
  • Play/pause button required for auto-rotating carousels
  • aria-live="polite"
    on slide container (set to
    "off"
    during auto-rotation)
  • aria-hidden="true"
    on inactive slides
  • Each slide:
    role="group"
    +
    aria-roledescription="slide"
html
<div
  role="region"
  aria-roledescription="carousel"
  aria-label="{{ section.settings.heading | escape }}"
>
  <div class="carousel__controls">
    <button
      aria-label="{{ 'accessibility.previous_slide' | t }}"
      aria-controls="CarouselSlides-{{ section.id }}"
    >{% render 'icon-chevron-left' %}</button>
    <button
      aria-label="{{ 'accessibility.next_slide' | t }}"
      aria-controls="CarouselSlides-{{ section.id }}"
    >{% render 'icon-chevron-right' %}</button>
    <button
      aria-label="{{ 'accessibility.pause_slideshow' | t }}"
      aria-pressed="false"
    >{% render 'icon-pause' %}</button>
  </div>

  <div id="CarouselSlides-{{ section.id }}" aria-live="polite">
    {% for slide in section.blocks %}
      <div
        role="group"
        aria-roledescription="slide"
        aria-label="{{ 'accessibility.slide_n_of_total' | t: n: forloop.index, total: forloop.length }}"
        {% unless forloop.first %}aria-hidden="true"{% endunless %}
      >
        {{ slide.settings.content }}
      </div>
    {% endfor %}
  </div>
</div>
规则:
  • 自动轮播的最小间隔为5秒,悬停或聚焦时暂停
  • 自动轮播的轮播图必须包含播放/暂停按钮
  • 幻灯片容器设置
    aria-live="polite"
    (自动轮播期间设为
    "off"
  • 非活跃幻灯片设置
    aria-hidden="true"
  • 每张幻灯片设置
    role="group"
    +
    aria-roledescription="slide"

Modal

模态框

html
<dialog
  id="Modal-{{ section.id }}"
  aria-labelledby="ModalTitle-{{ section.id }}"
  aria-modal="true"
>
  <div class="modal__header">
    <h2 id="ModalTitle-{{ section.id }}">{{ title }}</h2>
    <button
      type="button"
      aria-label="{{ 'accessibility.close' | t }}"
      on:click="/closeModal"
    >{% render 'icon-close' %}</button>
  </div>
  <div class="modal__content">
    <!-- Content -->
  </div>
</dialog>
Rules:
  • Use native
    <dialog>
    element
  • aria-labelledby
    pointing to the title
  • Close on Escape key (native with
    <dialog>
    )
  • Focus first interactive element on open
  • Return focus to trigger on close
html
<dialog
  id="Modal-{{ section.id }}"
  aria-labelledby="ModalTitle-{{ section.id }}"
  aria-modal="true"
>
  <div class="modal__header">
    <h2 id="ModalTitle-{{ section.id }}">{{ title }}</h2>
    <button
      type="button"
      aria-label="{{ 'accessibility.close' | t }}"
      on:click="/closeModal"
    >{% render 'icon-close' %}</button>
  </div>
  <div class="modal__content">
    <!-- 内容区域 -->
  </div>
</dialog>
规则:
  • 使用原生
    <dialog>
    元素
  • aria-labelledby
    指向标题
  • 支持Esc键关闭(
    <dialog>
    原生支持)
  • 打开时自动聚焦第一个可交互元素
  • 关闭时将焦点返回至触发元素

Cart Drawer

购物车抽屉

Same as modal pattern but with additional:
  • Live region for cart count updates:
    <span aria-live="polite" aria-atomic="true">
  • Clear "remove item" buttons with
    aria-label="{{ 'cart.remove_item' | t: title: item.title }}"
  • Quantity inputs with associated labels
与模态框模式相同,额外添加:
  • 购物车数量更新的实时区域:
    <span aria-live="polite" aria-atomic="true">
  • 清晰的“移除商品”按钮,设置
    aria-label="{{ 'cart.remove_item' | t: title: item.title }}"
  • 数量输入框关联对应的标签

Forms

表单

html
<form action="{{ routes.cart_url }}" method="post">
  <div class="form__field">
    <label for="Email-{{ section.id }}">{{ 'forms.email' | t }}</label>
    <input
      type="email"
      id="Email-{{ section.id }}"
      name="email"
      required
      aria-required="true"
      autocomplete="email"
      aria-describedby="EmailError-{{ section.id }}"
    >
    <p
      id="EmailError-{{ section.id }}"
      class="form__error"
      role="alert"
      hidden
    >{{ 'forms.email_required' | t }}</p>
  </div>
</form>
Rules:
  • Every input has a visible
    <label>
    with matching
    for
    /
    id
  • Use
    <fieldset>/<legend>
    for radio/checkbox groups
  • Error messages:
    role="alert"
    +
    aria-describedby
    linking to input
  • aria-invalid="true"
    on invalid inputs
  • autocomplete
    attributes on common fields
  • Required fields:
    required
    +
    aria-required="true"
    + visual indicator
html
<form action="{{ routes.cart_url }}" method="post">
  <div class="form__field">
    <label for="Email-{{ section.id }}">{{ 'forms.email' | t }}</label>
    <input
      type="email"
      id="Email-{{ section.id }}"
      name="email"
      required
      aria-required="true"
      autocomplete="email"
      aria-describedby="EmailError-{{ section.id }}"
    >
    <p
      id="EmailError-{{ section.id }}"
      class="form__error"
      role="alert"
      hidden
    >{{ 'forms.email_required' | t }}</p>
  </div>
</form>
规则:
  • 每个输入框都有可见的
    <label>
    ,并通过
    for
    /
    id
    关联
  • 单选框/复选框组使用
    <fieldset>/<legend>
  • 错误提示设置
    role="alert"
    +
    aria-describedby
    关联到输入框
  • 无效输入框设置
    aria-invalid="true"
  • 常用字段添加
    autocomplete
    属性
  • 必填字段同时设置
    required
    +
    aria-required="true"
    + 视觉标识

Product Filters

产品筛选器

html
<form class="facets">
  <div class="facets__group">
    <button
      type="button"
      aria-expanded="false"
      aria-controls="FilterColor-{{ section.id }}"
    >{{ 'filters.color' | t }}</button>
    <fieldset id="FilterColor-{{ section.id }}" hidden>
      <legend class="visually-hidden">{{ 'filters.filter_by_color' | t }}</legend>
      {% for color in colors %}
        <label>
          <input type="checkbox" name="filter.color" value="{{ color }}">
          {{ color }}
        </label>
      {% endfor %}
    </fieldset>
  </div>
  <div aria-live="polite" aria-atomic="true">
    {{ 'filters.results_count' | t: count: results.size }}
  </div>
</form>
html
<form class="facets">
  <div class="facets__group">
    <button
      type="button"
      aria-expanded="false"
      aria-controls="FilterColor-{{ section.id }}"
    >{{ 'filters.color' | t }}</button>
    <fieldset id="FilterColor-{{ section.id }}" hidden>
      <legend class="visually-hidden">{{ 'filters.filter_by_color' | t }}</legend>
      {% for color in colors %}
        <label>
          <input type="checkbox" name="filter.color" value="{{ color }}">
          {{ color }}
        </label>
      {% endfor %}
    </fieldset>
  </div>
  <div aria-live="polite" aria-atomic="true">
    {{ 'filters.results_count' | t: count: results.size }}
  </div>
</form>

Price Display

价格展示

html
{% if product.compare_at_price > product.price %}
  <div class="price" aria-label="{{ 'products.sale_price_label' | t: sale_price: product.price | money, original_price: product.compare_at_price | money }}">
    <s aria-hidden="true">{{ product.compare_at_price | money }}</s>
    <span>{{ product.price | money }}</span>
  </div>
{% else %}
  <div class="price">{{ product.price | money }}</div>
{% endif %}
  • Use
    aria-label
    to provide full price context (sale vs. original)
  • aria-hidden="true"
    on the visual strikethrough to avoid duplicate reading
html
{% if product.compare_at_price > product.price %}
  <div class="price" aria-label="{{ 'products.sale_price_label' | t: sale_price: product.price | money, original_price: product.compare_at_price | money }}">
    <s aria-hidden="true">{{ product.compare_at_price | money }}</s>
    <span>{{ product.price | money }}</span>
  </div>
{% else %}
  <div class="price">{{ product.price | money }}</div>
{% endif %}
  • 使用
    aria-label
    补充完整价格上下文(促销价vs原价)
  • 视觉删除线设置
    aria-hidden="true"
    避免重复朗读

Accordion

折叠面板

html
<details>
  <summary>{{ block.settings.heading }}</summary>
  <div class="accordion__content">
    {{ block.settings.content }}
  </div>
</details>
Native
<details>/<summary>
provides keyboard and screen reader support automatically.
html
<details>
  <summary>{{ block.settings.heading }}</summary>
  <div class="accordion__content">
    {{ block.settings.content }}
  </div>
</details>
原生
<details>/<summary>
自动支持键盘和屏幕阅读器。

Tabs

标签页

html
<div role="tablist" aria-label="{{ 'accessibility.product_tabs' | t }}">
  {% for tab in tabs %}
    <button
      role="tab"
      id="Tab-{{ tab.id }}"
      aria-selected="{% if forloop.first %}true{% else %}false{% endif %}"
      aria-controls="Panel-{{ tab.id }}"
      tabindex="{% if forloop.first %}0{% else %}-1{% endif %}"
    >{{ tab.title }}</button>
  {% endfor %}
</div>
{% for tab in tabs %}
  <div
    role="tabpanel"
    id="Panel-{{ tab.id }}"
    aria-labelledby="Tab-{{ tab.id }}"
    {% unless forloop.first %}hidden{% endunless %}
    tabindex="0"
  >{{ tab.content }}</div>
{% endfor %}
  • Arrow keys navigate between tabs (left/right)
  • Only active tab has
    tabindex="0"
    , others
    -1
html
<div role="tablist" aria-label="{{ 'accessibility.product_tabs' | t }}">
  {% for tab in tabs %}
    <button
      role="tab"
      id="Tab-{{ tab.id }}"
      aria-selected="{% if forloop.first %}true{% else %}false{% endif %}"
      aria-controls="Panel-{{ tab.id }}"
      tabindex="{% if forloop.first %}0{% else %}-1{% endif %}"
    >{{ tab.title }}</button>
  {% endfor %}
</div>
{% for tab in tabs %}
  <div
    role="tabpanel"
    id="Panel-{{ tab.id }}"
    aria-labelledby="Tab-{{ tab.id }}"
    {% unless forloop.first %}hidden{% endunless %}
    tabindex="0"
  >{{ tab.content }}</div>
{% endfor %}
  • 左右箭头键切换标签页
  • 仅活跃标签页设置
    tabindex="0"
    ,其他设置
    -1

Dropdown Navigation

下拉导航

html
<nav aria-label="{{ 'accessibility.main_navigation' | t }}">
  <ul role="list">
    {% for link in linklists.main-menu.links %}
      <li>
        {% if link.links.size > 0 %}
          <button aria-expanded="false" aria-controls="Submenu-{{ forloop.index }}">
            {{ link.title }}
          </button>
          <ul id="Submenu-{{ forloop.index }}" hidden role="list">
            {% for child in link.links %}
              <li><a href="{{ child.url }}">{{ child.title }}</a></li>
            {% endfor %}
          </ul>
        {% else %}
          <a href="{{ link.url }}">{{ link.title }}</a>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
</nav>
html
<nav aria-label="{{ 'accessibility.main_navigation' | t }}">
  <ul role="list">
    {% for link in linklists.main-menu.links %}
      <li>
        {% if link.links.size > 0 %}
          <button aria-expanded="false" aria-controls="Submenu-{{ forloop.index }}">
            {{ link.title }}
          </button>
          <ul id="Submenu-{{ forloop.index }}" hidden role="list">
            {% for child in link.links %}
              <li><a href="{{ child.url }}">{{ child.title }}</a></li>
            {% endfor %}
          </ul>
        {% else %}
          <a href="{{ link.url }}">{{ link.title }}</a>
        {% endif %}
      </li>
    {% endfor %}
  </ul>
</nav>

Tooltip

提示框

html
<button aria-describedby="Tooltip-{{ block.id }}">
  {{ 'labels.info' | t }}
</button>
<div id="Tooltip-{{ block.id }}" role="tooltip" popover>
  {{ block.settings.tooltip_text }}
</div>
html
<button aria-describedby="Tooltip-{{ block.id }}">
  {{ 'labels.info' | t }}
</button>
<div id="Tooltip-{{ block.id }}" role="tooltip" popover>
  {{ block.settings.tooltip_text }}
</div>

Mobile Accessibility

移动端无障碍设计

  • Touch targets: minimum 44x44px, 8px spacing between targets
  • No orientation lock: never restrict to portrait/landscape
  • No hover-only content: everything accessible via tap
  • Use
    dvh
    instead of
    vh
    for mobile viewport units
  • 触摸目标: 最小尺寸44x44px,目标间间距至少8px
  • 禁止锁定方向: 不要限制为仅竖屏或横屏
  • 无悬停专属内容: 所有内容均可通过点击访问
  • 移动端视口单位使用
    dvh
    替代
    vh

Animation & Motion

动画与运动

css
/* Always provide reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
  • No flashing above 3 times per second
  • Auto-playing animations need pause/stop controls
  • Meaningful animations only — don't animate for decoration
css
/* 始终提供减少动画的选项 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
  • 每秒闪烁次数不超过3次
  • 自动播放的动画需提供暂停/停止控件
  • 仅保留有意义的动画——不要为装饰而添加动画

Visually Hidden Utility

视觉隐藏工具类

css
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
Use for screen-reader-only content like labels and descriptions.
css
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
用于仅屏幕阅读器可见的内容,如标签和描述。

Color Contrast

颜色对比度

ElementMinimum Ratio
Normal text (<18px / <14px bold)4.5:1
Large text (≥18px / ≥14px bold)3:1
UI components & graphics3:1
Focus indicators3:1
Never rely solely on color to convey information — always pair with text, icons, or patterns.
元素最低对比度
普通文本(<18px / <14px粗体)4.5:1
大文本(≥18px / ≥14px粗体)3:1
UI组件与图形3:1
焦点指示器3:1
切勿仅依赖颜色传递信息——始终搭配文本、图标或图案。

References

参考资料

  • Component accessibility patterns
  • Focus and keyboard patterns
  • 组件无障碍模式
  • 焦点与键盘模式