Back to Blog
Shopify Form Accessibility: Labels, Errors, Autocomplete in Liquid — featured image

Shopify Form Accessibility: Labels, Errors, Autocomplete in Liquid

Vijaygopal Balasa
9 min read

Form-label failures are the third-most-cited violation in ADA Title III digital-accessibility demand letters, after missing alt text and color contrast. Most Shopify themes ship with at least one — a newsletter signup with a placeholder-only "Email" field, a checkout that omits autocomplete attributes, an error message that turns the input red without saying what went wrong. This guide covers the four form-related WCAG criteria + the exact Liquid pattern that fixes each on Shopify's native templates.

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:

  1. No programmatic label. Screen readers announce the input as "edit text" with no field-name context.
  2. No autocomplete. Browser autofill cannot pre-populate the email field.
  3. 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 with id="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:

Fieldautocomplete value
Emailemail
Phonetel
Phone (country code)tel-country-code
Full namename
First namegiven-name
Last namefamily-name
Street address line 1address-line1
Street address line 2address-line2
Cityaddress-level2
State / regionaddress-level1
Postal / ZIP codepostal-code
Countrycountry
Country codecountry-name
Birthdaybday
New passwordnew-password
Existing passwordcurrent-password
One-time codeone-time-code
Credit card numbercc-number
Credit card expiry monthcc-exp-month
Credit card expiry yearcc-exp-year
Credit card security codecc-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:

  1. aria-invalid="true" flags the field as invalid for screen readers.
  2. aria-describedby connects the input to the error message so the message is announced after the field name.
  3. role="alert" tells the screen reader to announce the error immediately when it appears (implicit aria-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> + matching id.
  • Every user-info field has a correct autocomplete value.
  • Required fields have both required + aria-required="true".
  • Error messages use aria-invalid="true" on the input + aria-describedby linking 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"> or aria-label.
  • Cart-attribute / line-item-property custom fields have labels.

Verify your fix

Run the free AccessComply scan on the storefront. The scan reports per-form failures with the exact selector + WCAG criterion. Free tier includes 3 scans per month with no signup.

Further reading

Free to install

Scan your store free, fix violations at the source

AccessComply scans your Shopify store for ADA + EAA / WCAG 2.1 + 2.2 AA violations and applies real source-code fixes — no overlays, no widgets.

Vijaygopal Balasa, Founder, AccessComply
Written by

Vijaygopal Balasa

Founder, AccessComply

Founder of AccessComply. Builds AI agents that fix Shopify accessibility violations at the source-code level — not via overlays. Focused on real WCAG 2.2 AA outcomes for merchants.

More on Shopify How-To

See all →