
Liquid Theme A11y
Apply correct ARIA and semantic HTML for Shopify Liquid storefront components like color swatches, breadcrumbs, and product tables.
Install
npx skills add https://github.com/benjaminsehl/liquid-skills --skill liquid-theme-a11yWhat is this skill?
- Color swatch fieldset pattern with role="radio" and aria-checked for single-select options
- Breadcrumb nav with ordered list semantics and aria-current="page" on the active item
- Accessible product tables using caption, thead, and th scope="col" for size charts and specs
- Requires text labels (including visually-hidden) so choices are not color-only
- Extends base liquid-theme-a11y SKILL.md with copy-paste HTML examples
Adoption & trust: 1.7k installs on skills.sh; 108 GitHub stars; 3/3 security scanners passed (skills.sh audits).
Recommended Skills
Frontend Designanthropics/skills
Vercel React Best Practicesvercel-labs/agent-skills
Remotion Best Practicesremotion-dev/skills
Vercel Composition Patternsvercel-labs/agent-skills
Develop Userscriptsxixu-me/skills
Next Best Practicesvercel-labs/next-skills
Journey fit
Common Questions / FAQ
Is Liquid Theme A11y safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Liquid Theme A11y
# Component Accessibility Patterns Detailed ARIA patterns for e-commerce components beyond what's covered in SKILL.md. ## Color Swatches ```html <fieldset> <legend>{{ 'products.color' | t }}</legend> {% for color in product.options_by_name['Color'].values %} <button type="button" role="radio" aria-checked="{% if color == selected_color %}true{% else %}false{% endif %}" aria-label="{{ color }}" style="background-color: {{ color | handleize }}" class="color-swatch" > <span class="visually-hidden">{{ color }}</span> </button> {% endfor %} </fieldset> ``` - Use `role="radio"` with `aria-checked` for single-select swatches - Always provide text label (visually hidden if needed) - Never rely on color alone — include text or pattern ## Breadcrumbs ```html <nav aria-label="{{ 'accessibility.breadcrumb' | t }}"> <ol role="list"> <li><a href="/">{{ 'general.home' | t }}</a></li> <li><a href="{{ collection.url }}">{{ collection.title }}</a></li> <li aria-current="page">{{ product.title }}</li> </ol> </nav> ``` - `aria-current="page"` on the current page (last item, no link) - Use `<ol>` for ordered list semantics ## Tables (Size Charts, Specs) ```html <table> <caption class="visually-hidden">{{ 'products.size_chart' | t }}</caption> <thead> <tr> <th scope="col">{{ 'products.size' | t }}</th> <th scope="col">{{ 'products.chest' | t }}</th> <th scope="col">{{ 'products.waist' | t }}</th> </tr> </thead> <tbody> {% for row in size_data %} <tr> <th scope="row">{{ row.size }}</th> <td>{{ row.chest }}</td> <td>{{ row.waist }}</td> </tr> {% endfor %} </tbody> </table> ``` - Always use `<th scope="col|row">` for header cells - `<caption>` describes the table purpose - Wrap in scrollable container for mobile: `<div role="region" tabindex="0" aria-label="...">` ## Slider / Range Input ```html <div role="group" aria-label="{{ 'filters.price_range' | t }}"> <label for="PriceMin-{{ section.id }}">{{ 'filters.min_price' | t }}</label> <input type="range" id="PriceMin-{{ section.id }}" min="0" max="{{ max_price }}" value="{{ min_value }}" aria-valuemin="0" aria-valuemax="{{ max_price }}" aria-valuenow="{{ min_value }}" aria-valuetext="{{ min_value | money }}" > <label for="PriceMax-{{ section.id }}">{{ 'filters.max_price' | t }}</label> <input type="range" id="PriceMax-{{ section.id }}" min="0" max="{{ max_price }}" value="{{ max_value }}" aria-valuetext="{{ max_value | money }}" > </div> ``` - `aria-valuetext` provides human-readable value (e.g., "$25.00" instead of "2500") ## Switch / Toggle ```html <button role="switch" aria-checked="false" aria-label="{{ 'settings.dark_mode' | t }}" > <span class="switch__thumb"></span> </button> ``` - `role="switch"` with `aria-checked="true|false"` - Toggle with Space or Enter key ## Combobox / Autocomplete ```html <div class="combobox"> <label for="Search-{{ section.id }}">{{ 'search.label' | t }}</label> <input type="text" id="Search-{{ section.id }}" role="combobox" aria-expanded="false" aria-autocomplete="list" aria-controls="SearchResults-{{ section.id }}" aria-activedescendant="" > <ul id="SearchResults-{{ section.id }}" role="listbox" hidden> <!-- Suggestions populated via JS --> </ul> </div> ``` - Arrow keys navigate suggestions, update `aria-activedescendant` - Escape clears, Enter selects - `aria-expanded` reflects listbox visibility ## Disclosure (Show/Hide) ```html <button type="button" aria-expanded="false" aria-controls="Content-{{ block.id }}" > {{ block.settings.label }} </button> <div id="Content-{{ block.id }}" hidden> {{ block.settings.content }} </div> ``` Simple show/hide toggle. If the content is a list of items (like a menu), prefer `<details>/<summary>`. ## Product Media Gallery ```h