Skip to content
Log in

Theme SEO and accessibility

On this page

Building a theme that ranks well in search engines and works for all users is not optional — it is part of shipping quality work. This article covers StoreConnect’s built-in SEO features, responsive image best practices, and the WCAG 2.1 Level AA accessibility requirements that apply to storefront themes.

SEO

The meta_data snippet

Include {% render "meta_data" %} in your layout’s <head>. This single snippet auto-generates the title, meta description, meta keywords, Open Graph tags, and a canonical URL for every page type.

```liquid

... {% render "meta_data" %}

```

When meta_data is included, do not manually add a <title> tag — the snippet provides it.

What it generates:

Tag Source
<title> The title variable from the platform (auto-generated per page type)
<meta name="description"> The meta_description variable
<meta name="keywords"> The meta_keywords variable
<link rel="canonical"> current_request.canonical_url (auto-normalized)
<meta property="og:type"> Auto-detected: "website", "article", or "page"
<meta property="og:title"> Same as <title>
<meta property="og:description"> Same as meta description
<meta property="og:url"> Canonical URL
<meta property="og:locale"> current_store.locale
<meta property="og:image"> Social image or primary image
<meta property="og:price:amount"> Product price (product pages only)
<meta property="og:price:currency"> Store currency (product pages only)

On paginated pages, the title automatically appends “ - Page N”.

SEO properties on drops

Most content drops expose SEO fields that merchants can set in the CMS:

Drop SEO properties
ProductDrop meta_title, meta_description, meta_keywords, search_description, social_image, url, condition, upc
ArticleDrop meta_title, meta_description, meta_keywords, search_keywords, canonical_url
ProductCategoryDrop meta_title, meta_description, meta_keywords, social_image, google_product_category
ArticleCategoryDrop meta_title, meta_description, meta_keywords, social_image
PageDrop meta_title, meta_description
StoreDrop meta_title, meta_description, meta_keywords, social_image

Meta description defaults

CMS meta description fields are often blank. Override the meta_data snippet to auto-generate descriptions from content fields when the CMS field is empty:

```liquid

{%- if meta_description == blank -%} {%- if current_product != blank -%} {%- assign meta_description = current_product.search_description | default: current_product.summary_content | strip_html | truncate: 160 -%} {%- elsif current_product_category != blank -%} {%- assign meta_description = current_product_category.introduction_content | strip_html | truncate: 160 -%} {%- elsif current_article != blank -%} {%- assign meta_description = current_article.summary_content | default: current_article.introduction_content | strip_html | truncate: 160 -%} {%- elsif current_page != blank -%} {%- assign meta_description = current_page.body_content | strip_html | truncate: 160 -%} {%- endif -%} {%- if meta_description == blank -%} {%- assign meta_description = current_store.meta_description -%} {%- endif -%} {%- endif -%} ```

This ensures every page has a meta description — Lighthouse flags missing descriptions as a critical SEO issue.

Canonical URLs

The platform auto-generates canonical URLs via current_request.canonical_url. These are normalized to remove tracking parameters, normalize casing and trailing slashes, and include the full domain. The meta_data snippet renders the <link rel="canonical"> tag automatically.

Structured data (JSON-LD)

The breadcrumbs snippet renders both visible breadcrumb HTML and a BreadcrumbList JSON-LD script:

```liquid

{% render “breadcrumbs” %} ```

Include this on product, category, article, and content pages.

Product rich data

The products/product/rich_data_json snippet generates Product or ProductGroup JSON-LD markup:

```liquid

{% render “products/product/rich_data_json”, product: current_product %} ```

Include this on the product page. It includes name, SKU, GTIN, brand, description, images, price range, availability, condition, and URL.

Sitemaps and robots.txt

The platform auto-generates /sitemap.xml and /robots.txt. These require no theme configuration. Custom content pages are automatically included in the sitemap.

Merchant feeds

Automated product feeds are available at /merchant_feeds/google.xml and /merchant_feeds/facebook.xml. These are generated from product data with no theme work required.

Twitter/X cards

