Token导航 LogoToken导航TokenDH.com
前端设计只读github未标认证来源可访问许可证需确认审计通过

liquid-theme-a11yliquid 主题 a11y

Agent Skill

用于辅助无障碍访问检查、页面可用性审计和前端可访问性改进。它适合让 Agent 检查语义标签、键盘操作、颜色对比、ARIA 属性和自动化检测结果。使用时需要结合真实页面和浏览器验证,不应只依赖静态文本判断;涉及修复建议时,应兼顾设计系统、组件复用和 WCAG 等通用无障碍规范。

总安装

35,280

周安装

1,527

GitHub Stars

106

下载量

12,360
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:liquid-theme-a11y(liquid 主题 a11y)
来源仓库:https://github.com/benjaminsehl/liquid-skills
仓库路径:skills/liquid-theme-a11y
安装命令:
npx skills add https://github.com/benjaminsehl/liquid-skills --skill liquid-theme-a11y
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/benjaminsehl/liquid-skills --skill liquid-theme-a11y

简介

Shopify Liquid 主题组件的 WCAG 2.2 可访问性模式。

  • 涵盖 11 个电子商务特定组件:产品卡、轮播、模态、表单、过滤器、选项卡、下拉菜单、工具提示、购物车抽屉、价格以及具有语义 HTML 和 ARIA 模式的手风琴
  • 包括用于纠正 HTML 元素和 ARIA 角色的决策表映射组件,以及每个模式的完整代码示例
  • 在所有交互元素中强制执行键盘导航、屏幕阅读器支持、带陷印的焦点管理以及减少运动首选项
  • 需要最小 44x44px 触摸目标、UI 组件对比度为 3:1、跳过链接、每页单个 h1 以及用于动态内容更新的实时区域

SKILL.md

Accessibility for Shopify Liquid Themes

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.

Decision Table: Which Pattern?

ComponentHTML ElementARIA PatternReference
Expandable content<details>/<summary>None neededAccordion
Modal/dialog<dialog>aria-modal="true"Modal
Tooltip/popup[popover] attributerole="tooltip" fallbackTooltip
Dropdown menu<nav> + <ul>aria-expanded on triggersNavigation
Tab interface<div>role="tablist/tab/tabpanel"Tabs
Carousel/slider<div>role="region" + aria-roledescriptionCarousel
Product card<article>aria-labelledbyProduct card
Form<form>aria-invalid, aria-describedbyForms
Cart drawer<dialog>Focus trapCart drawer
Price display<span>aria-label for contextPrices
Filters<form> + <fieldset>aria-expanded for disclosuresFilters

Page Structure

Landmarks

<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

Skip Link

.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

Focus Management

Focus Indicators

/* 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

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.

Component Patterns

Product Card

<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

Carousel

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

Modal

<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

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

Forms

<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

Product Filters

<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

{% 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

Accordion

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

Tabs

<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

Dropdown Navigation

<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

<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

Animation & Motion

/* 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

Visually Hidden Utility

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

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.

References

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude

31.61%
按下载量换算3,907

Codex

31.28%
按下载量换算3,866

Cursor

19.91%
按下载量换算2,461

Gemini CLI

8.94%
按下载量换算1,105

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills