Appearance
Footer support guide
Diagnostic and troubleshooting reference for the footer surface — auto-emitted policy row, newsletter form, payment-icon aria-labels, block library, localization form, trust copy, follow-on-shop toggle, and the .footer__copyright color-mix migration. For merchant setup and configuration, see Footer guide.
FAQ
Q1: Why doesn't my newsletter show up in the footer?
Newsletter and social links are blocks — not section toggles. To show the newsletter: go to Theme editor → Sections → Footer → Add block → Newsletter. To show social links: Add block → Social links. Payment icons are not a block — they render automatically from the payment types enabled on your store (Shopify admin → Settings → Payments). If no payment types are enabled, the icon row is omitted.
Fix:
- Theme editor → Sections → Footer
- Click
Add block - Select
Newsletter - Save
If the block is already present but the newsletter still doesn't render:
- Run the console snippet below — inspect
newsletterin thefooterrow. Should betrueif acustomer-formelement is present inside.footer__newsletter-block. - Inspect
document.querySelector('.footer__newsletter-block customer-form')in DevTools — should return the form element. - Confirm
assets/customer-form.jsis loading: DevTools → Network → filtercustomer-form— should return 200.
Q2: How do I add a custom link to the footer policies?
Footer policies are auto-emitted from Shopify's named policy accessors (shop.privacy_policy.url, shop.refund_policy.url, and so on) — you add them in Shopify admin, not the theme editor. Only policies that are published (their URL resolves) render a link.
To add a policy:
- Shopify admin → Settings → Policies
- Click the policy type you want to add (Privacy policy, Refund policy, etc.)
- Write or paste your policy text
- Save — the footer renders the link automatically
Policies rendered: Privacy policy, Refund policy, Shipping policy, Terms of service, Subscription policy — exactly these five. Contact information is not part of the policy row.
You cannot add arbitrary custom links to the policy row. The policy row is reserved for Shopify-native policies. To add custom links, add a link_column block to the footer instead.
Q3: Newsletter form submits but the subscription doesn't seem to work
The newsletter form is deliberately not consent-gated: pressing Subscribe IS the marketing opt-in. A hidden contact[accepts_marketing]=true field rides every submission, and the "Unsubscribe anytime." line under the field carries the assurance. No cookie-banner acceptance is required, and the theme shows no "accept marketing cookies" message.
The real failure modes:
Invalid or empty email. Validation runs before submit — an empty or malformed address shows an inline field error and the form does not POST. Correct the address and resubmit.
Network failure. On the footer (and password-page) surface the form submits via fetch. If the request cannot complete, the toast shows "Subscription failed. Please try again." Check DevTools → Network for the POST to the form action (/contact) — a failed or blocked request there is the cause (offline, content blocker, corporate proxy).
Already subscribed. Shopify treats a repeat subscription as success — the visitor sees "Thank you for subscribing!" and no duplicate customer is created. Verify the address's marketing status under Shopify admin → Customers.
Diagnostics:
- Run the console snippet →
footer.newsletter— confirms the form markup is present - DevTools → Network → filter
customer-form—assets/customer-form.jsshould return 200 (it powers validation and the fetch submit) - Submit with a valid email and watch the Network tab for the
/contactPOST — on success the thank-you toast replaces the form body
Q4: How do I customize per-preset footer block compositions?
Every preset ships the same footer block sequence in its section group (listings/<preset>/sections/footer-group.json) — a text block, three link columns, newsletter, social and legal links — with Uisce adding one extra text block. What differs between presets is the content in those blocks, not the composition. To override:
- Theme editor → Sections → Footer
- Rearrange, add, or remove blocks using the block controls
- Blocks in the theme editor override the shipped footer-group defaults for that preset
For programmatic changes: Edit listings/<preset>/sections/footer-group.json directly. Then push via bash scripts/sync-theme-safe.sh <Preset> — the safe default; it preserves live settings and deletes nothing. scripts/push-presets.sh <Preset> is the destructive full-reset path (overwrites live settings with committed defaults) — use it only per docs/deploy/RUNBOOK.md.
Q5: .footer__copyright text is barely visible
This was a known defect (carry-over from axe-core reports in Phases 06.4, 06.5, and 06.6). It was fixed in Phase 06.9 via the color-mix pattern:
css
color: color-mix(in srgb, var(--color-foreground) 70%, var(--color-background));If it still appears low-contrast after upgrading:
- Confirm the fix is present in your theme copy: DevTools → Network (or Sources) →
footer.css— search forcolor-mix; the rule should appear under.footer__copyright - Run the console snippet → inspect
footer.present— should betrue - DevTools → Elements → inspect
.footer__copyright→ Computed → color — should be the 70% foreground value - If the color is still the old value, perform a hard refresh (Ctrl+Shift+R) to clear the CDN-cached
footer.css
If the contrast fails on a specific color scheme, file an issue with the footer's scheme class from DevTools — the color-scheme--* class on the <footer> element. Color schemes are per-section classes; the <body> carries only the preset-* class.
Console diagnostic snippet
Paste into Chrome DevTools console (F12 → Console tab) on any page of the live store. Reports footer state always; also reports 404, password, or gift-card state when on those surfaces.
js
(function () {
var path = location.pathname;
var report = { path: path, surfaces: {} };
// Footer always
var footer = document.querySelector('.footer');
report.surfaces.footer = {
present: !!footer,
policies: !!document.querySelector('.footer__policies'),
localization: !!document.querySelector('.footer__localization'),
newsletter: !!document.querySelector('.footer__newsletter-block customer-form'),
payment_icons_aria: Array.from(document.querySelectorAll('.footer__payment span[aria-label]')).length,
};
// 404
if (document.querySelector('.section-404')) {
report.surfaces['404'] = {
hero: !!document.querySelector('.section-404__hero svg'),
search: !!document.querySelector('.section-404__search'),
recently_viewed: !!document.querySelector('recently-viewed'),
noindex: document.querySelector('meta[name="robots"]')
? document.querySelector('meta[name="robots"]').content
: null,
};
}
// Password
if (document.querySelector('.password-page')) {
report.surfaces.password = {
logo: !!document.querySelector('.password-page__logo img'),
hero: !!document.querySelector('.password-page__hero svg'),
countdown: !!document.querySelector('count-down'),
newsletter: !!document.querySelector('.password-page__newsletter customer-form'),
social: !!document.querySelector('.password-page__social'),
localization: !!document.querySelector('.password-page__localization'),
};
}
// Gift-card
if (document.querySelector('.gift-card-page')) {
report.surfaces.gift_card = {
qr: !!document.querySelector('gift-card-qr canvas'),
copy: !!document.querySelector('gift-card-copy'),
balance: document.querySelector('.gift-card-page__balance')
? document.querySelector('.gift-card-page__balance').textContent.trim()
: null,
state:
['active', 'expired', 'depleted'].filter(function (s) {
return !!document.querySelector('.gift-card-page--state-' + s);
})[0] || 'active',
apple_wallet: !!document.querySelector('.gift-card-page__apple-wallet'),
google_wallet: !!document.querySelector('.gift-card-page__google-wallet'),
};
}
console.table(report.surfaces);
return report;
})();What to look for:
footer.present === true— footer DOM is present on standard storefront pages;falseby design on the gift-card and password pages (those layouts render no footer)footer.policies === true— the policy-row<ul>renders whenever the Show policies toggle is ON, even with zero published policies; individual links appear only for published policiesfooter.localization === true— localization form is rendering (requiresshow_country_selectoror similar to be ON)footer.newsletter === true— newsletter block markup is present (<customer-form>element exists); this does not provecustomer-form.jsloaded or the element upgraded — check the Network tab for thatfooter.payment_icons_aria— count of payment icons witharia-label(should be ≥ 1 if shop has payment types)
Curl cheat-sheet (D-67)
Topic 1 — Footer policy URL resolution
Verify your policy URLs resolve correctly:
bash
# Replace 'your-store.myshopify.com' with your store domain
curl -L -s -o /dev/null -w "%{http_code}" https://your-store.myshopify.com/policies/privacy-policy
# Expected: 200For a password-protected (dev) store, capture the storefront-password cookie first — the same flow documented in search.md → Capture auth cookie. The password is submitted with --data-urlencode so values containing &, +, =, or % survive intact rather than splitting into extra form fields:
bash
# 1. GET the password page and extract the CSRF token
curl -s -c cookies.txt https://your-store.myshopify.com/password -o /tmp/pw.html
CSRF=$(grep -o 'name="authenticity_token" value="[^"]*"' /tmp/pw.html | head -1 | sed 's/.*value="//;s/"//')
# 2. Submit the storefront password (replace YOUR_STOREFRONT_PASSWORD with your store's password)
curl -s -c cookies.txt -b cookies.txt \
--data-urlencode "form_type=storefront_password" \
--data-urlencode "authenticity_token=${CSRF}" \
--data-urlencode "password=YOUR_STOREFRONT_PASSWORD" \
-L https://your-store.myshopify.com/password -o /dev/null
# 3. Verify the policy URL with the auth cookie
curl -L -s -b cookies.txt -o /dev/null -w "%{http_code}" \
https://your-store.myshopify.com/policies/privacy-policy
# Expected: 200Topic 2 — 404 status code
bash
curl -I https://your-store.myshopify.com/not-a-real-path-12345
# Expected: HTTP/2 404Topic 3 — Password form action
bash
curl -I https://your-store.myshopify.com/password
# Expected: HTTP/2 200 (password page)Topic 4 — Gift-card noindex header
bash
curl -I https://your-store.myshopify.com/gift_cards/{code}
# Expected: x-robots-tag: noindex, nofollow (Shopify platform header)Topic 5 — Apple Wallet pass URL availability
bash
curl -I {apple_wallet_pass_url}
# Expected: HTTP/2 200 (if Apple integration enabled)Topic 6 — Google Wallet template substitution
Inspect page HTML to verify the template was substituted:
bash
curl -s https://your-store.myshopify.com/gift_cards/{code} | grep 'gift-card-page__google-wallet'
# Expected: <div class="gift-card-page__google-wallet"> wrapping an unclassed <a href="...">
# The href is your configured template with the code and balance placeholders replaced
# by the real values.Topic 7 — Cache-control for footer asset freshness
bash
curl -I https://your-store.myshopify.com/cdn/shop/t/1/assets/footer.css
# Expected: cache-control: public, max-age=31536000 (Shopify CDN strong cache)
# After pushing theme changes, the asset URL includes a new version param — cache busts automatically