Twitter card tags are not auto-generated. Add them to your layout if needed:

```liquid

{% if current_product != blank %} {% assign tw_image = current_product.social_image | default: current_product.image %} {% elsif current_article != blank %} {% assign tw_image = current_article.social_image | default: current_article.hero_image %} {% else %} {% assign tw_image = current_store.social_image | default: current_store.logo %} {% endif %} {% if tw_image != blank %}

{% endif %} ```

SEO checklist

  • [ ] {% render "meta_data" %} in layout <head>
  • [ ] Meta description defaults added for when CMS fields are blank
  • [ ] {% render "breadcrumbs" %} on product, category, article, and content pages
  • [ ] {% render "products/product/rich_data_json", product: current_product %} on product page
  • [ ] <html lang="{{ current_store.locale }}"> on the <html> element
  • [ ] One <h1> per page
  • [ ] Logical heading hierarchy (h1 > h2 > h3, no skipped levels)
  • [ ] Descriptive alt text on all images with fallback: alt="{{ image.alt_text | default: product.name }}"
  • [ ] Store-level meta_description set in the CMS (used as final fallback)

Responsive images

Google uses mobile-first indexing — your site’s mobile experience determines its search ranking. Always use srcset and sizes on content images rather than a fixed src alone.

Image sizes available

The platform generates images at nine named sizes:

Size Max dimensions Property Typical use
pico 16×16 pico_url LQIP placeholders
icon 32×32 icon_url Tiny icons
tiny 50×50 tiny_url Cart line item thumbnails
small 100×100 small_url Thumbnails
thumb 240×240 thumb_url Product cards (mobile)
medium 480×480 medium_url Product cards (desktop)
large 640×640 large_url Product detail images
huge 1024×1024 huge_url Hero images
massive 2048×2048 massive_url Retina hero images

Responsive image pattern

```liquid

{{ image.alt_text | default: product.name }} ```

Loading strategy

Location Strategy Attributes
Hero / above the fold Eager load, high priority fetchpriority="high" (no loading="lazy")
Product cards in grid Lazy load loading="lazy" decoding="async"
Below-the-fold content Lazy load loading="lazy" decoding="async"

Accessibility

Target: WCAG 2.1 Level AA conformance. This means all content is perceivable, operable, understandable, and robust for users with disabilities — including those using screen readers, keyboard navigation, and assistive technologies.

Viewport meta tag

The layout must include:

```html

```

Never use maximum-scale=1 or user-scalable=no — these prevent pinch-to-zoom, which is a WCAG 1.4.4 violation.

Add a “skip to main content” link as the first focusable element after {{ theme_bar }}:

```liquid

{{ theme_bar }} {% render "header" %}
...

```

Required CSS (the base theme does not include this — add it to your theme):

```css

.skip-link { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }

.skip-link:focus { position: fixed; top: 0; left: 0; width: auto; height: auto; padding: 0.75rem 1.5rem; margin: 0; overflow: visible; clip: auto; white-space: normal; background: #fff; color: #000; z-index: 99999; font-size: 1rem; text-decoration: underline; } ```

Semantic HTML

Use correct HTML elements for their purpose:

  • <header> — site header, once per page
  • <nav> — navigation regions, with aria-label to distinguish multiple navs
  • <main id="SC-Main"> — primary content, once per page
  • <footer> — site footer
  • <section> — thematic grouping with a heading

Heading hierarchy

  • One <h1> per page (the main content title).
  • Use heading levels sequentially — never skip from <h1> to <h3>.
  • Footer headings should use <h2> with a CSS class for visual sizing, not <h4>. Lighthouse flags level skips.

Image alt text

Every <img> must have an alt attribute:

  • Content images — describe what is shown: alt="{{ image.alt_text | default: product.name }}"
  • Decorative images — use empty alt: alt=""
  • Decorative SVG icons — use aria-hidden="true" focusable="false"

Form accessibility

