
Shopify Form Accessibility: Labels, Errors, Autocomplete in Liquid
Forms are a high-impact accessibility review area because unclear labels, unidentified purposes, visual-only errors, and missing recovery guidance can block signup or purchase tasks. The examples below illustrate useful Liquid patterns, but they are not a claim that every theme has the same failure or that one snippet fixes every form state. Test the rendered form, validation behavior, keyboard flow, and screen-reader announcements on the actual storefront.
Why "placeholder only" is not enough
The most common Shopify form failure:
{%- comment -%} Wrong — placeholder used as label {%- endcomment -%}
<form action="/contact#newsletter" method="post">
<input type="hidden" name="form_type" value="customer">
<input type="email" name="contact[email]" placeholder="Email">
<button type="submit">Subscribe</button>
</form>
Three problems:
- No programmatic label. Screen readers announce the input as "edit text" with no field-name context.
- No
autocomplete. Browser autofill cannot pre-populate the email field. - Placeholder vanishes when the user types, so users with cognitive disabilities who lose track of what field they're in have no recovery cue.
The right pattern — visible label + autocomplete + label-input association
<form action="/contact#newsletter" method="post" class="newsletter-form">
<input type="hidden" name="form_type" value="customer">
<input type="hidden" name="contact[tags]" value="newsletter">
<label for="newsletter-email">Email</label>
<input
type="email"
id="newsletter-email"
name="contact[email]"
autocomplete="email"
required
aria-describedby="newsletter-email-help">
<p id="newsletter-email-help" class="form-help">
We\'ll email occasional accessibility-compliance updates. Unsubscribe anytime.
</p>
<button type="submit">Subscribe</button>
</form>
Key elements:
<label for="newsletter-email">paired withid="newsletter-email"on the input — the canonical programmatic association.autocomplete="email"— declares the input purpose for autofill + accessibility extensions.aria-describedby="newsletter-email-help"— connects helper text to the input so screen readers read it after the field name.- The label is visible, not hidden in
.sr-only— visible labels help everyone, not just screen-reader users.
If brand design genuinely requires the label to be hidden (a single-field newsletter where space is constrained), use .sr-only rather than removing the label entirely:
<label for="newsletter-email" class="sr-only">Email</label>
<input
type="email"
id="newsletter-email"
name="contact[email]"
placeholder="Email"
autocomplete="email"
required>
The placeholder is now a visual fallback for sighted users; the screen-reader user gets the proper label.
The autocomplete value table — what to use where
WCAG 1.3.5 references the HTML Living Standard's autocomplete value list. The values you'll use most on Shopify forms:
| Field | autocomplete value |
|---|---|
email | |
| Phone | tel |
| Phone (country code) | tel-country-code |
| Full name | name |
| First name | given-name |
| Last name | family-name |
| Street address line 1 | address-line1 |
| Street address line 2 | address-line2 |
| City | address-level2 |
| State / region | address-level1 |
| Postal / ZIP code | postal-code |
| Country | country |
| Country code | country-name |
| Birthday | bday |
| New password | new-password |
| Existing password | current-password |
| One-time code | one-time-code |
| Credit card number | cc-number |
| Credit card expiry month | cc-exp-month |
| Credit card expiry year | cc-exp-year |
| Credit card security code | cc-csc |
Shopify's native checkout sets these correctly. Custom storefront forms (Hydrogen, custom signup, third-party app embeds) often miss them — audit every form on the storefront, not just the native checkout.
Glossary: HTML autocomplete attribute →
Error identification — the right way
Setup: a customer signs up with an existing email. The form should surface the error so screen-reader users know what failed.
The wrong way — color-only:
{%- if form.errors -%}
<input type="email" name="contact[email]" class="error-red-border">
{%- endif -%}
Red border tells sighted users something is wrong. Tells screen-reader users nothing.
The right way — aria-invalid + aria-describedby + role="alert":
{%- assign has_error = form.errors and form.errors.email -%}
<label for="signup-email">Email</label>
<input
type="email"
id="signup-email"
name="customer[email]"
autocomplete="email"
required
{% if has_error %}aria-invalid="true" aria-describedby="signup-email-error"{% endif %}>
{%- if has_error -%}
<p id="signup-email-error" role="alert" class="form-error">
{{ form.errors.translated_fields.email }} {{ form.errors.messages.email }}.
Try a different email or sign in if you already have an account.
</p>
{%- endif -%}
Three things happen:
aria-invalid="true"flags the field as invalid for screen readers.aria-describedbyconnects the input to the error message so the message is announced after the field name.role="alert"tells the screen reader to announce the error immediately when it appears (implicitaria-live="assertive").
The error text itself satisfies WCAG 3.3.3 Error Suggestion — it explains what went wrong AND how to fix it.
Common Shopify form-accessibility failures
1. Newsletter signup with placeholder-only label
Cause: theme <input placeholder="Email"> with no label. Fix: add <label for> + matching id + autocomplete="email".
2. Search bar with no accessible name
Cause: theme renders <input type="search"> with no label or aria-label. Fix: <label for="search-input" class="sr-only">Search</label> or aria-label="Search".
3. Quantity stepper with no label
Cause: cart line-item quantity input is <input type="number"> with no label. Fix: <label for="qty-{{ line.key }}" class="sr-only">Quantity</label> so screen readers know which line item the field controls.
4. Checkout custom field added via cart-attribute script
Cause: merchant adds gift-message or order-note input via Liquid; ships without label. Fix: every custom cart-attribute or line-item-property field needs <label> + autocomplete (where applicable).
5. Customer-account login that blocks paste
Cause: theme has onpaste="return false" on the password field (often inherited from a theme-customization tutorial). Fix: remove onpaste and autocomplete="off"; the password field should accept paste so password managers work. WCAG 3.3.8 Accessible Authentication explicit requirement at WCAG 2.2 AA.
6. Form errors flagged by color only
Cause: stylesheet adds border-color: red on .error class but no text content. Fix: add the aria-invalid + aria-describedby + role="alert" pattern above.
The full Liquid newsletter pattern
{%- form 'customer', id: 'newsletter-form', class: 'newsletter-form' -%}
<input type="hidden" name="contact[tags]" value="newsletter">
<label for="newsletter-email">{{ 'general.newsletter.email_label' | t }}</label>
<div class="newsletter-form__input-wrap">
<input
type="email"
id="newsletter-email"
name="contact[email]"
autocomplete="email"
placeholder="{{ 'general.newsletter.email_placeholder' | t }}"
required
aria-required="true"
{% if form.errors %}aria-invalid="true" aria-describedby="newsletter-email-error"{% endif %}>
<button type="submit">
{{ 'general.newsletter.submit' | t }}
</button>
</div>
{%- if form.errors -%}
<p id="newsletter-email-error" role="alert" class="newsletter-form__error">
{{- form.errors | default_errors -}}
</p>
{%- elsif form.posted_successfully? -%}
<p role="status" class="newsletter-form__success">
{{ 'general.newsletter.success' | t }}
</p>
{%- endif -%}
{%- endform -%}
The role="status" on the success message is the WCAG 4.1.3 Status Messages compliance for the success path.
Quick checklist
- Every
<input>,<select>,<textarea>has a<label for>+ matchingid. - Every user-info field has a correct
autocompletevalue. - Required fields have both
required+aria-required="true". - Error messages use
aria-invalid="true"on the input +aria-describedbylinking to the error +role="alert". - Error messages explain what went wrong AND how to fix it.
- Success messages use
role="status"so screen readers announce them. - Password fields do not block paste (
onpaste="return false"removed). - Search inputs have
<label class="sr-only">oraria-label. - Cart-attribute / line-item-property custom fields have labels.
Verify your fix
Run the free public AccessComply scan on the storefront. It checks up to 10 discoverable pages, subject to crawler safety and time limits, and reports detected form-label findings with selectors and WCAG mappings. Public scans require no signup and are limited to 3 per day per store. The installed Free app separately includes 3 app scans per calendar month and 3 eligible deterministic fixes per rolling 30 days.
Further reading
- WCAG 3.3.2 Labels or Instructions deep-dive
- WCAG 1.3.5 Identify Input Purpose deep-dive
- WCAG 3.3.1 Error Identification deep-dive
- WCAG 3.3.3 Error Suggestion deep-dive
- WCAG 4.1.3 Status Messages deep-dive
- Glossary — HTML autocomplete attribute
- Glossary — aria-live region
- HTML Living Standard — autocomplete values
- Shopify accessibility complete guide
- Shopify checkout accessibility guide
Find the storefront issues holding back growth
Scan SEO, speed, and accessibility by page. Review supported fixes before they run, keep saved originals, and verify the live result afterward.