```liquid

<input type=”email” id=”email” name=”{{ form.email.name }}” value=”{{ form.email.value }}” {% if form.errors.size > 0 %} aria-invalid=”true” aria-describedby=”email-error” {% endif %} required autocomplete=”email”> {% if form.errors.size > 0 %} {{ form.errors.first }} {% endif %} ```

Accessible navigation

```liquid

```

Live regions

Use aria-live for content that updates dynamically without a page reload:

```liquid

{%- comment -%} Cart count badge {%- endcomment -%} {{ current_cart.item_count }} {{ “cart.items” | t }}

{%- comment -%} Flash messages {%- endcomment -%}

{{ current_flash.notice }}

```

Color contrast

WCAG AA requires: - 4.5:1 contrast ratio for normal text - 3:1 contrast ratio for large text (18px+ or 14px+ bold) - 3:1 for UI components and graphical objects

Create a CSS variable for brand colors on dark surfaces:

```css

:root { –color-primary-on-dark: #E0944A; /* lighter tone that passes 4.5:1 on dark backgrounds */ } ```

Icon-only buttons

Every icon-only button or link must have an accessible label:

```liquid

</button> ```

Keyboard navigation

  • All interactive elements must be keyboard-focusable.
  • Focus indicators must be visible — add :focus-visible styles.
  • Dropdowns and modals must close on Escape and return focus to the trigger element.
  • Use tabindex only when necessary; never use values greater than 0.

Common pitfalls from real theme audits

These recurring issues are found across StoreConnect themes via Lighthouse audits:

Images without alt text. Always include alt with a fallback: alt="{{ image.alt_text | default: product.name }}". The alt_text property comes from the CMS media record — if unset, fall back to the parent object’s name.

Heading hierarchy violations. Footer headings are commonly <h4>, which skips levels from the page’s <h1>. Use <h2> with a CSS class for visual sizing instead. Never choose heading level based on desired font size — use CSS for that.

SVG icons missing aria-hidden. Decorative SVG icons (hamburger, close, chevron, search, cart) must have aria-hidden="true" focusable="false". If the icon is inside a button with aria-label, the icon is decorative.

Flash messages not announced. The flash/notice snippet needs role="status" on notices and role="alert" on alerts for screen readers to announce them.

Links with no discernible name. Icon-only links (cart, social media) that hide their text on mobile need aria-label so the mobile experience has an accessible name.

Invalid HTML in menus. <ul> elements must only contain <li> children. Close buttons or logos inside a <ul> wrapped in a <div> produce invalid HTML. Wrap them in <li> elements instead.

meta_data snippet limitations. CMS meta fields are often blank. Override the meta_data snippet to generate fallback descriptions from content fields — this single change fixes the most common Lighthouse SEO failure.

Lighthouse with theme preview. Testing via ?theme-preview= adds latency from the redirect and injects a preview bar with its own accessibility issues. These are platform artifacts, not theme issues. Account for them when interpreting scores.

Accessibility checklist

  • [ ] <html lang="{{ current_store.locale }}"> present
  • [ ] Skip-to-content link as first focusable element (with visually-hidden CSS)
  • [ ] One <h1> per page, logical heading hierarchy (footer uses <h2> not <h4>)
  • [ ] All images have alt attributes with fallback (alt_text | default: name)
  • [ ] Decorative SVGs have aria-hidden="true" focusable="false"
  • [ ] All form inputs have associated <label> elements
  • [ ] Flash messages use role="status" (notices) and role="alert" (alerts)
  • [ ] Icon-only buttons and links have aria-label
  • [ ] Dropdowns and accordions use aria-expanded
  • [ ] Modals use role="dialog" with aria-modal="true" and aria-labelledby
  • [ ] Dynamic content updates use aria-live
  • [ ] All interactive elements are keyboard accessible
  • [ ] Visible focus indicators on all focusable elements
  • [ ] Color contrast meets WCAG AA (4.5:1 normal text, 3:1 large text)
  • [ ] Dark-surface link colors use an accessible variant of the brand color
  • [ ] Menu <ul> elements contain only <li> children

Was this article helpful?

Was this article helpful?