Every Webflow fix we catalog

SEOLZ catalogs 182 fixes for Webflow across 6 areas — SEO, answer-engine readiness, accessibility, security and site-health. Each lists the exact steps for Webflow, with a link to the official docs.

SEO · 52 fixes

Catalog coverageModerate effort

Audit your XML sitemap and internal linking so that every product and category page is discoverable and crawlable by Google — not hidden behind JavaScript, pagination, or missing links.

On Webflow

  1. Webflow Project Settings › SEO: ensure 'Auto-generate sitemap' is enabled. The sitemap is served at yoursite.com/sitemap.xml.
  2. CMS Collections: every Collection (e.g., Products, Categories) used on your site must have a Collection Page template published. Open the Designer › CMS Collections and confirm each collection has a live template page.
  3. For each CMS Collection, open the Collection settings and confirm 'Indexable' is toggled ON — items with this off are excluded from the sitemap.
  4. In the Designer, confirm your Navigation component contains links to category/collection list pages using standard <a> tags (Webflow's link element), not JavaScript-only interactions.
  5. Webflow Ecommerce: Designer › Ecommerce › Products — ensure each product is Published (not Draft) and that the Product Template page is set to indexed.
  6. Submit yoursite.com/sitemap.xml in Google Search Console after publishing.
Code · Generic HTML (html)
<!-- CORRECT: Standard crawlable link -->
<a href="/products/blue-running-shoes">Blue Running Shoes</a>

<!-- WRONG: JavaScript-only — Googlebot cannot follow this -->
<span onclick="navigate('/products/blue-running-shoes')">Blue Running Shoes</span>

Standard crawlable product link — use plain <a href> tags, never JavaScript-only click handlers, for navigation links that Google must follow.

Cwv clsModerate effort

Eliminate unexpected layout shifts by reserving space for every image, video, embed, and dynamically injected element so nothing jumps when the page loads.

On Webflow

  1. Images: In the Webflow Designer, click any image element → Element Settings (gear icon) → ensure 'Width' and 'Height' are set to explicit values (not 'auto' for both simultaneously). For CMS-bound images, set a fixed aspect ratio container: create a div wrapper with padding-top: 75% (for 4:3) and position the image absolutely inside it, or use the CSS aspect-ratio property in Webflow's Style panel.
  2. Lazy loading: Webflow automatically adds loading='lazy' to images below the fold. Ensure images in the hero/above-the-fold area have loading='eager' (set in Element Settings) so they load immediately without a shift.
  3. Fonts: In Project Settings → Fonts, Webflow inlines font-display: swap for Google Fonts automatically. For custom uploaded fonts (Project Settings → Fonts → Upload), add font-display: optional via a global CSS embed in Project Settings → Custom Code → Head code: <style>@font-face { font-display: optional; }</style>.
  4. Interactions / animations: In Webflow Interactions panel, review any 'Page Load' animations that move elements downward on trigger — replace with opacity-only animations that do not affect layout.
  5. Embeds: For any HTML Embed element (e.g. a reviews widget), wrap it in a div with a min-height style set in the Style panel to the expected rendered height.
  6. Verify: Publish your site and run PageSpeed Insights; check CLS < 0.1 on mobile.
Code · Generic HTML (html)
<!-- CORRECT: explicit width and height prevent layout shift -->
<img
  src="/images/product-red-sneaker.jpg"
  alt="Red canvas sneaker, side view"
  width="800"
  height="800"
  loading="lazy"
>

Always declare width and height on every img element. The browser uses these to reserve space before the image file downloads.

Cwv inpLarger project

Reduce Interaction to Next Paint (INP) to under 200 ms by breaking up long JavaScript tasks, deferring non-critical third-party scripts, and lightening event handlers so the page feels fast and responsive after every tap or click.

On Webflow

  1. In the Webflow Designer, go to Page Settings (the gear icon) for each page. Scroll to 'Before </body> tag' and move any third-party script embeds there if they are currently in 'Inside <head> tag', so they load after page content.
  2. Add `defer` or `async` as an attribute directly in your `<script>` embed code within the Custom Code panel.
  3. In Project Settings → Custom Code, review scripts in the 'Head Code' and 'Footer Code' sections. Move non-critical scripts to Footer and add `defer`.
  4. For interactions built with Webflow Interactions, audit any 'Page Load' triggers that fire heavy animations immediately — delay them or trigger them on scroll/interaction instead.
  5. Use Finsweet's Webflow Optimizer or a Cloudflare Worker to further defer scripts if native embed options are insufficient.
  6. Verify INP improvement with PageSpeed Insights and Chrome DevTools Performance panel after publishing each change.
Cwv lcpModerate effort

Reduce Largest Contentful Paint (LCP) to under 2.5 seconds by serving the hero image in a next-gen format (WebP/AVIF), preloading it, and eliminating render-blocking resources.

On Webflow

  1. IDENTIFY LCP IMAGE: Run PageSpeed Insights on your published Webflow URL. The LCP element is usually a Hero Section background image or a large <img> element at the top of the page.
  2. CONVERT TO WEBP/AVIF: In the Webflow Designer, select your hero image element › Asset panel › upload a WebP version of your hero image. Webflow also auto-generates WebP for images uploaded as JPEG/PNG in newer projects.
  3. SET LOADING PRIORITY: Select the hero <img> in the Designer › Element Settings (D key) › set Loading to 'Eager' and in the Custom Attributes panel add: Name: fetchpriority, Value: high.
  4. PRELOAD: Go to Project Settings › Custom Code › Head Code. Add: <link rel='preload' as='image' href='YOUR_WEBFLOW_IMAGE_CDN_URL' fetchpriority='high'>. Find the URL from the published page's source.
  5. DEFER JS: In Project Settings › Custom Code, move non-critical third-party <script> tags to the Footer Code section and add 'defer'. Do not defer scripts that must run before first paint.
  6. REMOVE INTERACTIONS FROM HERO: Webflow Interactions that trigger on page load for above-the-fold elements add JS weight. Simplify or remove entrance animations on the hero section.
  7. VERIFY: Publish the site and re-run PageSpeed Insights. Check Webflow's SEO settings confirm no lazy-load is applied to the hero.
Code · Webflow (html)
<!-- Webflow: Head Code injection — preload LCP hero image -->
<link
  rel="preload"
  as="image"
  href="https://uploads-ssl.webflow.com/YOUR_SITE_ID/YOUR_IMAGE_ID/hero-banner.webp"
  fetchpriority="high"
>

Paste in Project Settings › Custom Code › Head Code. Replace the href with your published Webflow image CDN URL (right-click live hero image, Copy image address).

Duplicate meta descriptionModerate effort

Write a unique, page-specific meta description for every page on your store so Google can display a compelling, accurate snippet for each one in search results.

On Webflow

  1. For a static page: open the Webflow Designer, select the page from the Pages panel (top-left), click the gear icon next to the page name, and in the 'SEO Settings' section update the 'Meta Description' field.
  2. For CMS Collection items (e.g. products, blog posts): go to the CMS Collection in the Designer, open the Collection Settings, and bind the Meta Description field to a dedicated CMS text field; then fill in unique descriptions for each item in the CMS Editor or via CSV import.
  3. To create a CMS field for meta descriptions: in the CMS Collection Settings, click 'Add Field', choose 'Plain Text', name it 'Meta Description', and map it under Page Settings → SEO → Meta Description with the dynamic binding button.
  4. Bulk-populate descriptions by exporting the CMS collection to CSV (CMS → [Collection] → Export), filling in the meta description column, and re-importing via CMS → [Collection] → Import.
  5. For the homepage: Pages panel → gear icon next to 'Home' → SEO Settings → Meta Description.
  6. Publish the site and verify with View Source or an SEO browser extension, then re-index in Google Search Console.
Code · Webflow (html)
<!-- Webflow outputs this in <head> automatically when you fill in Page Settings > SEO > Meta Description -->
<!-- For CMS collections, bind via: Page Settings > SEO > Meta Description > click the purple binding icon > select your 'Meta Description' CMS field -->
<meta name="description" content="Discover our hoops collection — basketball shoes, apparel, and accessories for every level of play. Shop now with free shipping.">

Webflow renders this automatically from the Meta Description field in Page Settings. For CMS-driven pages, bind the field to a CMS plain-text field as shown.

Duplicate titleModerate effort

Give every page a unique, descriptive title tag that accurately reflects that page's specific content.

On Webflow

  1. For static pages: open the Designer → select the page in the Pages panel → click the gear/settings icon next to the page → SEO Settings → edit 'Title Tag'.
  2. For CMS Collection pages (e.g. a Products collection): Pages panel → CMS Collections → open the Collection template page → SEO Settings → use the '+Add Field' dynamic binding to insert the item's Name (or a custom SEO Title field) so every generated page title is unique.
  3. Add a dedicated 'SEO Title' text field to your CMS Collection schema (CMS → Collections → edit schema) so content editors can override the title per item without touching the template.
  4. Publish changes and verify with Webflow's built-in SEO checklist or a third-party crawler.
Code · Webflow (html)
<title>{{Product Name}} | BrandName</title>
<!-- Webflow renders this automatically when you bind the SEO Title
     field to the CMS 'Name' field (or a custom 'SEO Title' field)
     in the Collection template page settings. -->

Webflow CMS collection template SEO Title field binding — set this in the Designer SEO panel using the dynamic field picker. The output rendered in <head> looks like this.

Faceted url indexableModerate effort

Point the rel=canonical on every faceted/filter URL to its clean parent category URL (or add noindex), so Google treats filtered pages as a single authoritative page instead of thousands of duplicates.

On Webflow

  1. Webflow eCommerce collection pages can have filter/sort query parameters added by custom JavaScript or Finsweet's CMS Filter library (a popular Webflow filtering solution).
  2. In Webflow Designer: go to Pages → click the gear icon on your Collection List or Shop page → SEO Settings. There is a 'Canonical Tag' field — enter the clean base URL of the collection page here (e.g., https://yoursite.com/shop/womens-pants). This is the most direct fix for static collection pages.
  3. If filters are driven by URL parameters (e.g., Finsweet's fs-cmsfilter attribute with URL state enabled), go to the Finsweet CMS Filter configuration on the page's embed/script element and set the 'url' parameter to false or 'history: false' to prevent filter state from being written to the URL — this eliminates the faceted URL problem at source.
  4. Alternatively, add a custom canonical override in Project Settings → Custom Code → Head Code: <link rel='canonical' href='https://yoursite.com/shop/womens-pants'> — but be aware this applies to all pages; use page-level Head Code (in Page Settings → Custom Code) for per-page control.
  5. For dynamic collection pages powered by the Webflow CMS, use the page's SEO settings panel and reference the collection item's slug to build the correct canonical URL.
  6. Verify with Google Search Console URL Inspection.
Code · Webflow (html)
<link rel="canonical" href="https://www.yoursite.com/shop/womens-pants">

Add to Page Settings → Custom Code → Inside <head> tag for each collection page. Replace the URL with your actual clean collection URL.

Images missing altModerate effort

Add descriptive alt text to every product and content image so search engines and screen readers can understand what the image shows.

On Webflow

  1. In the Webflow Designer, click any image element on the canvas → in the Element Settings panel on the right (the gear icon tab) → find the 'Alt Text' field → type your description. Toggle off 'Decorative' if it is on.
  2. CMS collection images: Designer → CMS → Collections → [select collection] → open any item → find the image field → click the image → add alt text in the settings pane.
  3. Asset panel images: If images are placed via the Assets panel, click the image element after placing it on the canvas and set alt text via Element Settings as above.
Code · Generic HTML (html)
<!-- BEFORE (missing alt — bad) -->
<img src="/images/red-leather-bag.jpg">

<!-- AFTER (descriptive alt — correct) -->
<img src="/images/red-leather-bag.jpg" alt="Red leather crossbody bag with gold buckle clasp">

<!-- Decorative image (intentionally empty alt — correct) -->
<img src="/images/divider-line.png" alt="">

Place this wherever an <img> tag appears in your HTML. Replace the placeholder text with your real description.

Images missing alt bulkModerate effort

Add descriptive alt text to every product and content image at the template level so search engines and screen readers can understand all images on the page.

On Webflow

  1. In the Webflow Designer, click any Image element on the canvas → in the right-hand Settings panel (the gear icon), fill in the 'Alt Text' field.
  2. For CMS-driven images (e.g. a Product Image field in the CMS): click the image element → in Settings, switch the Alt Text field from 'Custom' to 'Get from CMS field' → bind it to the appropriate CMS text field (e.g. 'Product Name' or a dedicated 'Image Alt' field in your CMS collection).
  3. If your CMS collection lacks a dedicated alt text field, add one: CMS → open the Collection → Add Field → Plain Text → name it 'Image Alt Text' → populate it for all items → bind it as above.
  4. Publish the site and verify in DevTools that `alt` attributes are populated on live pages.
Code · Webflow (html)
<!-- Expected rendered HTML after binding Alt Text to CMS 'Product Name' field -->
<img
  src="https://uploads-ssl.webflow.com/abc123/red-shoes.jpg"
  alt="Red Canvas Running Shoes"
  loading="lazy"
  sizes="(max-width: 479px) 100vw, (max-width: 767px) 48vw, 32vw"
  srcset="https://uploads-ssl.webflow.com/abc123/red-shoes-p-500.jpg 500w,
          https://uploads-ssl.webflow.com/abc123/red-shoes.jpg 800w"
  width="800"
  height="800">

Webflow generates this markup when you bind Alt Text to a CMS field in the Designer. Shown here as the expected rendered output to verify in DevTools.

Low performance scoreLarger project

Improve your Lighthouse/PageSpeed performance score by reducing page weight, eliminating render-blocking resources, optimising images, and addressing Core Web Vitals so Google ranks your store higher in search results.

On Webflow

  1. Webflow serves assets via Fastly CDN with GZIP — core delivery is handled; focus on asset optimisation and JS.
  2. Optimise images: in Webflow Designer, select each Image element → Asset panel → re-upload WebP versions; enable 'Lazy load' in element Settings panel for all below-the-fold images.
  3. Set explicit width and height on all images to prevent CLS: in the Designer, set fixed dimensions or use intrinsic sizing in Style panel.
  4. Audit custom code and third-party embeds: Project Settings → Custom Code (site-wide head/footer) and Page Settings → Custom Code (page-level) — remove any unused scripts; move non-critical scripts to Before </body> and add defer attribute.
  5. Minimise interactions and animations: heavy Webflow Interactions (GSAP-based) on mobile are a leading cause of high Total Blocking Time — audit via Designer → Interactions panel and remove or simplify any page-load triggers.
  6. Use Webflow's Asset Manager to compress fonts — or switch to system fonts for body text to eliminate font-loading delay.
  7. Re-publish the site after changes: Designer → Publish, then re-test with PageSpeed Insights.
Code · Generic HTML (html)
<!-- In <head>: preconnect to critical third-party origins to reduce connection latency -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

<!-- Preload your Largest Contentful Paint image (hero/banner) so it loads earlier -->
<link rel="preload" as="image" href="/images/hero.webp" fetchpriority="high">

<!-- Hero image: eager load, explicit dimensions to prevent CLS -->
<img
  src="/images/hero.webp"
  alt="Descriptive alt text for your hero image"
  width="1200"
  height="600"
  fetchpriority="high"
  decoding="async"
>

<!-- All other images: lazy load, explicit dimensions to prevent CLS -->
<img
  src="/images/product-shot.webp"
  alt="Descriptive product alt text"
  width="800"
  height="800"
  loading="lazy"
  decoding="async"
>

<!-- Defer non-critical third-party scripts (analytics, chat, pixels) -->
<script src="https://example-analytics.com/tracker.js" defer></script>

<!-- Load non-critical CSS asynchronously to unblock render -->
<link rel="preload" href="/css/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/non-critical.css"></noscript>

Paste these patterns into your <head> and image markup to address the most common Lighthouse failures.

Meta description too longQuick win

Shorten your meta description to 150–160 characters so Google displays it in full instead of cutting it off with "…" in search results.

On Webflow

  1. Open the Webflow Designer → click the Settings panel (⚙) in the left toolbar → select 'SEO Settings'.
  2. Alternatively, open the Pages panel (the stack icon) → hover over the target page → click the Settings (gear) icon → scroll to the 'SEO' section.
  3. Edit the 'Meta Description' field, keeping it under 160 characters.
  4. For CMS collection items (products, blog posts): open the CMS collection in the Editor, open the item, and update the 'Meta Description' field in the item's settings panel.
  5. Publish the site.
Code · Webflow (html)
<meta name="description" content="Award-winning Bang & Olufsen headphones and speakers. Visit our showroom at The Gardens Mall or shop online with free delivery.">

Webflow outputs meta descriptions automatically from the SEO settings panel. This snippet shows the rendered HTML you should see in View Source after publishing — verify it matches your edited text and is under 160 chars.

Meta description too shortModerate effort

Expand your meta description to 120–160 characters so Google displays a compelling, complete snippet in search results that drives more clicks to your store.

On Webflow

  1. Open the Webflow Designer for your project.
  2. Click the page settings icon (⚙) for the page you want to update (Pages panel → hover → Settings).
  3. Under the 'SEO Settings' section, update the 'Meta Description' field to 120–160 characters → close the panel (auto-saves).
  4. For CMS collection items (e.g., products or blog posts): open the CMS Collection settings → edit the item → find the 'SEO' fields → update the description field or bind it to a rich-text/plain-text CMS field that holds a full description.
  5. Publish your site.
Code · Generic HTML (html)
<meta name="description" content="Shop the official Nike national team jerseys — advanced Dri-FIT heat tech meets bold heritage design. Free shipping on orders over $75. Explore the full collection now.">

Place this inside the <head> element of your HTML page. Replace the content value with your 120–160 character description.

Missing canonicalModerate effort

Add a self-referencing canonical tag to every page so Google knows which URL is the definitive version of that content.

On Webflow

  1. In the Webflow Designer, open the Page Settings for any page by clicking the Pages panel (left sidebar) → hover over the page → click the gear icon.
  2. Scroll down to the 'SEO Settings' section. Webflow automatically sets a self-referencing canonical by default based on the published URL.
  3. To set a custom canonical for a specific page, paste the full preferred URL into the 'Canonical URL' field in that page's SEO Settings panel.
  4. For Collection pages (CMS-driven pages like products or blog posts), open the Collection in the CMS settings → click the template page → Page Settings → SEO → set the canonical using a dynamic field binding (e.g. bind to a 'Canonical URL' custom CMS field you create).
  5. Publish the site and verify via page source.
Code · Generic HTML (html)
<link rel="canonical" href="https://www.yourstore.com/your-page/" />

Paste this inside the <head> element of every page. Replace the href value with the full, absolute preferred URL of that specific page.

Missing h1Quick win

Add a single, descriptive H1 tag to every page that currently lacks one, clearly stating the page's main topic.

On Webflow

  1. Open the Webflow Designer for the page.
  2. In the Navigator panel (or on canvas), locate or add a 'Heading' element at the top of the page main content.
  3. With the Heading element selected, look at the Element Settings panel (right side) — confirm the 'Tag' is set to 'H1' (not H2, H3, or Div).
  4. Type your descriptive heading text directly on the canvas.
  5. For CMS Collection pages (e.g. product detail, blog post): open the Collection Template page in the Designer, add a Heading block, set tag to H1, then bind it to the 'Name' or 'Title' CMS field using the purple binding icon.
  6. Publish the site and verify with browser DevTools.
Code · Webflow (html)
<h1 class="product-heading">Leather Crossbody Bag</h1>

In the Webflow Designer, set the Heading element's tag to H1. This is the rendered output Webflow produces — verify it in View Page Source.

Missing meta descriptionModerate effort

Write and add a unique meta description (120–160 characters) to every page that is missing one, so Google can display an accurate, compelling snippet in search results.

On Webflow

  1. For a static page: Designer → Pages panel (top-left) → hover over the page → click the settings gear → 'SEO Settings' section → fill in 'Meta Description'.
  2. For a CMS Collection page (e.g., Products, Blog): Designer → Pages panel → click the Collection template page → gear icon → bind the 'Meta Description' field to a CMS field (e.g., a dedicated 'SEO Description' text field in your Collection schema) using the '+' connector.
  3. To add a dedicated SEO description field: CMS → Collections → click the collection → 'Add Field' → Plain Text → label it 'SEO Description' → then go back and bind it as above.
  4. For the published site, verify by viewing page source or using the Webflow SEO Analyzer in Project Settings → SEO.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <meta name="description" content="Shop our full range of handmade leather wallets. Free shipping on orders over $50. Slim, durable & personalised — order yours today.">
  <title>Handmade Leather Wallets | YourStoreName</title>
</head>

Place this tag inside the <head> element of every HTML page. Keep the content between 120–160 characters.

Missing og descriptionModerate effort

Add an og:description meta tag to every page so that social media platforms display a compelling, accurate preview when your pages are shared.

On Webflow

  1. Open your project in the Webflow Designer.
  2. For static pages: click the page in the Pages panel → the settings cog → scroll to 'Open Graph Settings' → fill in the 'OG Description' field.
  3. For CMS Collection pages (products, blog posts): open the Collection settings → go to the template page → open Page Settings → bind the OG Description field to a CMS field (e.g., the product's short description or excerpt field) using the dynamic binding toggle.
  4. Publish the site and verify with Facebook's Sharing Debugger or LinkedIn Post Inspector.
Code · Webflow (html)
<meta property="og:description" content="A concise, compelling summary of this page in 100–160 characters that encourages clicks." />

In the Webflow Designer, paste this into Page Settings → Custom Code → Head Code for a static page fallback. For CMS pages, use the dynamic OG Description field in Page Settings instead.

Missing og imageModerate effort

Add an og:image meta tag to every page so that social media platforms and messaging apps display a rich, branded image preview whenever someone shares your store's link.

On Webflow

  1. In the Webflow Designer, open the Pages panel on the left → hover over a page → click the settings gear icon.
  2. In the Page Settings panel, scroll down to 'Open Graph settings' → upload an og:image (1200×630 px recommended) and fill in og:title and og:description.
  3. For dynamic Collection pages (e.g. products, blog posts), open the Collection in the CMS → edit the Collection Page template → in Page Settings, bind the og:image field to a CMS image field (e.g. 'Product Image') using the dynamic binding option.
  4. Ensure your CMS Collection items each have an image set in that field so the binding resolves correctly for every item.
  5. For site-wide defaults, go to Project Settings → SEO → add a default og:image URL in the Open Graph fields.
  6. Publish your site and validate each page type using Facebook's Sharing Debugger.
Missing og titleQuick win

Add an og:title meta tag to every page so your store's title appears correctly when shared on social media platforms like Facebook, LinkedIn, and Pinterest.

On Webflow

  1. In the Webflow Designer, click on the page you want to edit in the Pages panel on the left.
  2. Click the gear icon next to the page name to open Page Settings.
  3. Scroll down to the 'Open Graph Settings' section. Fill in the 'OG Title' field with your desired title.
  4. For dynamic CMS collection pages (e.g. product pages, blog posts): open the Collection's template page → open Page Settings → in the OG Title field, click 'Add Field' to bind it to a CMS field such as the item's Name or SEO Title field.
  5. To set og:title site-wide for pages that don't have it explicitly set, go to Project Settings → SEO → and fill in the default Open Graph title.
  6. Publish your site and verify using the Facebook Sharing Debugger.
Code · Webflow (html)
<!-- Webflow generates this automatically from the OG Title field in Page Settings.
     For static pages, enter your title text directly in the OG Title field.
     For CMS collection pages, bind OG Title to a CMS field (e.g. Name or SEO Title).
     Webflow outputs: -->
<meta property="og:title" content="Your Page Title | Store Name" />

Enter this in the Page Settings → Open Graph → OG Title field in the Webflow Designer. For CMS pages, bind the field to your CMS Name or SEO Title field. No code entry needed — shown here for reference only.

Missing titleQuick win

Add a unique, descriptive title tag (30–60 characters) to every page that is missing one.

On Webflow

  1. In the Webflow Designer, select the page from the Pages panel (left sidebar).
  2. Click the settings (gear) icon next to the page name → go to the 'SEO Settings' section.
  3. Fill in the 'Title Tag' field with your descriptive title (30–60 characters).
  4. For CMS collection items (e.g. products): open the CMS Collection settings → add or edit the 'SEO Title' field and bind it to the relevant CMS field using the '+' dynamic bind button so every item gets a unique title automatically.
  5. Publish the site for changes to take effect.
Code · Webflow (html)
<!-- Webflow: Page Settings → SEO → Title Tag field (no code needed for most pages) -->
<!-- For a static override via Custom Code → Head, paste: -->
<title>Your Specific Page Title | YourBrand</title>

Webflow generates the <title> tag from the Page Settings UI. For CMS pages, this Embed code is NOT needed — use the field binding in Page Settings instead. For a static page where you need a custom override, you can paste a <title> override in Page Settings → Custom Code → Inside <head> tag.

Multiple h1Moderate effort

Remove all but one H1 tag from each page so every page has exactly one clear, descriptive primary heading.

On Webflow

  1. Open your project in the Webflow Designer and navigate to the affected page or Collection Template page.
  2. In the Navigator panel (left sidebar), expand the page structure and look for all 'Heading' elements. Click each one and check the Tag dropdown in the right-hand Style panel — only one should be set to H1.
  3. Change all additional H1 elements to H2, H3, etc., using the Tag dropdown (it shows H1–H6 and other block options).
  4. For Collection (CMS) pages (e.g. product detail pages), the bound title field should be H1; all other headings in the layout should be H2 or lower.
  5. Publish the site and verify in DevTools or an SEO audit.
Code · Webflow (html)
<!-- Correct rendered output from a Webflow CMS Product Template page -->
<!-- The CMS-bound product name field is set to H1 in the Designer -->
<h1 class="product-title">Handmade Ceramic Mug – Ocean Blue</h1>

<!-- Other headings in the layout are set to H2, H3, etc. -->
<h2 class="section-heading">About This Product</h2>
<p>Wheel-thrown and hand-glazed in small batches...</p>

<h2 class="section-heading">Shipping &amp; Returns</h2>
<h3 class="subsection-heading">Processing Time</h3>
<p>Orders ship within 3 business days.</p>

Webflow outputs heading tags based on the Tag dropdown in the Designer. This shows the correct rendered HTML for a Webflow CMS product template page.

Noindex detectedQuick win

Remove or replace the noindex directive on any page you want to appear in Google search results.

On Webflow

  1. Global setting: In the Webflow Designer, go to Project Settings → SEO tab. Confirm 'Disable Webflow indexing' is UNCHECKED. When checked, Webflow injects a noindex tag on every page.
  2. Per-page setting: In the Designer, open the Pages panel, click the settings gear next to a page → SEO Settings section → find the 'Indexing' toggle or 'robots' field and set it to allow indexing.
  3. CMS Collection pages: Open the CMS collection settings → Template page settings → confirm the robots/indexing field does not contain noindex.
  4. Custom code in <head>: In Page Settings → Custom Code → Inside <head> tag, check for any hardcoded <meta name="robots" content="noindex"> and remove it.
  5. After publishing, verify in Google Search Console → URL Inspection → Request Indexing.
Code · Generic HTML (html)
<meta name="robots" content="index,follow">

Place inside <head>. This is the CORRECT tag that allows indexing and link-following.

Non self canonicalQuick win

Audit every page whose canonical tag points to a different URL, then either correct it to self-reference or confirm the cross-reference is intentional and correct.

On Webflow

  1. Webflow generates self-canonical tags automatically. Non-self-canonicals are introduced either via custom code embeds or third-party SEO integrations.
  2. In the Webflow Designer, select the affected page, open Page Settings (gear icon), and scroll to the 'Custom Code' section → 'Head Code'.
  3. Search for any <link rel="canonical" ...> tag in the Head Code field. If present and incorrect, remove it entirely (so Webflow outputs its automatic canonical) or correct the href to the page's own URL.
  4. Also check your Site Settings → Custom Code → 'Head Code' for a site-wide canonical override and remove/correct it.
  5. Publish the site, then view-source the live page to confirm the canonical tag matches the page URL.
Code · Generic HTML (html)
<link rel="canonical" href="https://www.example.com/your-page-url/">

Place this tag inside the <head> element of every page. The href must exactly match the page's own preferred URL.

Og url mismatchModerate effort

Set a unique, page-specific og:url meta tag on every page that exactly matches that page's canonical URL.

On Webflow

  1. In Webflow Designer, open the Page Settings for a given page (click the Pages panel → gear icon next to a page).
  2. Scroll to the 'Open Graph Settings' section. In the 'Open Graph URL' field, ensure it is blank or set to the correct page URL — if it contains a hardcoded URL (e.g. the homepage), clear it. Webflow uses the published page URL by default when this field is empty.
  3. For Collection (CMS) pages (e.g. blog posts, product detail pages), open the Collection Template page settings, go to Open Graph Settings, and bind the OG URL field to the CMS item's slug/URL field using the '+' dynamic binding icon so each item gets its own URL.
  4. If you've added a global og:url via a custom code embed in the <head> (Site Settings → Custom Code → Head Code), locate and remove or fix that hardcoded tag.
  5. Publish your site, then use 'View Source' on a CMS item page and a static page to verify the og:url is unique and matches the canonical URL on each.
  6. Use the Facebook Sharing Debugger to force a re-scrape and confirm the corrected values.
Code · Webflow (html)
<!-- Do NOT use a static og:url here — this causes the mismatch.
     For static pages, Webflow sets og:url automatically from the page URL.
     For CMS pages, bind og:url to the item URL in Page Settings > Open Graph.
     Only add custom head code if you have a specific override need. -->

Paste into Site Settings → Custom Code → Head Code ONLY as a fallback for static pages. For CMS Collection pages, use Webflow's built-in OG URL field with dynamic binding instead — do not use this static snippet for CMS pages.

Page not foundModerate effort

Fix broken internal links pointing to pages that return a 404 error by either removing those links or setting up a 301 redirect from the missing URL to the correct replacement page.

On Webflow

  1. In the Webflow Designer or Dashboard, open your project and go to Project Settings → SEO → Redirects (301).
  2. Click '+ Add redirect'. Enter the old broken URL path and the new destination path. Save and republish your site.
  3. To fix internal links in page content, open the Designer, navigate to the page or component containing the broken link, click the linked element, open the link settings panel (chain icon), and update or remove the link URL.
  4. For CMS collection item links, go to the CMS panel, open the affected collection, and update the internal link fields there.
  5. For links in global components (navbar, footer), open the component in the Designer, find and update the link.
  6. Republish your site after all changes, then verify the old URL redirects correctly using a browser or HTTP status tool.
Code · Generic HTML (html)
<!-- BEFORE: broken internal link -->
<a href="/old-products/discontinued-item">Shop Now</a>

<!-- AFTER: updated to correct URL -->
<a href="/products/replacement-item">Shop Now</a>

Update an internal anchor link to point to the correct URL instead of the broken one.

Page speed warningModerate effort

Reduce your page load time to under 1.5 seconds by optimizing images, eliminating render-blocking resources, and enabling caching and compression.

On Webflow

  1. Images: In the Webflow Designer, select any image element and in the element settings enable 'Lazy load' for all images below the fold. Upload images at the correct display size and enable WebP output in Project Settings > General > Image Optimization.
  2. Interactions / animations: In the Webflow Designer, go to the Interactions panel (the lightning bolt icon) and audit all animations. Remove or simplify page-load trigger animations on hero sections — these block perceived performance.
  3. Custom code: In Project Settings > Custom Code, review any scripts added to the <head> or before </body> tags. Move non-critical scripts to the footer position and add defer or async attributes via a custom code embed.
  4. Fonts: In Project Settings > Fonts, use system fonts for body text and limit web font imports to one or two families. Each Google Font variant is a separate request.
  5. CMS images: For CMS-driven pages (e.g. product or blog pages), open the Collection Template in the Designer and ensure the image element bound to your CMS image field has 'Lazy load' enabled and a defined 'Max width' so Webflow auto-sizes it.
  6. Publish and verify: After publishing changes, run PageSpeed Insights on your live Webflow domain (not the .webflow.io preview) to get accurate results, since the preview subdomain may have different caching behaviour.
Redirect chainModerate effort

Update every internal link that passes through a redirect chain to point directly at the final destination URL, eliminating all intermediate hops.

On Webflow

  1. Crawl your published Webflow site with Screaming Frog or Ahrefs to export redirect chains; record the final destination URL for each.
  2. Open the Webflow Designer. Select the element (link block, text link, button) that contains the chained URL. In the Settings panel (gear icon), find the Link Settings section and replace the URL with the final destination URL.
  3. For links in the Navbar symbol or Footer symbol: open the Symbol in the Designer (double-click to enter), update the link URL inside, then press Escape. Because it's a symbol, the change propagates to every instance site-wide.
  4. For CMS-driven links (e.g. collection list items linked to a field): go to the CMS Collection in the Designer or Webflow Editor, check whether the link field value is set to an intermediate URL, and correct it to the final URL.
  5. Check Project Settings → SEO → 301 Redirects; if any redirect rule targets another redirecting URL, update it to the true final destination and republish.
  6. Publish the site and re-crawl to verify all internal links resolve in a single hop.
Code · Generic HTML (html)
<!-- BEFORE: link points to an intermediate URL that itself redirects -->
<a href="https://example.com/old-page">Our Story</a>

<!-- AFTER: link points directly to the final destination URL -->
<a href="https://example.com/about-us">Our Story</a>

Replace every intermediate redirect URL in your anchor href with the final destination URL — the one that returns HTTP 200 with no further redirects.

Search results indexableModerate effort

Add a noindex robots meta tag to all internal search result pages and block the search URL path in robots.txt to prevent Google from crawling and indexing thin, duplicate search results.

On Webflow

  1. In the Webflow Designer, select your Search Results page in the Pages panel (left sidebar).
  2. Click the gear/settings icon for that page → scroll to 'SEO Settings' → check the box 'Exclude from search indexing (noindex)'. Webflow will inject <meta name="robots" content="noindex"> on that page.
  3. If search results are rendered on a Collection Template page (e.g., a CMS-driven search), open that template's page settings and apply the same noindex checkbox.
  4. For robots.txt: go to Project Settings → SEO → scroll to the robots.txt editor → add: Disallow: /search (adjust the path to match your actual search URL path).
  5. Publish your site and verify with Google Search Console URL Inspection.
Code · Generic HTML (html)
<meta name="robots" content="noindex,follow">

Place this tag inside the <head> element of every search results page template.

Seo category description missingModerate effort

Write a 100–200 word description for every category page explaining what products it contains and who they are for, so Google can rank it for category-level keywords.

On Webflow

  1. In the Webflow Designer, open your Ecommerce → Categories CMS collection.
  2. Add a new Rich Text or Plain Text field called 'Description' to the Category collection schema (Collections → Categories → Add Field).
  3. Go to each category item in the CMS editor and fill in the 100–200 word description.
  4. Open your Category Template page in the Designer (found under Ecommerce → Category Template).
  5. Drag a Rich Text element (or Text Block) onto the canvas in the desired position (above the product list is recommended).
  6. Connect it to the CMS: select the element, click 'Get text from Categories' in the element settings panel, and choose your 'Description' field.
  7. Publish the site.
Code · Webflow (html)
<div class="category-description w-richtext">
  <p>
    Our women's waterproof hiking boots are built for trail runners,
    weekend trekkers, and serious mountaineers who refuse to let wet
    weather slow them down. Every pair features a fully sealed waterproof
    membrane, aggressive rubber outsoles, and reinforced ankle support.
    Browse the full range below.
  </p>
</div>

After binding the CMS Rich Text field in the Designer, Webflow outputs markup similar to this on the published page — no manual editing needed, but this is what the rendered HTML looks like for reference.

Seo category description thinModerate effort

Expand your category page description to at least 100 words of genuinely helpful, keyword-rich content that explains what shoppers will find in that category.

On Webflow

  1. In Webflow Designer, open the Collection Template page for your product category CMS collection.
  2. Add a Rich Text element or Text Block to the template layout where you want the description to appear (above or below the product grid).
  3. Bind the element to the 'Description' (or equivalent long-text) field in your Webflow CMS collection by clicking 'Connect a field' in the element settings.
  4. Go to the Webflow CMS Editor (or the Editor mode) and update each category entry's Description field with 100+ words of content.
  5. Publish the site. Confirm the description renders correctly by previewing the live category page.
Code · Webflow (html)
<div class="category-description w-richtext">
  <h2>Shop Our [Category Name]</h2>
  <p>
    [Your 100+ word description goes here, written in the Webflow CMS editor
    for each category entry. Use the Rich Text field type in your CMS
    collection to support headings, bold, bullets, and links.]
  </p>
</div>

In Webflow Designer, add a Rich Text element to your Collection Template page and bind it to the Description CMS field. The rendered HTML output looks like this.

Seo category emptyQuick win

Either populate the empty category with products, or add a noindex tag and remove it from your navigation so Google does not waste crawl budget on a page with no content.

On Webflow

  1. Open your Webflow project in the Designer.
  2. Option A — Add products: Go to the CMS panel → your product Collection, click 'New Item', and create or move products into this category.
  3. Option B — Noindex: Open the Collection page template for the empty category. In the Page Settings panel (gear icon), scroll to 'SEO Settings' and check 'Exclude from search index (noindex)'. This applies to all items matching that template; for a single category, you may need a conditional visibility workaround or set it per Collection item via the CMS field 'noindex' toggle (Webflow supports a noindex field per CMS item).
  4. Option B (per CMS item): In your Category collection, add a boolean field called 'No Index'. In the Collection page template, bind the noindex meta tag to this field using an Embed element: <meta name='robots' content='noindex, follow'>. Toggle the field on for empty categories in the CMS editor.
  5. Option C — Delete & redirect: Delete the CMS item for the empty category. Go to Project Settings → SEO → 301 Redirects and add a redirect from the old URL slug to the parent category slug.
  6. Remove the empty category from any navigation Collection lists or static nav links in the Designer.
Code · Generic HTML (html)
<meta name="robots" content="noindex, follow">

Place inside the <head> element of an empty category page to prevent Google from indexing it.

Seo category sparseModerate effort

Add more products to sparse category pages, or consolidate very thin categories into broader, related ones so every indexed category page delivers genuine value to shoppers and search engines.

On Webflow

  1. In your Webflow Designer, open the CMS Collections panel and select the Category collection.
  2. Add a rich-text or multi-line text field for a 'Category Description' if one doesn't exist, populate it in the CMS editor, and bind it to the category template page.
  3. To add products: open each Product item in the CMS → set its Category reference field to the target category.
  4. To merge categories: update all products referencing the sparse category to reference the surviving one, then delete the old category item and set up a 301 redirect in Project Settings → SEO → 301 Redirects.
  5. To noindex: on the category Collection Template page, open Page Settings → SEO → uncheck 'Index this page', or add a custom <meta name="robots" content="noindex, follow"> in the Head Code section of the page settings.
Code · Generic HTML (html)
<meta name="robots" content="noindex, follow">

Add inside <head> of any sparse category page you want excluded from Google's index while still being followable by crawlers.

Seo facet url not canonicalizedModerate effort

Add a canonical tag to every filtered, sorted, and paginated collection/category URL pointing back to the clean (unfiltered) category URL, so Google consolidates duplicate pages and focuses crawl budget on your real content.

On Webflow

  1. Webflow automatically adds self-referencing canonical tags to all CMS and e-commerce pages. For filtered/sorted collection URLs generated by Webflow's native filtering or by third-party filter widgets (Finsweet CMS Filter, etc.), you need to customise the canonical output.
  2. Open your Webflow project → Pages panel → select the Collection List page or E-commerce Category page → open Page Settings → SEO Settings. In the 'Canonical URL' field, enter the clean base URL for this collection (without query parameters).
  3. If filter parameters are added dynamically via JavaScript (as with Finsweet CMS Filter), the canonical in the <head> already points to the clean URL since it is set at page-load time before JS runs — verify this is the case by inspecting the rendered HTML source.
  4. For Webflow Commerce: go to Ecommerce → Categories, click on a category, and set the canonical URL in the SEO panel to the clean category URL.
  5. Publish the site and verify with Google Search Console URL Inspection.
Code · Generic HTML (html)
<head>
  <!-- All other head tags -->
  <link rel="canonical" href="https://www.example.com/collections/hoops">
</head>

Place this inside the <head> of every filtered/sorted/paginated category page. The href must be the full, absolute clean URL (no query parameters).

Seo product content client renderedLarger project

Server-render your product descriptions in the initial HTML so that all crawlers — including Google, AI search bots, and social media scrapers — can read your content without executing JavaScript.

On Webflow

  1. Webflow CMS renders Collection pages (including ecommerce product pages) server-side using its CMS bindings — descriptions bound to a CMS field are in the initial HTML.
  2. In the Webflow Designer, open your Product Template page. Select the text block showing the product description and confirm it is bound to the Product Description CMS field (purple 'Connected' indicator). If it is not bound (plain static text or set via custom JS), bind it to the CMS field.
  3. For Product JSON-LD: Webflow does not auto-generate Product schema. Add it manually by placing an Embed element inside the <head> using Page Settings → Custom Code → Head Code. Use the Webflow CMS field reference syntax (e.g., {{wf {"path":"description"} }}) to dynamically insert values server-side. See code snippet below.
  4. Alternatively, use the Relume or Slater integration, or a Webflow App like 'Schema App' or 'NoCodeMonkey Structured Data' which inject server-rendered JSON-LD.
  5. Publish the site and validate with Google Search Console URL Inspection → View Tested Page → HTML tab — description text and JSON-LD must appear in the raw HTML output.
  6. Do NOT add JSON-LD via a <script> that uses fetch() or document.querySelector() to read product data — this runs client-side and is invisible to crawlers.
Seo product description minimalModerate effort

Expand your product descriptions to at least 300 words by adding specifications, use cases, and benefits so search engines have enough content to rank and shoppers have enough information to buy.

On Webflow

  1. Go to Webflow Designer → CMS → Collections → Products (or your product collection).
  2. Click the product item to open the CMS item editor.
  3. Locate the 'Description' rich-text field (or your equivalent long-form field).
  4. Expand the description to 300+ words, using the rich-text editor to add H2/H3 headings, bulleted spec lists, and benefit paragraphs.
  5. Publish the collection item. Then submit the URL in Google Search Console for re-indexing.
Code · Generic HTML (html)
<section class="product-description">
  <h2>About the [Product Name]</h2>
  <p>
    The [Product Name] is a [category] designed for [target audience]. Whether you need
    [use case 1] or [use case 2], this [product] delivers [core benefit] without
    [common pain point].
  </p>

  <h3>Key Features &amp; Specifications</h3>
  <ul>
    <li><strong>Material:</strong> [e.g. 100% organic cotton, BPA-free polypropylene]</li>
    <li><strong>Dimensions:</strong> [W × H × D] / Weight: [X kg / X lbs]</li>
    <li><strong>Colour options:</strong> [Colour 1], [Colour 2], [Colour 3]</li>
    <li><strong>Compatibility:</strong> [e.g. Works with iOS 14+ and Android 10+]</li>
    <li><strong>Warranty:</strong> [X-year manufacturer warranty]</li>
  </ul>

  <h3>Why Shoppers Choose [Product Name]</h3>
  <p>
    Unlike [generic alternatives], the [Product Name] [specific differentiator]. This means
    [tangible benefit for the shopper], saving you [time / money / effort].
  </p>
  <p>
    Ideal for [persona / scenario], the [Product Name] has been tested to [standard or
    certification] and ships ready to use — no assembly required.
  </p>

  <h3>How to Use</h3>
  <p>
    [Step-by-step or scenario-based usage instructions in 2–4 sentences. Include any
    important setup, safety, or care notes here.]
  </p>

  <h3>In the Box</h3>
  <ul>
    <li>1 × [Product Name]</li>
    <li>1 × [Accessory or manual]</li>
    <li>1 × [Charging cable / carry bag / etc.]</li>
  </ul>
</section>

Structural template for a product description section — paste inside your product page's main content area.

Seo product description missingModerate effort

Write and publish a 200–400 word product description covering what the product is, its key specs, who it's for, and how to use it.

On Webflow

  1. In the Webflow Designer, open the CMS panel (left sidebar) → select your Products collection.
  2. Click the product item to open its CMS record.
  3. Find the 'Description' (or equivalent Rich Text) field and enter your 200–400 word description.
  4. To set a meta description: in the same CMS record, scroll to the 'SEO Settings' section and fill in the 'Meta Description' field.
  5. Publish the site and visit the live product URL to verify the description renders.
Code · Generic HTML (html)
<section class="product-description">
  <h2>About This Product</h2>
  <p>
    The <strong>Trailblazer 500 Waterproof Hiking Boot</strong> is a lightweight,
    full-grain leather boot built for all-day comfort on rugged mountain trails.
    Its Gore-Tex membrane keeps feet dry in rain and creek crossings while the
    Vibram outsole delivers confident grip on wet rock and loose scree.
  </p>
  <p>
    Designed for day hikers and weekend backpackers who want reliable protection
    without the weight of a mountaineering boot, the Trailblazer 500 strikes the
    ideal balance between support and agility. The padded collar and gusseted
    tongue prevent trail debris from entering the boot, and the cushioned EVA
    midsole absorbs impact mile after mile.
  </p>
  <h3>Key Specifications</h3>
  <ul>
    <li><strong>Upper:</strong> Full-grain leather with Gore-Tex waterproof lining</li>
    <li><strong>Outsole:</strong> Vibram Megagrip rubber</li>
    <li><strong>Weight:</strong> 680 g / pair (size UK 9)</li>
    <li><strong>Shaft height:</strong> Mid (ankle) cut</li>
    <li><strong>Available widths:</strong> Standard and Wide</li>
  </ul>
  <h3>How to Use</h3>
  <p>
    Lace firmly for ankle support on uneven terrain. Break the boot in over two
    or three shorter walks before a full-day hike. Apply a water-repellent spray
    to the leather upper every 20–30 hours of trail use to maintain waterproofing.
  </p>
</section>

Minimum viable product description markup — paste inside your product page's <main> or article element.

Seo product description thinModerate effort

Expand every product description to at least 300 words of unique, keyword-rich content that answers real shopper questions and gives Google enough text to understand and rank the page.

On Webflow

  1. Open the Webflow Designer and go to your CMS → Ecommerce → Products → [select the product].
  2. Find the 'Description' (Rich Text) field and click 'Edit' to open the rich-text editor.
  3. Write your expanded description; use headings, paragraphs, and bulleted lists inside the rich-text element.
  4. If you need additional long-form fields (e.g. 'Full Details'), add a new Rich Text field to the Products collection schema under CMS → Ecommerce → Products → Fields, then bind it to a new Rich Text element on the product template page.
  5. Publish the project and submit the URL in Google Search Console.
Code · Generic HTML (html)
<section class="product-description" aria-label="Product description">
  <h2>About This Product</h2>
  <p>
    Write your opening paragraph here (2–3 sentences introducing the product,
    its primary benefit, and who it is for). Aim for a natural, engaging tone
    that mirrors how your customers talk about this type of product.
  </p>

  <h3>Key Features</h3>
  <ul>
    <li><strong>Feature One:</strong> Explain the benefit, not just the spec.</li>
    <li><strong>Feature Two:</strong> Include the detail a shopper needs to decide.</li>
    <li><strong>Feature Three:</strong> Use the words your customers actually search for.</li>
    <li><strong>Feature Four:</strong> Keep each bullet to one clear point.</li>
  </ul>

  <h3>Materials &amp; Specifications</h3>
  <p>
    Describe materials, dimensions, weight, compatibility, or other technical
    details relevant to your product category. Specificity builds trust and
    captures long-tail searches (e.g. "100% organic cotton", "fits 13-inch laptops").
  </p>

  <h3>How to Use</h3>
  <p>
    Walk the customer through setup, application, or use in plain language.
    Answer the most common pre-purchase questions here so shoppers feel confident.
  </p>

  <h3>Why Our Customers Love It</h3>
  <p>
    Reinforce with a sentence or two of social proof context or a unique selling
    point that differentiates this product from generic alternatives.
  </p>
</section>

Ideal semantic structure for a product description section on any platform's product page template.

Seo product image alt filenameModerate effort

Replace auto-generated filename-based alt text on product images with short, descriptive phrases that accurately describe what the image shows.

On Webflow

  1. For e-commerce product images, go to the Webflow Designer → CMS → Products Collection and open a product.
  2. Click the product image field; the image settings panel opens on the right.
  3. Fill in the 'Alt text' field with your descriptive text.
  4. Save and publish.
  5. For images placed directly in the Designer canvas, click the image element, open the Settings panel (D key), and enter alt text in the 'Alt text' input.
  6. For bulk CMS updates, export the Products collection as CSV, add alt text in the alt column, and re-import.
Code · Webflow (html)
<!-- Correct output Webflow generates when Alt Text field is filled -->
<img
  src="https://uploads-ssl.webflow.com/abc123/blue-sweater.webp"
  alt="Blue cotton crew-neck sweater, front view"
  loading="lazy"
  width="800"
  height="1000"
  class="product-image"
>

When embedding a custom HTML embed or editing CMS-powered images in Webflow, always populate the alt field. This snippet shows the correct output Webflow should produce.

Seo product missing brandModerate effort

Add a `brand` property to your Product schema markup so search engines can match your listings to brand-specific queries.

On Webflow

  1. Open your Webflow project in the Designer. Navigate to your Product Template page (Ecommerce → Product Template).
  2. In the Page Settings panel (gear icon) for the Product Template, scroll to 'Custom Code' → 'Head Code' section.
  3. Paste a `<script type='application/ld+json'>` block there. Use Webflow's CMS dynamic embed feature: add an 'Embed' element inside the product template canvas, then use the + (Add Field) binding to pull `{{product-brand}}` or a custom CMS field you've created for brand.
  4. Create a 'Brand' text field in your Ecommerce Products CMS collection (CMS → Collections → Products → Add Field), populate it for all products, then bind it into the JSON-LD embed.
  5. Publish the site and test a product URL in Google's Rich Results Test.
Code · Webflow (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "WEBFLOW_BINDING:product-name",
  "description": "WEBFLOW_BINDING:product-description",
  "sku": "WEBFLOW_BINDING:product-sku",
  "brand": {
    "@type": "Brand",
    "name": "WEBFLOW_BINDING:product-brand"
  },
  "offers": {
    "@type": "Offer",
    "priceCurrency": "USD",
    "price": "WEBFLOW_BINDING:product-price",
    "availability": "https://schema.org/InStock"
  }
}
</script>
<!-- NOTE: Replace each WEBFLOW_BINDING:field-name placeholder by clicking the + icon
     in the Embed editor and selecting the matching CMS field for your Products collection. -->

Add this as an Embed element inside the Product Template canvas in Webflow Designer. Replace the brand field value binding with your CMS 'Brand' field using the + Add Field button in the Embed editor.

Seo product missing gtinModerate effort

Add a GTIN (and/or MPN) field to your Product structured data so Google can match your listings to its product knowledge graph for rich results and Google Shopping.

On Webflow

  1. Open the Webflow Designer → CMS → Collections → Products (or your product Collection).
  2. Add a new plain text field called 'GTIN' and another called 'MPN' to the Collection schema.
  3. Populate these fields for each product in the CMS editor (CMS → Products → [select item] → fill GTIN/MPN fields).
  4. On the Product Template page in the Designer, add an Embed element inside the page body. In the Embed, paste the Product JSON-LD script and use Webflow's CMS binding syntax to pull in the GTIN field value dynamically.
  5. Publish the site and validate with Google's Rich Results Test.
Seo product missing stock statusModerate effort

Add a visible stock availability label and an `availability` property to your Product structured data so Google can display rich results and shoppers know immediately whether a product is in stock.

On Webflow

  1. Webflow's Ecommerce product template does not auto-generate Product JSON-LD with `availability`. You must add it manually.
  2. Open the Webflow Designer → navigate to the CMS/Ecommerce Product Template page → in the Page Settings (gear icon at top-right) → Custom Code tab → paste your JSON-LD `<script>` block in the *Before </body> tag* field.
  3. Use Webflow's CMS dynamic embed: inside the Product template, add an *Embed* element and write the JSON-LD using Webflow's dynamic field bindings — bind `availability` to a custom field or use a conditional based on the product's 'Inventory Tracking' status.
  4. For a visible label, add a Conditional Visibility text element to the product template: create a text element reading 'In Stock', set its Conditional Visibility to show only when the inventory field `> 0`; duplicate it for 'Out of Stock' with the inverse condition.
  5. Publish and validate in the Rich Results Test.
Code · Webflow (html)
<!-- Paste in Product Template → Page Settings → Custom Code → Before </body> -->
<!-- Replace 'PRODUCT-NAME', price, URL, image etc. with Webflow CMS embed/dynamic fields -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "PRODUCT-NAME",
  "description": "PRODUCT-DESCRIPTION",
  "sku": "PRODUCT-SKU",
  "image": "PRODUCT-IMAGE-URL",
  "brand": {
    "@type": "Brand",
    "name": "YOUR-BRAND"
  },
  "offers": {
    "@type": "Offer",
    "url": "PRODUCT-PAGE-URL",
    "priceCurrency": "USD",
    "price": "PRODUCT-PRICE",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
</script>
<!-- Note: Use a Webflow Embed element inside the product template to bind CMS fields dynamically instead of hardcoding values above. -->

Paste inside the Product Template page's custom code section (Page Settings → Custom Code → Before </body>). Replace Webflow dynamic field placeholders with your actual CMS field slugs.

Seo product no imagesModerate effort

Add at least one high-quality image to every product page so shoppers can see what they're buying and Google can surface your products in Image Search.

On Webflow

  1. Open the Webflow Designer and go to the CMS panel → Products collection.
  2. Click the product record that is missing images.
  3. Find the 'Main Image' (or 'Product Images') field and click it to upload an image from your computer or asset manager.
  4. To add additional gallery images, locate the multi-image or gallery field (if configured in your collection schema) and upload additional photos.
  5. After uploading, click each image field and fill in the 'Alt text' input with a descriptive label.
  6. Publish your site and visit the product page URL to verify images render correctly.
Code · Generic HTML (html)
<figure class="product-gallery">
  <img
    src="/images/products/black-leather-wallet-front.jpg"
    alt="Black leather wallet with RFID blocking, front view"
    width="800"
    height="800"
    loading="eager"
  />
  <img
    src="/images/products/black-leather-wallet-open.jpg"
    alt="Black leather wallet open showing card slots and cash pocket"
    width="800"
    height="800"
    loading="lazy"
  />
</figure>

Basic responsive product image with descriptive alt text — place inside your product page HTML where the gallery/main image appears.

Seo product no spec tableModerate effort

Add a structured specifications table or list to every product page so shoppers, search engines, and AI tools can instantly find and use key product details.

On Webflow

  1. Open your Webflow project and navigate to the CMS Collection for Products (CMS → Collections → Products).
  2. Add new fields to the Product collection for each specification (e.g. 'Material' as a Plain Text field, 'Weight (kg)' as a Number field).
  3. Open the Product Template page in the Designer, add a Collection Field element in the product detail section, and bind each field to a table row. Build the table layout using Webflow's Div/Grid or an HTML Embed element.
  4. For a fully custom approach, add an HTML Embed element and write a <table> with Webflow CMS embed tags (e.g. {{wf {"path":"material"} }} ) to auto-populate specs from collection data.
  5. Set all table widths to percentage-based values or add overflow-x: auto on a wrapper div to ensure mobile responsiveness.
  6. Publish the project and verify specs appear on the live product page.
Seo product price not in htmlModerate effort

Render your product price in the server-generated HTML so Google and other crawlers can read it directly — without executing JavaScript.

On Webflow

  1. In Webflow Designer, open the Product Template page (eCommerce → Product Template).
  2. Ensure a Product Price element (Insert → eCommerce → Product Price) is placed on the canvas and bound to the product's Price field — Webflow outputs this as static HTML at publish time.
  3. Do NOT replace the native Price element with a plain text element whose content is set via custom JavaScript — this would make it JS-only.
  4. For JSON-LD structured data, Webflow does not auto-generate Product schema. Add it via Project Settings → Custom Code → Head Code or via a Page Settings → Custom Code block. Use a Webflow CMS-bound embed with dynamic fields if prices vary (see code snippet below).
  5. After publishing, view page source and confirm the price string is present in the HTML.
Seo product single imageModerate effort

Add at least 4 product images (multiple angles, detail shots, and lifestyle/in-use photos) to every product page to improve click-through rates, conversions, and Google Images visibility.

On Webflow

  1. Open the Webflow Designer → navigate to the CMS → Products collection → [select your product].
  2. In the product item editor, scroll to the 'Main Image' and 'More Images' (multi-image) fields.
  3. Click '+ Add Image' in the 'More Images' field and upload your additional product photos.
  4. For each image, click the settings gear/pencil icon and enter descriptive alt text in the 'Alt Text' input.
  5. Click 'Save Item' and then Publish the site. Ensure your product template's Designer layout references the 'More Images' field via a Lightbox Gallery element so all images display.
Code · Generic HTML (html)
<figure class="product-gallery">
  <!-- Main hero image -->
  <img
    src="/images/red-leather-wallet-front.webp"
    alt="Red leather bifold wallet — front view"
    width="2000"
    height="2000"
    loading="eager"
    fetchpriority="high"
  />
</figure>

<!-- Thumbnail strip -->
<ul class="product-thumbnails" role="list">
  <li>
    <img
      src="/images/red-leather-wallet-front-thumb.webp"
      alt="Red leather bifold wallet — front view"
      width="200"
      height="200"
      loading="lazy"
    />
  </li>
  <li>
    <img
      src="/images/red-leather-wallet-back-thumb.webp"
      alt="Red leather bifold wallet — back view"
      width="200"
      height="200"
      loading="lazy"
    />
  </li>
  <li>
    <img
      src="/images/red-leather-wallet-interior-thumb.webp"
      alt="Red leather bifold wallet — interior card slots"
      width="200"
      height="200"
      loading="lazy"
    />
  </li>
  <li>
    <img
      src="/images/red-leather-wallet-lifestyle-thumb.webp"
      alt="Red leather bifold wallet shown in a shirt pocket"
      width="200"
      height="200"
      loading="lazy"
    />
  </li>
</ul>

Basic accessible product image gallery markup. Place inside your product detail page template.

Seo product title genericModerate effort

Rewrite every product page title tag to include the specific product name plus at least one differentiating attribute (variant, material, brand, or model) so Google and shoppers can instantly tell what the page is about.

On Webflow

  1. Open the Webflow Designer and navigate to your CMS Products collection.
  2. Go to the CMS → Products collection list and open the product item.
  3. Find the 'SEO Settings' section within the item editor and update the 'Title Tag' field.
  4. To apply a template across all products, go to the Product Collection Template page in the Designer, open Page Settings (the gear icon), and set the Title Tag field to a dynamic binding such as: [Product Name] – [Category Field] – [Brand Field].
  5. Publish your site for changes to go live.
  6. For bulk updates, export the Products collection to CSV, edit the 'seo-title' column, and re-import.
Code · Generic HTML (html)
<head>
  <title>Magnetic Card Holder Phone Mount – MagSafe Compatible – BrandName</title>
  <meta name="description" content="Attach your cards securely to any MagSafe-compatible iPhone with BrandName's slim magnetic card holder mount. Holds 1–3 cards. Available in black and white.">
</head>

Place inside the <head> section of every product page. Follow the pattern: Product Name – Key Attribute – Brand.

Seo variant urls in sitemapModerate effort

Remove product variant URLs (e.g. ?variant= or ?sku= query strings) from your XML sitemap and keep only the canonical base product URL for each product.

On Webflow

  1. Go to Webflow Designer → Project Settings (gear icon) → SEO tab → 'Sitemap' section.
  2. Ensure 'Auto-generate sitemap' is enabled; Webflow's native sitemap lists only canonical Collection (product) page URLs without query parameters.
  3. If you are using Webflow Commerce with variant URLs appearing in the sitemap, check for third-party sitemap or SEO integrations under Integrations tab and disable variant URL inclusion.
  4. For custom sitemap files uploaded to Webflow (via Assets or hosting), open the XML file, delete any <loc> entries containing '?' parameters, and re-upload.
  5. Re-submit in Google Search Console after publishing changes.
Code · Generic HTML (xml)
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

  <!-- CORRECT: canonical base product URL -->
  <url>
    <loc>https://www.example.com/products/blue-sneaker</loc>
    <lastmod>2024-06-01</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>

  <!-- WRONG — never include variant query strings:
  <url>
    <loc>https://www.example.com/products/blue-sneaker?variant=12345</loc>
  </url>
  <url>
    <loc>https://www.example.com/products/blue-sneaker?sku=blue-large</loc>
  </url>
  -->

</urlset>

Correct sitemap entry — clean base product URL only, no query parameters.

Seo variant urls not canonicalizedModerate effort

Add a canonical tag to every product variant URL pointing back to the base product page, so Google consolidates ranking signals on one URL instead of splitting them across hundreds of duplicates.

On Webflow

  1. Open your Webflow project in the Designer. Navigate to your Product Template page (CMS → Collections → Products → open the template).
  2. Click the page settings icon (gear) for the Product Template → SEO Settings → Canonical URL field.
  3. Set the Canonical URL to the dynamic base product URL using a CMS field binding — bind it to the product's slug/URL so it always resolves to the clean product URL, never including query parameters.
  4. If your Webflow site uses query parameters for filtering or variant selection (common with custom code or third-party integrations), add a custom <link rel="canonical"> tag via the page's Custom Code section (under Page Settings → Custom Code → Inside <head> tag), pointing to the base product URL.
  5. Publish the site after making changes and verify via View Source on a product URL.
Code · Webflow (html)
<head>
  <!-- Webflow Product Template: canonical set via Page Settings > SEO > Canonical URL -->
  <!-- If adding via Custom Code instead, use the CMS slug to build the absolute URL -->
  <!-- Example for a store at www.example.com with products at /products/[slug] -->
  <!-- Bind the CMS slug field in the Webflow Designer instead of hardcoding -->
  <link rel="canonical" href="https://www.example.com/products/BIND-PRODUCT-SLUG-HERE">
</head>

Paste into the Product Template page settings → Custom Code → Inside <head> tag. Replace the slug binding with your CMS product URL field.

Server errorModerate effort

Identify and fix every page returning an HTTP 5xx server error so search engines can crawl and index those URLs again.

On Webflow

  1. In the Webflow Designer, open the Pages panel and click the affected page; use Preview to check if the error appears in the editor or only on the published site.
  2. Go to your Webflow Project Settings > Integrations and disable any recently added custom embed code or third-party script that may be causing a server-side error.
  3. For CMS Collection pages, go to the CMS panel > Collections, open the relevant collection, and verify required fields are not empty or misconfigured — a missing required binding can crash a template.
  4. Check Webflow Logic (if used) via the Logic tab for any workflow step throwing an error; disable the workflow temporarily and test.
  5. For infrastructure-level 5xx errors (Webflow's CDN/servers), contact Webflow Support at webflow.com/dashboard > Help — include the URL, time, and error code.
  6. After the fix, publish your site and submit the URL in Google Search Console > URL Inspection > Request Indexing.
Code · Generic HTML (html)
# Run this in your terminal to check the HTTP status code of any URL
curl -o /dev/null -s -w "HTTP Status: %{http_code}\n" https://www.example.com/your-page

# A healthy page returns:
# HTTP Status: 200

# A broken page returns (example):
# HTTP Status: 500

This is the correct HTTP 200 response header your server should return for a healthy page. Use curl on the command line to verify any URL's status code before and after your fix.

Slow pageModerate effort

Reduce your page load time to under 3 seconds by compressing images, eliminating render-blocking scripts and styles, and improving server response speed.

On Webflow

  1. In the Webflow Designer, open the Assets panel — select each image and confirm dimensions match the displayed size. Use the Image settings to enable lazy loading ('Load: Lazy') on below-the-fold images.
  2. Go to Project Settings → Assets → enable 'Auto-compress images' and choose WebP output if available on your plan.
  3. In Project Settings → Custom Code (or page-level Custom Code), review any scripts added to <head> or before </body> — move analytics/tracking to before </body> and add 'defer' where possible.
  4. In the Designer, review interactions and animations — delete any that are purely decorative and complex, as they add JS weight.
  5. Webflow hosts on Fastly CDN and handles GZIP automatically. Publish your site (Publish button) after any changes and re-test with PageSpeed Insights.
  6. For heavy pages, consider moving large background videos to a YouTube/Vimeo embed rather than a self-hosted video asset in Webflow.
Code · Webflow (html)
<!-- Preload hero/LCP image — update the href to your actual Webflow asset URL -->
<link
  rel="preload"
  as="image"
  href="https://uploads-ssl.webflow.com/your-site-id/your-hero-image.webp"
  type="image/webp"
  fetchpriority="high"
>

Paste into Project Settings → Custom Code → Head Code to preload your hero image for faster LCP.

Thin contentModerate effort

Add meaningful, original written content to pages that contain fewer than 300 words so Google has enough substance to rank and display them confidently.

On Webflow

  1. Open your Webflow project in the Designer. Navigate to the thin page using the Pages panel.
  2. For standard pages: click on the text element or add a new Rich Text element from the Add panel (the '+' icon) and write your expanded content.
  3. For CMS-driven product or blog pages: go to the CMS Collections panel → click on the collection (e.g. 'Products') → click the item to edit → expand the rich-text field (e.g. 'Description') and add detailed content. This propagates automatically to all pages using that CMS item's template.
  4. For collection list pages (category equivalents): open the collection page template in the Designer and add a Rich Text or plain Text element above or below the collection list, bound to a CMS field or written as static text.
  5. Publish the project after saving changes and verify the text appears in the live published site.
Code · Generic HTML (html)
<section class="product-description">
  <h2>About This Product</h2>
  <p>
    The Merino Wool Crew-Neck Sweater is crafted from 100% ethically sourced
    Grade-A Merino wool, making it naturally temperature-regulating, moisture-
    wicking, and soft enough to wear directly against the skin. Perfect for
    layering in autumn and winter, or as a standalone piece during mild spring
    evenings.
  </p>

  <h3>Key Features</h3>
  <ul>
    <li>100% Merino wool — no synthetic blends</li>
    <li>Available in sizes XS–3XL with a relaxed, unisex fit</li>
    <li>12 colours, each piece individually yarn-dyed for depth</li>
    <li>Ribbed cuffs and hem for a tailored finish</li>
    <li>Machine washable on a gentle 30°C wool cycle</li>
    <li>Certified by the Responsible Wool Standard (RWS)</li>
  </ul>

  <h3>Sizing &amp; Fit</h3>
  <p>
    This sweater is cut with a relaxed, slightly oversized silhouette. If you
    prefer a closer fit, we recommend sizing down. See our full <a href="/size-guide">size guide</a>
    for chest, waist, and sleeve measurements.
  </p>

  <h3>Care Instructions</h3>
  <p>
    Machine wash cold on a gentle/wool cycle. Lay flat to dry — do not tumble
    dry, iron at high heat, or dry-clean. With proper care, Merino wool retains
    its shape and softness for years.
  </p>

  <h3>Frequently Asked Questions</h3>
  <dl>
    <dt>Is Merino wool itchy?</dt>
    <dd>
      No. Merino fibres are finer than standard wool (under 19.5 microns),
      which means they bend against the skin rather than prick it, making this
      sweater comfortable even for people who normally find wool irritating.
    </dd>
    <dt>How does Merino wool compare to cashmere?</dt>
    <dd>
      Merino is more durable and easier to care for than cashmere, while still
      offering exceptional softness. It's an ideal everyday choice for those
      who want luxury without high maintenance.
    </dd>
    <dt>Can I return or exchange this item?</dt>
    <dd>
      Yes. We accept returns and exchanges within 30 days of delivery, provided
      the item is unworn and in its original packaging. See our
      <a href="/returns">returns policy</a> for full details.
    </dd>
  </dl>
</section>

Example of a well-structured, content-rich product description section with FAQ — paste into any HTML page template.

Title too longQuick win

Shorten your page title tag to 60 characters or fewer so it displays fully — without truncation — in Google search results.

On Webflow

  1. Open the Webflow Designer and select the page from the Pages panel (the page icon on the left sidebar).
  2. Click the gear icon next to the page name to open Page Settings.
  3. Under 'SEO Settings', edit the 'Title Tag' field to 60 characters or fewer.
  4. For CMS collection items (products, blog posts): open the CMS Collection, edit the item, and update the 'Title Tag' field in the item's settings, or update the title template in the Collection's settings to use a shorter dynamic field.
  5. Click Save and publish the site.
Code · Generic HTML (html)
<head>
  <meta charset="UTF-8">
  <title>Running Shoes for Men | YourBrand</title>
</head>

Place inside the <head> element of every HTML page. Keep the total character count at or under 60.

Title too shortQuick win

Expand your page title to between 30 and 60 characters so Google displays it fully in search results and shoppers can immediately understand what the page is about.

On Webflow

  1. Open your project in the Webflow Designer.
  2. For a static page: click the Pages panel (top left) → hover over the page → click the Settings gear icon → in the 'SEO Settings' section, update the 'Title Tag' field.
  3. For CMS collection items (e.g. products, blog posts): go to the CMS Collection settings → open the Collection item → find the 'Slug & SEO' section → update the 'Title Tag' field. You can also bind the title tag to a CMS field dynamically from the Collection Page template settings.
  4. For the homepage: Pages panel → Home → gear icon → update 'Title Tag' in SEO Settings.
  5. Publish the site after saving to push changes live.
  6. For bulk updates across many CMS items, export the CMS as a CSV, update the SEO title column, and re-import.
Code · Webflow (html)
<!-- Webflow generates this automatically from the Title Tag field in Page/Collection settings -->
<!-- Example of what Webflow outputs in the <head>: -->
<title>Women's Leather Tote Bags | Handcrafted – BrandName</title>

Webflow outputs the Title Tag field directly as the HTML <title>. For CMS-driven pages, bind the Title Tag field to a rich-text or plain-text CMS field in the Collection Page settings. No manual HTML editing needed — shown here for reference only.

Answer Engine Optimization · 17 fixes

Aeo schema client renderedModerate effort

Move your JSON-LD structured data from client-side JavaScript injection to server-rendered HTML so that AI crawlers and search engines can read it in the raw page source.

On Webflow

  1. In the Webflow Designer select the page you want to edit and open Page Settings (the gear icon next to the page name in the Pages panel).
  2. Scroll to 'Custom Code' → 'Head Code' section and paste your complete <script type="application/ld+json">…</script> block there — Webflow renders this server-side in the published HTML.
  3. For CMS Collection pages (e.g., product detail pages), open the Collection Template's Page Settings and use Webflow CMS field bindings inside the JSON-LD values — you can manually reference field slugs like {{Name}}, {{Price}} using Webflow's Embed element with CMS bindings, which are rendered server-side.
  4. Do NOT use Webflow Interactions or custom JavaScript to inject schema dynamically — this creates client-rendered schema invisible to AI crawlers.
  5. Publish the site and view the page source to confirm the JSON-LD block appears in raw HTML.
Code · Generic HTML (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Your Product Name",
  "description": "A clear description of your product.",
  "sku": "YOUR-SKU-123",
  "brand": {
    "@type": "Brand",
    "name": "Your Brand Name"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://www.example.com/products/your-product",
    "priceCurrency": "USD",
    "price": "49.99",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.7",
    "reviewCount": "128"
  }
}
</script>

Place this block inside <head> or <body> in the server-rendered HTML. All values must be populated by the server, not by JavaScript.

Article missing authorQuick win

Add a structured-data author field (schema.org Person) to every article or blog post so AI engines and Google can identify and trust who wrote the content.

On Webflow

  1. In the Webflow Designer, open your Blog Post Collection template page.
  2. Go to Page Settings (the gear icon for the CMS collection page) → Custom Code → Head Code.
  3. Paste the JSON-LD snippet there, using Webflow CMS field bindings (e.g. {{wf {"path":"author-name"} }}) to pull author name and URL dynamically from Collection fields.
  4. In your Blog Posts Collection, add fields: 'Author Name' (plain text), 'Author URL' (link), 'Author SameAs' (plain text, comma-separated).
  5. Reference those fields in the JSON-LD inside the Head Code block.
  6. Publish and validate with the Rich Results Test.
Code · Webflow (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "WEBFLOW_FIELD:post-title",
  "datePublished": "WEBFLOW_FIELD:published-date",
  "dateModified": "WEBFLOW_FIELD:updated-date",
  "author": {
    "@type": "Person",
    "name": "WEBFLOW_FIELD:author-name",
    "url": "WEBFLOW_FIELD:author-url",
    "sameAs": [
      "WEBFLOW_FIELD:author-linkedin",
      "WEBFLOW_FIELD:author-twitter"
    ]
  },
  "publisher": {
    "@type": "Organization",
    "name": "Your Store Name",
    "logo": {
      "@type": "ImageObject",
      "url": "https://www.yourstore.com/logo.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "WEBFLOW_FIELD:slug"
  }
}
</script>
<!-- NOTE: In the Webflow Designer, use the '+' embed field binding to replace each WEBFLOW_FIELD:xxx placeholder with the actual CMS field value before publishing. -->

Paste into the Blog Post Collection page's Page Settings → Custom Code → Head Code section. Replace the {{wf ...}} tokens with your actual Webflow CMS field embed codes via the Designer's field-binding interface.

Faq schema not visibleModerate effort

Render all FAQ answers in the server-delivered HTML so that both Google and your FAQ structured data can see every question and answer — not just the one currently open in a JavaScript accordion.

On Webflow

  1. Open the FAQ page in the Webflow Designer.
  2. Check your accordion component: Webflow's native Tabs/Accordion component renders all panel HTML in the DOM (visibility is controlled by CSS classes) — verify by previewing and viewing source. If all answer text is in the source HTML, only the JSON-LD step is needed.
  3. To add FAQPage JSON-LD, go to Page Settings (the gear icon on the page in the Pages panel) → Custom Code → Inside <head> tag, and paste the FAQPage JSON-LD <script> block.
  4. If you use a custom-coded accordion that uses JavaScript to inject answer text, refactor it to render all answers in static HTML and hide/show with CSS only.
  5. Publish and validate with the Rich Results Test.
Code · Webflow (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "What is your return policy?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "We accept returns within 30 days of purchase. Items must be unused and in original packaging."
      }
    },
    {
      "@type": "Question",
      "name": "How long does shipping take?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Standard shipping takes 3–5 business days. Express shipping (1–2 business days) is available at checkout."
      }
    },
    {
      "@type": "Question",
      "name": "Do you ship internationally?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Yes, we ship to over 40 countries. International orders typically arrive within 7–14 business days."
      }
    },
    {
      "@type": "Question",
      "name": "Can I change or cancel my order?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Orders can be changed or cancelled within 1 hour of placement. After that, please use our return process."
      }
    }
  ]
}
</script>

Paste into Page Settings → Custom Code → Inside <head> tag for the FAQ page in Webflow Designer.

Invalid rating valueModerate effort

Fix the AggregateRating.ratingValue in your structured data so it is a valid number between 1 and 5 (or your declared scale's minimum and maximum).

On Webflow

  1. Webflow does not inject AggregateRating structured data automatically — it must be added manually via an Embed element or custom code.
  2. Open the product template page in the Webflow Designer (CMS → Products → open the product template).
  3. Locate any HTML Embed element containing JSON-LD structured data. Find the ratingValue field.
  4. Replace any hardcoded value (especially 0) with a CMS-bound dynamic field: click the Embed element, open the field binding picker, and connect ratingValue to a Number field in your Products CMS collection that stores the actual average rating.
  5. Add a conditional visibility rule (using Webflow's Conditional Visibility or a surrounding Collection List) so the entire AggregateRating block is only rendered when the review count field is greater than 0.
  6. Publish the site and test in the Google Rich Results Test.
Code · Webflow (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "PRODUCT-NAME-FIELD",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "YOUR_RATING_VALUE",
    "bestRating": "5",
    "worstRating": "1",
    "reviewCount": "YOUR_REVIEW_COUNT"
  }
}
</script>

Paste into an HTML Embed element inside your Product Template in the Webflow Designer. Bind 'YOUR_RATING_VALUE' and 'YOUR_REVIEW_COUNT' to CMS Number fields using Webflow's field picker. Use Conditional Visibility on the Embed element so it only renders when review count > 0.

Low answer first scoreModerate effort

Restructure page content to lead with a direct, concise answer, use question-format headings, and add FAQ structured data so search engines and AI assistants can surface your answers prominently.

On Webflow

  1. Open your project in the Webflow Designer and navigate to the page.
  2. Select the first content element in the main section; rewrite it as a 160-character answer summary and ensure it sits above all other body text in the element tree.
  3. For headings: click each H2/H3 element, switch to the text editor, and rewrite the copy as a question.
  4. To add FAQPage JSON-LD: click the page gear icon (Page Settings) → Custom Code → 'Before </body> tag' field → paste your JSON-LD <script> block.
  5. For CMS-driven pages (e.g., product or blog templates), add the JSON-LD script in the template's Page Settings under Custom Code so it applies to all collection items.
  6. Publish the site and validate with Google's Rich Results Test.
Code · Generic HTML (html)
<!-- Answer-first page structure template -->

<!-- 1. Direct-answer summary (≤160 characters) -->
<p class="answer-summary">
  [Product Name] does [core benefit] by [key mechanism] — ideal for [target customer].
</p>

<!-- 2. Question-format H2 subheading -->
<h2>What does [Product Name] do?</h2>
<p>Direct 1–2 sentence answer here. Then expand with supporting detail below.</p>

<!-- 3. Another question-format heading -->
<h2>Who is [Product Name] designed for?</h2>
<p>Direct answer. Then elaborate.</p>

<!-- 4. FAQ section (visible on page — required for FAQPage schema) -->
<section id="faq">
  <h2>Frequently Asked Questions</h2>

  <h3>How long does shipping take?</h3>
  <p>Standard shipping takes 3–5 business days. Express options are available at checkout.</p>

  <h3>What is your return policy?</h3>
  <p>We accept returns within 30 days of delivery on unused items in original packaging.</p>

  <h3>Is [Product Name] compatible with [common concern]?</h3>
  <p>Yes. [One-sentence direct answer with any important caveats.]</p>
</section>

<!-- 5. FAQPage JSON-LD (copy the JSON-LD snippet above and paste here or in <head>) -->

Page content structure template — place this as the opening content block on any product, category, or landing page.

Low review countModerate effort

Collect at least 5 published reviews so Google will display star ratings in search results alongside your product listings.

On Webflow

  1. Webflow Ecommerce does not have a built-in review system, so add a third-party review widget: embed 'Judge.me for Webflow', 'Elfsight Product Reviews', or use a custom embed code from Yotpo.
  2. Most third-party widgets inject their own AggregateRating JSON-LD — confirm this is happening by inspecting the page source after setup.
  3. If using a custom solution, add the JSON-LD structured data block manually via a Webflow Embed element on the Product Template page (see code snippet below).
  4. Set up post-purchase review request emails within your chosen review platform's dashboard.
  5. Once 5+ reviews are collected and displaying, test the product page URL in Google's Rich Results Test.
Code · Generic HTML (html)
<!-- Product structured data with AggregateRating -->
<!-- Note: Google will only show review stars when reviewCount >= 5 -->
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Your Product Name",
  "image": [
    "https://example.com/images/product-front.jpg",
    "https://example.com/images/product-side.jpg"
  ],
  "description": "A clear, accurate description of your product.",
  "sku": "SKU-12345",
  "mpn": "MPN-12345",
  "brand": {
    "@type": "Brand",
    "name": "Your Brand"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.5",
    "reviewCount": "12",
    "bestRating": "5",
    "worstRating": "1"
  },
  "offers": {
    "@type": "Offer",
    "url": "https://example.com/products/your-product",
    "priceCurrency": "USD",
    "price": "49.99",
    "priceValidUntil": "2026-12-31",
    "itemCondition": "https://schema.org/NewCondition",
    "availability": "https://schema.org/InStock",
    "seller": {
      "@type": "Organization",
      "name": "Your Store Name"
    }
  }
}
</script>

Drop this complete block into the <head> section of any product page. Swap in your real values for name, image, SKU, price, ratingValue, and reviewCount.

Missing schema articleModerate effort

Add Article schema markup (JSON-LD) to every blog post and editorial page so Google can identify and rich-result-enhance your content in search.

On Webflow

  1. Open your Webflow project and navigate to the CMS Blog Post Collection Template page in the Designer.
  2. Add an 'Embed' element (from the Add panel → Components → Embed) inside the page template, ideally near the top of the layout.
  3. In the Embed element's code editor, paste the JSON-LD script block. Use Webflow's dynamic CMS fields by clicking 'Add Field' within the embed to insert variables like post name, author, publish date, and modified date.
  4. Publish the site, then validate a live blog post URL with the Rich Results Test.
Code · Webflow (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "[CMS Field: Post Name]",
  "description": "[CMS Field: Post Summary]",
  "image": {
    "@type": "ImageObject",
    "url": "[CMS Field: Main Image URL]"
  },
  "author": {
    "@type": "Person",
    "name": "[CMS Field: Author Name]"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Your Store Name",
    "logo": {
      "@type": "ImageObject",
      "url": "https://www.example.com/logo.png"
    }
  },
  "datePublished": "[CMS Field: Published Date]",
  "dateModified": "[CMS Field: Updated Date]",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "[CMS Field: Slug / Full URL]"
  },
  "url": "[CMS Field: Slug / Full URL]"
}
</script>

Add an Embed element to your Blog Post Collection Template page in the Webflow Designer. Use the 'Add Field' button inside the embed editor to replace the placeholder text with dynamic CMS fields.

Missing schema breadcrumblistModerate effort

Add BreadcrumbList structured data (JSON-LD) to every page so Google can display your site's navigation path directly in search results.

On Webflow

  1. In the Webflow Designer, select the page (e.g. your Product Template or Category Template CMS collection page).
  2. Open Page Settings (gear icon) › Custom Code › Inside <head> tag section.
  3. Paste the JSON-LD script block; use Webflow's CMS field embed syntax — wrap the script in an Embed element on the canvas and bind dynamic fields (e.g. Name field, Slug field) to the name and item values so every generated page has correct breadcrumb data.
  4. For collection pages, add the Embed element inside the CMS Collection List wrapper and connect it to collection fields.
  5. Publish and validate with Google's Rich Results Test.
Code · Webflow (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://www.example.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "CATEGORY_NAME_FIELD",
      "item": "https://www.example.com/CATEGORY_SLUG_FIELD/"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "PRODUCT_NAME_FIELD",
      "item": "https://www.example.com/products/PRODUCT_SLUG_FIELD"
    }
  ]
}
</script>
<!-- Replace CATEGORY_NAME_FIELD etc. with Webflow CMS Embed bindings when placing this in an Embed element inside a Collection List. -->

Paste into Page Settings › Custom Code › Inside <head> tag for your CMS Collection template page. Replace static values with Webflow CMS field embeds.

Missing schema faqpageQuick win

Add FAQPage schema markup to any page that contains a Frequently Asked Questions section so Google can display your answers as rich results directly in search.

On Webflow

  1. In the Webflow Designer, open the Page Settings for your FAQ page by clicking the Pages panel → hover over the page → click the gear icon.
  2. Scroll to the 'Custom Code' section at the bottom of Page Settings.
  3. Paste your JSON-LD <script> block into the 'Inside <head> tag' field and click Save.
  4. Publish your site for the changes to go live.
  5. Validate with Google's Rich Results Test using the published URL.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Frequently Asked Questions | Your Store</title>
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [
      {
        "@type": "Question",
        "name": "What is your return policy?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "We accept returns within 30 days of purchase. Items must be unused and in their original packaging."
        }
      },
      {
        "@type": "Question",
        "name": "How long does shipping take?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Standard shipping takes 5–7 business days. Expedited options are available at checkout."
        }
      }
    ]
  }
  </script>
</head>
<body>
  <h1>Frequently Asked Questions</h1>
  <h2>What is your return policy?</h2>
  <p>We accept returns within 30 days of purchase. Items must be unused and in their original packaging.</p>
  <h2>How long does shipping take?</h2>
  <p>Standard shipping takes 5–7 business days. Expedited options are available at checkout.</p>
</body>
</html>

Full HTML page example showing exactly where to place the FAQPage script block inside the <head>.

Missing schema howtoModerate effort

Add HowTo structured data (JSON-LD) to any page that walks customers through a multi-step process, so Google can display your instructions as rich results in search.

On Webflow

  1. In the Webflow Designer, select the specific page in the Pages panel and click the gear icon to open Page Settings.
  2. Scroll to 'Custom Code' > 'Inside <head> tag' section and paste your complete JSON-LD HowTo block there.
  3. Alternatively, for a CMS Collection page (e.g. a blog post template), open the Collection template page settings the same way and use Webflow's dynamic data bindings to pull step content from CMS fields.
  4. Publish the site and validate with Google's Rich Results Test.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>How to Complete Your Task | Your Store</title>
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "HowTo",
    "name": "How to Complete Your Task",
    "description": "Follow these steps to complete your task easily.",
    "totalTime": "PT10M",
    "step": [
      {
        "@type": "HowToStep",
        "position": 1,
        "name": "Prepare your materials",
        "text": "Gather everything you need before you start: list materials here."
      },
      {
        "@type": "HowToStep",
        "position": 2,
        "name": "Complete the main action",
        "text": "Describe exactly what the user should do in this core step."
      },
      {
        "@type": "HowToStep",
        "position": 3,
        "name": "Verify the result",
        "text": "Explain how the user confirms the task was completed successfully."
      }
    ]
  }
  </script>
</head>
<body>
  <!-- Your how-to page content -->
</body>
</html>

Place this block inside the <head> element of any static HTML page that contains step-by-step instructions.

Missing schema localbusinessQuick win

Add LocalBusiness JSON-LD structured data to your store so search engines and AI answer engines can surface your business name, address, phone number, and hours in rich results and direct answers.

On Webflow

  1. In the Webflow Designer, open your Project Settings (the gear icon in the top-left).
  2. Go to the 'Custom Code' tab.
  3. Paste your complete JSON-LD <script> block into the 'Head Code' field — this adds it to every published page.
  4. Click 'Save Changes', then publish your site.
  5. For page-specific placement, select the target page in the Pages panel, click the gear icon next to it, and paste the block into 'Custom Code → Inside <head> tag'.
  6. Validate with Google's Rich Results Test after publishing.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Your Store</title>
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Store",
    "name": "Your Business Name",
    "url": "https://www.yourdomain.com",
    "logo": "https://www.yourdomain.com/logo.png",
    "address": {
      "@type": "PostalAddress",
      "streetAddress": "123 Main Street",
      "addressLocality": "Springfield",
      "addressRegion": "IL",
      "postalCode": "62701",
      "addressCountry": "US"
    },
    "telephone": "+1-217-555-0100",
    "openingHoursSpecification": [
      {
        "@type": "OpeningHoursSpecification",
        "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"],
        "opens": "09:00",
        "closes": "18:00"
      }
    ]
  }
  </script>
</head>
<body>
  <!-- page content -->
</body>
</html>

Paste this block anywhere inside the <head> element of your HTML page. Replace all placeholder values before deploying.

Missing schema organizationQuick win

Add an Organization schema markup block to your store's homepage (and/or sitewide template) so search engines and AI assistants can definitively identify your brand.

On Webflow

  1. In the Webflow Designer, open your Project Settings (the gear icon at the top left).
  2. Go to the 'Custom Code' tab.
  3. Paste your complete <script type='application/ld+json'>…</script> Organization block into the 'Head Code' field.
  4. Click Save Changes, then publish your site.
  5. Verify using the Rich Results Test on your published domain.
Code · Generic HTML (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Acme Store",
  "url": "https://www.acmestore.com",
  "logo": {
    "@type": "ImageObject",
    "url": "https://www.acmestore.com/images/logo.png",
    "width": 250,
    "height": 250
  },
  "sameAs": [
    "https://www.facebook.com/acmestore",
    "https://www.instagram.com/acmestore",
    "https://twitter.com/acmestore",
    "https://www.linkedin.com/company/acmestore",
    "https://www.youtube.com/c/acmestore"
  ],
  "contactPoint": {
    "@type": "ContactPoint",
    "contactType": "customer service",
    "email": "hello@acmestore.com"
  }
}
</script>

Drop this complete block inside your <head>…</head> section on every page. Replace all placeholder values.

Missing schema productModerate effort

Add Product schema markup (JSON-LD) to every product page so search engines can display rich results — including price, availability, and ratings — directly in search.

On Webflow

  1. Open your Webflow project and navigate to the product template page (the CMS Collection page bound to your Products collection).
  2. Add an 'Embed' element (the HTML Embed widget) inside the page — place it anywhere on the canvas (it will be invisible to visitors).
  3. Paste the JSON-LD script block into the Embed element, using Webflow's CMS field bindings to dynamically pull in product Name, Price, SKU, Image, and Description from your Products collection fields.
  4. Publish the site, then validate a live product page URL with Google's Rich Results Test.
Missing schema webpageModerate effort

Add WebPage schema markup to every page so search engines and AI answer engines can precisely understand and describe your content.

On Webflow

  1. In the Webflow Designer, open Project Settings → Custom Code tab.
  2. Paste a global WebPage JSON-LD <script> block in the 'Head Code' section for site-wide injection.
  3. For per-page dynamic schema, select a specific page in the Pages panel → click the gear icon → 'Custom Code' → 'Head Code', and paste the page-specific JSON-LD there.
  4. For CMS-driven pages (blog posts, landing pages), open the Collection template page, go to its Page Settings → Custom Code, and use Webflow CMS field embed variables (e.g., {{Name}}, {{Slug}}) inside a custom HTML Embed element placed in the body to output dynamic JSON-LD.
  5. Publish your site and validate with Google's Rich Results Test.
Code · Webflow (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "WebPage",
  "name": "{{Name}}",
  "description": "{{Meta Description}}",
  "url": "https://www.example.com/pages/{{Slug}}/",
  "inLanguage": "en-US",
  "isPartOf": {
    "@type": "WebSite",
    "name": "My Store",
    "url": "https://www.example.com/"
  }
}
</script>

For Webflow CMS Collection pages, place this inside an HTML Embed element in the page body. Replace {{Name}} and {{Slug}} with your actual CMS field names as shown in the Webflow field picker.

Non iso dateQuick win

Change every datePublished (and dateModified) value in your structured data from a human-readable string to a strict ISO-8601 date — formatted as YYYY-MM-DD or YYYY-MM-DDThh:mm:ss+hh:mm — so search engines can reliably parse your content's freshness.

On Webflow

  1. Open your Webflow project in the Designer. For CMS-powered pages (Blog Posts, etc.), go to the CMS Collection settings and ensure the Publish Date field is a Date field type (not plain text).
  2. In the page template (e.g., Blog Post template), add or locate a JSON-LD Embed element containing your schema.
  3. Bind the date field to the JSON-LD using a CMS embed with the correct format. In Webflow's embed code, use the CMS date field connected via the 'Add Field' button and select the ISO 8601 format option.
  4. Webflow's date fields output in ISO-8601 when you select the 'YYYY-MM-DD' or full datetime format in the embed binding dialog.
  5. For static pages with hardcoded JSON-LD, select the embed element, click Edit Code, and manually correct the date string to YYYY-MM-DD.
  6. Publish the site and confirm via Google's Rich Results Test.
Code · Generic HTML (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "Your Article Headline",
  "datePublished": "2025-03-12",
  "dateModified": "2025-03-12",
  "author": {
    "@type": "Person",
    "name": "Author Name"
  }
}
</script>

Minimum viable JSON-LD block to paste into any HTML <head> — replace values with your own.

Org missing sameasQuick win

Add a `sameAs` array to your Organization schema, linking to your verified social profiles, Wikipedia page, LinkedIn, and other authoritative sources so search engines can confidently identify your brand as a known entity.

On Webflow

  1. In the Webflow Designer, open your Project Settings (gear icon) → Custom Code tab.
  2. Paste the Organization JSON-LD block (with sameAs) into the 'Head Code' field under 'Global Code Injection'.
  3. Click Save Changes and then Publish your site.
  4. View source on your live site to confirm the schema appears in the <head>, then validate with Google's Rich Results Test.
Code · Generic HTML (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "name": "Your Brand Name",
  "url": "https://www.yourdomain.com",
  "logo": "https://www.yourdomain.com/images/logo.png",
  "sameAs": [
    "https://www.linkedin.com/company/your-company",
    "https://www.facebook.com/yourpage",
    "https://twitter.com/yourhandle",
    "https://www.instagram.com/yourhandle",
    "https://www.youtube.com/@yourchannel",
    "https://en.wikipedia.org/wiki/Your_Brand",
    "https://www.wikidata.org/wiki/QXXXXXXX"
  ]
}
</script>

Paste this directly before the closing </head> tag in your HTML template. Remove any sameAs entries for platforms your brand does not have a presence on.

Page unreachableModerate effort

Ensure every page that should appear in AI-powered answers and rich results is actually reachable by crawlers, then add structured data so search engines and AI systems can read and surface its content.

On Webflow

  1. In the Webflow Designer, confirm the page is published: go to Pages panel → right-click the page → Page Settings → ensure it is not set to 'Exclude from search index' (noindex) and is published in the current project.
  2. Check Project Settings → SEO → confirm 'Disable Webflow subdomain indexing' or any global noindex setting is not accidentally applied to production.
  3. For broken CMS collection item URLs, check the CMS Collections panel — confirm the item is set to 'Published' (not 'Draft' or 'Archived') and the slug matches the expected URL.
  4. For redirects, go to Project Settings → Hosting → Redirects and add a 301 redirect from the broken path to the correct live URL.
  5. To add structured data, open the page in the Designer → Add an Embed element (from the Add panel → Components → Embed) → paste your JSON-LD <script> block directly. For CMS-driven pages, use dynamic binding inside the Embed element to pull in product name, price, etc. from Collection fields.
  6. Publish the site, then submit in Google Search Console and verify with the Rich Results Test.

Accessibility (WCAG) · 66 fixes

Aria allowed attrModerate effort

Audit every element that uses ARIA attributes and ensure each attribute is permitted for that element's role — removing or correcting any mismatched aria-* attributes.

On Webflow

  1. Open your project in the Webflow Designer.
  2. Select the flagged element on the canvas. Open the Element Settings panel (gear icon, ⚙) and scroll to the 'Custom Attributes' section.
  3. Find the mismatched aria-* attribute listed there. Either delete it, change the attribute name to one that is valid for the element's role, or change the element's role attribute to one that supports the aria-* attribute.
  4. If the attribute is being set by custom JavaScript in the project's Custom Code section (Project Settings → Custom Code or page-level Embed), find and correct it there.
  5. Publish the site and verify with the axe DevTools browser extension.
Code · Webflow (html)
<!-- Example: a Webflow Div Block used as a disclosure button -->
<!-- In Custom Attributes panel, set: -->
<!--   Name: role          Value: button        -->
<!--   Name: aria-expanded Value: false         -->
<!--   Name: tabindex      Value: 0             -->

<!-- Do NOT add aria-checked or aria-selected to role="button" -->
<!-- Those attributes are only valid on checkbox/radio/option/tab roles -->

<!-- If you need a toggle button, use aria-pressed instead: -->
<!--   Name: aria-pressed  Value: false         -->

In Webflow Designer → select the element → Element Settings (gear) → Custom Attributes. Remove any invalid attribute and add only permitted ones as shown.

Aria allowed roleQuick win

Remove or replace any ARIA `role` attribute that is not permitted on the HTML element it is applied to — in particular, do not add `role="textbox"` (or other interactive roles) to elements that already have a native semantic role which conflicts with it.

On Webflow

  1. Open the Webflow Designer and select the element flagged by the scanner.
  2. In the right-hand settings panel, click the Element Settings gear icon and scroll to the Custom Attributes section.
  3. Find the `role` attribute entry and either delete it entirely (if the native element semantics are sufficient) or change its value to a role permitted for this element type.
  4. If the element type itself is wrong (e.g. a Div Block where an Input should be used), replace it with the correct Webflow element (Form → Text Input, etc.) and remove any conflicting ARIA attributes.
  5. Publish and re-audit with axe DevTools.
Code · Webflow (html)
<!-- Resulting published HTML after removing the invalid custom attribute in Webflow Designer -->
<!-- Before Webflow publishes, ensure no custom attribute panel entry sets role="textbox" on a non-input element -->

<!-- Clean output for a text input in Webflow -->
<label for="contact-name">Your Name</label>
<input class="w-input" type="text" id="contact-name" name="contact-name" placeholder="Jane Smith">

In the Webflow Designer, open Element Settings → Custom Attributes for the flagged element and delete the invalid 'role' entry. No code file to edit — shown here as the resulting clean HTML Webflow will publish.

Aria command nameModerate effort

Give every button, link, and menuitem a clear, accessible name so screen readers can announce what it does.

On Webflow

  1. Open your Webflow project in the Designer.
  2. Select the flagged element (button, link block, or navbar toggle) in the canvas or Navigator panel.
  3. In the right-hand Settings panel (the gear icon), scroll to the 'Accessibility' section.
  4. In the 'Label (aria-label)' field, type the descriptive name for the element (e.g., 'Open mobile menu', 'View shopping cart').
  5. For link blocks wrapping an SVG or image icon, also set the 'Alt text' on the image element inside the link, or add a visually-hidden text element: add a Text element inside the link block, give it the class `sr-only`, then define `.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }` in your global CSS.
  6. Publish and re-test with axe.
Code · Webflow (css)
/* Webflow: visually-hidden utility class for accessible labels */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

Add to your Webflow project's global CSS (Designer → Style Manager → Add Global CSS class, or via Custom Code). Then apply the 'sr-only' class to any visually-hidden label spans.

Aria dialog nameModerate effort

Add a visible or programmatic accessible name to every dialog element so screen readers can announce what the dialog is about when it opens.

On Webflow

  1. In the Webflow Designer, click on the modal/dialog element in the Navigator panel to select it.
  2. Open the Element Settings panel (the gear icon on the right) and scroll to the Custom Attributes section.
  3. If the dialog has a visible heading inside it: give the heading element an HTML id (e.g., modal-title) via its own Custom Attributes, then add the attribute aria-labelledby with value modal-title to the dialog wrapper element.
  4. If there is no visible heading, add the attribute aria-label with a descriptive value (e.g., 'Newsletter signup') directly to the dialog wrapper element.
  5. Publish the site and confirm the fix using the axe DevTools browser extension.
Code · Webflow (html)
<!-- Custom Attributes to add in Webflow Designer on the modal wrapper -->
<!-- Attribute 1 -->
Name:  role
Value: dialog

<!-- Attribute 2 -->
Name:  aria-modal
Value: true

<!-- Attribute 3 (preferred — if a heading with id="modal-title" exists inside) -->
Name:  aria-labelledby
Value: modal-title

<!-- Attribute 3 (alternative — when no heading is present) -->
Name:  aria-label
Value: Newsletter signup

Set these as Custom Attributes in the Webflow Designer on the dialog wrapper element (Element Settings → Custom Attributes). No code file needed.

Aria hidden focusModerate effort

Remove `aria-hidden="true"` from any element that contains focusable children (links, buttons, inputs), or remove those focusable children from the keyboard tab order so they cannot receive focus while hidden from assistive technology.

On Webflow

  1. Open your Webflow project in the Designer. Select the element flagged as aria-hidden (use the Navigator panel to locate it in the DOM tree).
  2. In the Element Settings panel (the gear icon), scroll to the Custom Attributes section. If aria-hidden='true' is set there and the element contains interactive children, either remove the aria-hidden attribute or also set tabindex='-1' on each focusable child element the same way.
  3. For Navbar dropdowns, off-canvas menus, and modals built with Webflow Interactions: open the Interaction that hides/shows the panel and add a custom code step (or use the 'Set attribute' action if available) to toggle aria-hidden and tabindex='-1' together as the interaction plays.
  4. For finer control, add a <script> tag via Project Settings → Custom Code → Footer Code to handle the open/close logic in JavaScript (see code snippet below).
  5. Publish the project and re-run axe DevTools or Lighthouse to verify.
Aria input field nameModerate effort

Add a descriptive accessible name (aria-label, aria-labelledby, or a visible <label>) to every ARIA input field so assistive technologies can identify it.

On Webflow

  1. Open the Webflow Designer and click the input or form element that is flagged.
  2. In the right-hand Settings panel (the gear icon), scroll to the 'Accessibility' section. Fill in the 'Custom Attribute' fields: Name = aria-label, Value = your descriptive label text (e.g. 'Email address').
  3. Alternatively, if a visible label exists, ensure the label's 'For' attribute matches the input's 'ID' — set both in the Settings panel to create a proper label association without needing aria-label.
  4. For Webflow's native Form Block fields, click the label element inside the form and ensure it is not set to 'Display: None' in the Style panel — hiding labels visually removes them for sighted users but assistive tech may still read them; use the visually-hidden CSS technique instead if you need to hide labels.
  5. Publish and verify with axe DevTools.
Code · Webflow (html)
<input
  type="text"
  id="email-field"
  name="email"
  aria-label="Email address"
  placeholder="Enter your email"
  class="w-input"
>

In Webflow Designer, add a Custom Attribute to the input element: Name = aria-label, Value = your label text. This is the rendered HTML output.

Aria meter nameQuick win

Add a descriptive accessible name (via aria-label, aria-labelledby, or a visible <label>) to every element with role="meter" so screen readers can announce what the meter represents.

On Webflow

  1. Open your project in the Webflow Designer and select the element that has role="meter" (or the <meter> element).
  2. In the right-hand Element Settings panel (the gear icon), scroll to the Custom Attributes section.
  3. Click the + button to add a new attribute: Name = aria-label, Value = [your descriptive label, e.g. 'Average customer rating'].
  4. Also add aria-valuenow, aria-valuemin, and aria-valuemax as separate custom attributes with correct numeric values.
  5. Publish the site and verify with the axe DevTools browser extension in your browser.
Code · Webflow (html)
<!-- Custom Attributes to add in Webflow Designer for the meter element:

Name: aria-label
Value: Average customer rating

Name: aria-valuenow
Value: 3.8

Name: aria-valuemin
Value: 0

Name: aria-valuemax
Value: 5

If using a div with role="meter", also add:
Name: role
Value: meter
-->

<!-- Equivalent rendered HTML (for reference): -->
<div
  role="meter"
  aria-label="Average customer rating"
  aria-valuenow="3.8"
  aria-valuemin="0"
  aria-valuemax="5"
  class="rating-meter">
</div>

Add these as Custom Attributes in the Webflow Designer Element Settings panel for the meter element. No template file needed.

Aria progressbar nameQuick win

Add an accessible name (aria-label or aria-labelledby) to every progress bar element so screen readers can announce its purpose.

On Webflow

  1. In the Webflow Designer, click the progress bar element (often a styled div with a nested fill div, or a Webflow-native Progress Bar element).
  2. In the right-hand Settings panel (the gear icon), scroll to the Custom Attributes section.
  3. Click the '+' button to add a new attribute: set Name to 'aria-label' and Value to your descriptive label (e.g. 'Checkout progress').
  4. If the bar uses role='progressbar', also add aria-valuemin, aria-valuemax, and aria-valuenow as custom attributes with appropriate numeric values.
  5. If the progress bar is inside a Symbol (reusable component), open the Symbol and make the edit there so all instances are updated automatically.
  6. Publish your site and verify with the axe DevTools browser extension.
Code · Webflow (html)
<!-- Equivalent HTML Webflow produces after adding custom attributes -->
<!-- In Designer: add role=progressbar, aria-label=Checkout progress,
     aria-valuenow=2, aria-valuemin=1, aria-valuemax=4 -->
<div
  class="progress-bar-track"
  role="progressbar"
  aria-label="Checkout progress"
  aria-valuenow="2"
  aria-valuemin="1"
  aria-valuemax="4"
>
  <div class="progress-bar-fill" style="width: 50%;"></div>
</div>

In Webflow Designer, select the progress bar element → Settings panel → Custom Attributes. Add each attribute as a Name/Value pair. This is the equivalent markup Webflow produces.

Aria prohibited attrModerate effort

Remove or replace any ARIA attributes that are prohibited for the role assigned to each HTML element, so assistive technologies receive accurate, unambiguous information.

On Webflow

  1. Open your project in the Webflow Designer.
  2. Select the element flagged with the prohibited attribute in the Navigator panel.
  3. Open the Element Settings panel (D key) and scroll to the Custom Attributes section.
  4. Locate the prohibited ARIA attribute in the list, then either delete it (click the '−' icon) or change it to the correct, permitted attribute.
  5. If the attribute is set inside an Embed component, double-click the embed and edit the raw HTML directly.
  6. Publish and retest with axe DevTools.
Code · Webflow (html)
<!-- ❌ Before: aria-expanded on a non-interactive div (prohibited) -->
<!-- <div class="faq__question" aria-expanded="false">What is your return policy?</div> -->

<!-- ✅ After: use a <button> which natively supports aria-expanded -->
<button
  class="faq__question"
  aria-expanded="false"
  aria-controls="faq-answer-1"
  onclick="
    var expanded = this.getAttribute('aria-expanded') === 'true';
    this.setAttribute('aria-expanded', String(!expanded));
    document.getElementById('faq-answer-1').hidden = expanded;
  "
>
  What is your return policy?
</button>
<div id="faq-answer-1" hidden>
  We accept returns within 30 days of purchase in original condition.
</div>

For Webflow Embed components — paste corrected HTML directly into the Embed element in the Designer.

Aria required attrModerate effort

Add all required ARIA attributes (such as aria-expanded and aria-controls) to every interactive element that uses an ARIA role, so assistive technologies can communicate widget state to users.

On Webflow

  1. Open your project in the Webflow Designer and select the element that triggers the widget (e.g. the button or link that opens a dropdown or accordion panel).
  2. In the right-hand Element Settings panel (the gear icon), scroll to the Custom Attributes section and click the '+' button.
  3. Add the attribute name aria-expanded and set its value to false (or true if the widget starts open).
  4. Add a second custom attribute: aria-controls with the value set to the HTML ID of the panel it controls (set that ID on the target element via its Element Settings as well).
  5. Open the Interactions panel and, within your existing open/close interaction, add a 'Set Attribute' action that changes aria-expanded to 'true' on open and 'false' on close.
  6. Publish the site and verify with the axe DevTools browser extension.
Code · Webflow (javascript)
<script>
  document.addEventListener('DOMContentLoaded', function () {
    // Patch Webflow dropdown toggles
    document.querySelectorAll('.w-dropdown-toggle').forEach(function (toggle) {
      if (!toggle.hasAttribute('aria-expanded')) {
        toggle.setAttribute('aria-expanded', 'false');
      }
      var list = toggle.parentElement.querySelector('.w-dropdown-list');
      if (list) {
        if (!list.id) {
          list.id = 'dd-' + Math.random().toString(36).slice(2, 9);
        }
        toggle.setAttribute('aria-controls', list.id);
      }

      var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
          if (mutation.attributeName === 'class') {
            var isOpen = toggle.classList.contains('w--open');
            toggle.setAttribute('aria-expanded', String(isOpen));
          }
        });
      });
      observer.observe(toggle, { attributes: true });
    });
  });
</script>

Paste into your Webflow page's </body> custom code area (Page Settings → Custom Code → Before </body> tag) to patch Webflow Dropdown or tab components.

Aria required childrenModerate effort

Add the required child ARIA roles (e.g. `role="option"` inside `role="listbox"`, or `role="group"`/`role="option"` inside `role="combobox"`) so every composite ARIA widget contains the child roles the ARIA specification demands.

On Webflow

  1. Open the page in the Webflow Designer and select the container element that has the incorrect ARIA role (e.g. a Div with a custom role attribute).
  2. In the right-hand panel open Element Settings (the gear icon) → Custom Attributes section.
  3. Select each child element (e.g. each item Div inside the container) and add a custom attribute: Name = role, Value = option (or the required child role for your widget type).
  4. Also add any required state attributes on the children (e.g. aria-selected = false).
  5. For dynamic CMS-driven lists: select the Collection List Wrapper, open Settings, and add role="listbox" to the wrapper; select the Collection Item and add role="option" and aria-selected="false" as custom attributes.
  6. Publish the site and verify with axe DevTools.
Aria required parentModerate effort

Wrap every ARIA role that requires a parent container (e.g. `menuitem`, `option`, `listitem`) inside the correct parent element carrying the matching ARIA role (e.g. `menu`/`menubar`, `listbox`, `list`).

On Webflow

  1. Open your Webflow Designer and locate the component that contains the flagged element (e.g. a Navbar, Dropdown, or custom div).
  2. Select the parent container of the child-role elements, go to the Element Settings panel (the gear icon), and add the correct `role` attribute in the Custom Attributes section (e.g. attribute `role`, value `menu`).
  3. Ensure child elements each have their child role set correctly in their own Custom Attributes panel.
  4. Publish the site and run the axe browser extension to verify the fix.
Code · Webflow (html)
<!-- Webflow rendered output with Custom Attributes applied -->
<!-- Parent container: add Custom Attribute role = menubar -->
<ul role="menubar" class="nav-menu">
  <!-- Each nav item li: add Custom Attribute role = none -->
  <li role="none" class="nav-item">
    <!-- Each nav link: add Custom Attribute role = menuitem -->
    <a href="/shop" role="menuitem" class="nav-link">Shop</a>
  </li>
  <li role="none" class="nav-item">
    <a href="/about" role="menuitem" class="nav-link">About</a>
  </li>
</ul>

In Webflow Designer, set these Custom Attributes on each element via the Element Settings (gear icon) panel. This shows the expected rendered HTML output.

Aria rolesQuick win

Audit every element that has a `role` attribute and replace any invalid or misspelled role value with a valid WAI-ARIA role from the official specification.

On Webflow

  1. Open your project in the Webflow Designer.
  2. Select the element with the invalid role in the Navigator or on the canvas.
  3. In the right-hand Settings panel (gear icon), scroll to the Custom Attributes section.
  4. Find the `role` attribute entry, click it, and type the correct valid ARIA role value — or click the × to delete it if no valid role applies.
  5. Publish the site and re-run axe DevTools or WAVE to confirm the fix.
  6. For roles added inside Embed components (HTML Embed): click the Embed element on the canvas, open the embed editor, and correct the `role` attribute in the raw HTML.
Code · Webflow (html)
<!-- BEFORE (invalid role inside a Webflow HTML Embed): -->
<!-- <div role="promo-banner" class="banner">Free shipping over $50</div> -->

<!-- AFTER: use a valid ARIA landmark role, or remove the attribute -->
<div role="region" aria-label="Promotional banner" class="banner">
  Free shipping over $50
</div>

For Webflow HTML Embed components — correct the role attribute inside the embed editor:

Aria toggle field nameQuick win

Add a visible or programmatic accessible name to every toggle control (checkbox, switch, or button with role="switch") so screen readers can announce what the control does.

On Webflow

  1. Open your Webflow Designer and select the toggle/checkbox element on the canvas.
  2. For a native Webflow Form Checkbox, click the element, then in the right-hand Settings panel ensure the 'Label' text field is filled in. This text is rendered as a <label> linked to the checkbox.
  3. For custom toggle components (e.g. a div styled as a switch), select the element, open the Element Settings panel (the gear icon), and add a custom attribute: Name = aria-label, Value = 'Descriptive text for the toggle'.
  4. For icon-only buttons acting as toggles, select the <button> or link element, open Element Settings → Custom Attributes, and add aria-label = 'Descriptive text'.
  5. Alternatively, place a hidden text element inside the button: add a <span> with the label text and apply a class with CSS properties that visually hide it but keep it accessible (position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0)).
  6. Publish to staging, then run the axe DevTools extension to confirm all toggles have accessible names.
Code · Webflow (html)
<!-- Add via Webflow Designer: select the button/toggle → Element Settings → Custom Attributes
     Name: aria-label
     Value: Enable dark mode

     OR use an Embed element for a fully custom accessible toggle: -->
<label class="wf-toggle">
  <input type="checkbox" id="wf-dark-mode" name="dark-mode" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);">
  <span class="wf-toggle__track" aria-hidden="true"></span>
  <span class="wf-toggle__label">Enable dark mode</span>
</label>

Custom attribute to add via Webflow Designer → Element Settings → Custom Attributes, or via Webflow's Embed element for a fully custom component.

Aria tooltip nameModerate effort

Give every tooltip element an accessible name so screen readers can announce its content to users who cannot see the screen.

On Webflow

  1. Open your project in the Webflow Designer and locate the element with role="tooltip" in the Navigator panel.
  2. Select the element, open the Element Settings panel (the gear icon), and scroll to the Custom Attributes section.
  3. If the tooltip contains inner text, verify that text is descriptive. If not, add a custom attribute: Name = aria-label, Value = your plain-language tooltip description.
  4. If the tooltip is dynamically bound to a CMS collection, use the CMS binding button next to the aria-label value field to pull the description from a CMS field.
  5. Publish the site and run the axe DevTools browser extension on the live URL to confirm the fix.
Code · Webflow (html)
<span class="tooltip-wrapper">
  <!-- Trigger: set aria-describedby in Webflow Element Settings > Custom Attributes -->
  <button
    type="button"
    class="tooltip-trigger"
    aria-describedby="wf-tooltip-1"
    aria-label="Learn about our return policy"
  >
    &#9432;
  </button>

  <!-- Tooltip div: add role=tooltip and id via Custom Attributes panel -->
  <div
    id="wf-tooltip-1"
    role="tooltip"
    class="tooltip-content"
  >
    Free returns within 30 days of delivery. No questions asked.
  </div>
</span>

In Webflow Designer, add a Code Embed block or use Custom Attributes on the tooltip div. This shows the final rendered HTML Webflow should produce.

Aria valid attrQuick win

Find and replace every misspelled or non-existent ARIA attribute name (e.g. `aria-labeledby`) with the correct, spec-defined name (e.g. `aria-labelledby`) across your entire site's HTML and templates.

On Webflow

  1. Open your project in the Webflow Designer.
  2. Select the element that has the invalid ARIA attribute (use the Navigator panel to locate it).
  3. In the right-hand Settings panel (gear icon), scroll down to 'Custom Attributes'. Find the misspelled attribute entry (e.g. `aria-labeledby`) and correct the Name field to the proper spelling (e.g. `aria-labelledby`). Do not change the Value.
  4. If the attribute appears in a Symbol or Component, open the Symbol/Component in edit mode and apply the same correction there so all instances are updated.
  5. For custom code embeds (HTML Embed elements), click the element → Settings → open the embed code editor and correct the attribute name in the raw HTML.
  6. Publish the site and verify using axe DevTools in the browser.
Code · Webflow (html)
<!-- In Webflow Designer:
  1. Select the element (e.g. a search input).
  2. Open Settings panel (gear icon) → Custom Attributes section.
  3. If you see an attribute Name of 'aria-labeledby', click it and change the Name to 'aria-labelledby'.
  4. Leave the Value unchanged.

  Webflow will then output the following correct HTML:
-->
<label id="site-search-label" for="site-search-input">Search</label>
<input
  id="site-search-input"
  class="search-input"
  type="search"
  name="q"
  aria-labelledby="site-search-label"
  placeholder="Search products…"
>

In Webflow Designer, use the Custom Attributes panel (Settings tab on the selected element) to set Name and Value. This is the equivalent raw HTML that Webflow will output after you correct the attribute Name field.

Aria valid attr valueModerate effort

Audit every ARIA attribute on your pages and correct any that have invalid or mismatched values so assistive technologies can interpret them correctly.

On Webflow

  1. Open the Webflow Designer and select the element flagged in the accessibility audit.
  2. In the right-hand panel, open the Element Settings (gear icon) → scroll to 'Custom Attributes'.
  3. Find the ARIA attribute with the invalid value in the custom attributes list, click it, and correct the value to the valid string.
  4. For ARIA attributes that Webflow sets automatically (e.g. on its Navbar, Tabs, or Accordion components), these are controlled by Webflow's built-in interactions — check that the component's initial state matches the ARIA value (e.g. a collapsed tab should have `aria-expanded='false'`).
  5. Publish the site and verify with the axe DevTools browser extension or Lighthouse.
Code · Webflow (html)
<!-- In Webflow's Custom Attributes panel, enter these name/value pairs: -->

<!-- For a toggle button that starts collapsed: -->
<!-- Attribute name:  aria-expanded  -->
<!-- Attribute value: false           -->

<!-- For an input described by a hint text element: -->
<!-- Attribute name:  aria-describedby   -->
<!-- Attribute value: billing-hint        -->
<!-- (The target element must have id="billing-hint" set in its Custom Attributes) -->

<!-- Equivalent raw HTML output Webflow will produce: -->
<button class="nav-toggle" aria-expanded="false" aria-controls="nav-menu">
  Open Menu
</button>
<nav id="nav-menu">
  <!-- navigation links -->
</nav>

<input type="text" id="billing-zip" aria-describedby="billing-hint" />
<span id="billing-hint">5-digit US ZIP code</span>

In Webflow Designer → select the element → Element Settings (gear icon) → Custom Attributes. Add/edit the attribute name and value as shown below.

Autocomplete validModerate effort

Add valid `autocomplete` attribute values to every form field that collects personal information, using the exact tokens specified by the HTML standard.

On Webflow

  1. Open your project in the Webflow Designer.
  2. Click on the form input element you want to fix.
  3. In the right-hand Settings panel (gear icon), scroll to the 'Autocomplete' dropdown — Webflow exposes this natively for input elements. Select the correct autocomplete token from the dropdown list.
  4. If the exact token you need is not in the dropdown, switch to the Element Settings panel and add a custom HTML attribute: click '+ Add Attribute', enter `autocomplete` as the name and the correct token (e.g. `street-address`) as the value.
  5. For Webflow Ecommerce checkout forms, Webflow renders the checkout — if autocomplete tokens are missing or wrong on the hosted checkout, report it via the Webflow wishlist/support, as direct HTML access to the checkout is limited.
  6. Publish the site and validate with the axe DevTools browser extension.
Code · Webflow (html)
<!-- Custom attribute to add in Webflow Designer Element Settings panel -->
<!-- Attribute Name:  autocomplete -->
<!-- Attribute Value: (choose the correct token for the field, e.g.:) -->

<!-- For a given name field: -->
autocomplete = "given-name"

<!-- For an email field: -->
autocomplete = "email"

<!-- For a shipping street address field: -->
autocomplete = "shipping address-line1"

<!-- For a new password field: -->
autocomplete = "new-password"

<!-- For a postal code field: -->
autocomplete = "postal-code"

In the Webflow Designer, select an input, open Element Settings (gear icon), and add a custom attribute. This is the name/value pair to enter.

Avoid inline spacingModerate effort

Remove hard-coded text-spacing CSS values from inline `style` attributes so users can override them with their own stylesheets or browser settings.

On Webflow

  1. Open the Webflow Designer and select the element flagged with inline spacing.
  2. In the Style panel on the right, check Typography → Line Height, Letter Spacing, Word Spacing — these should be set on a CSS class, not as inline overrides.
  3. Click the element's class name at the top of the Style panel. If the spacing is set as an inline 'combo class' override with no class applied, create a new class (e.g. `product-body`) and set the spacing values on that class instead.
  4. Remove any spacing values that are set as element-level overrides (shown with an orange dot in the Style panel) by right-clicking them and selecting 'Remove style'.
  5. Publish the site and verify in the browser with axe DevTools that no `avoid-inline-spacing` violations remain.
  6. For CMS-bound elements, edit the Collection page template in the Designer using the same class-based approach — the same class applies to all dynamic items.
Code · Webflow (css)
/* Webflow: Site Settings → Custom Code → Head Code
   Only use this if the Designer's Style panel cannot remove the inline override.
   Prefer setting the class in the Designer's Typography panel instead. */
.product-body {
  line-height: 1.6 !important;
  letter-spacing: 0.04em !important;
  word-spacing: 0.1em !important;
}

Paste into Webflow's global Custom Code <head> tag or Site Settings → Custom Code if you cannot remove a class-level override through the Designer.

Button nameModerate effort

Add a descriptive, screen-reader-accessible name to every button on your site so assistive technology users know what each button does.

On Webflow

  1. In the Webflow Designer, select the button element on the canvas that is missing a label.
  2. In the right-hand Settings panel (the gear icon), find the 'Accessibility' section. In the 'Aria Label' field, type a clear, descriptive name for the button (e.g. 'Close dialog' or 'Open search').
  3. For Navbar components (hamburger menu button), click the Navbar element, then open its Settings → find the Menu Button and set its Aria Label in the same Accessibility section.
  4. For CMS-driven buttons where the label should be dynamic (e.g. 'Add [product name] to cart'), bind the Aria Label field to a CMS field using Webflow's dynamic binding (click the purple database icon next to the Aria Label input and connect it to the appropriate CMS field).
  5. For e-commerce Add to Cart buttons, select the button inside the Product element, open Settings → Accessibility, and set the Aria Label or bind it dynamically.
  6. Publish the site and test with a screen reader or the axe DevTools browser extension to confirm no button-name violations remain.
Code · Webflow (html)
<!-- Webflow Custom Embed: icon-only button with aria-label -->
<button type="button" class="w-nav-button" aria-label="Open navigation menu" aria-expanded="false">
  <div class="w-icon-nav-menu" aria-hidden="true"></div>
</button>

<!-- Webflow Custom Embed: search icon button -->
<button type="button" class="search-button" aria-label="Open search">
  <svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"
       width="20" height="20" viewBox="0 0 24 24" fill="none"
       stroke="currentColor" stroke-width="2">
    <circle cx="11" cy="11" r="8"/>
    <line x1="21" y1="21" x2="16.65" y2="16.65"/>
  </svg>
</button>

For buttons in Webflow's Embed widget (custom HTML blocks), use this markup. For Designer-built buttons, set the Aria Label in the Settings panel → Accessibility section — no code needed.

Color contrastQuick win

Increase the contrast ratio between your text color and its background color to meet the WCAG 4.5:1 minimum, so all users — including those with low vision — can read your content clearly.

On Webflow

  1. Open the Webflow Designer and select the element with the low-contrast text.
  2. In the Style panel on the right, find Typography → Color. Click the color swatch and enter your accessible hex value.
  3. If the color is set via a global class (e.g. '.body-text'), updating it on the class will fix all instances site-wide — confirm in the Class dropdown that you're editing the shared class.
  4. Publish the site and re-test.
Code · Webflow (css)
<style>
  /* Webflow: override low-contrast text class */
  .text-color-gray {
    color: #595959; /* was #777777 — now passes 4.5:1 on white */
  }
</style>

Add to Site Settings → Custom Code → Head Code <style> block, or directly in the Designer's Style panel for the relevant class.

Definition listModerate effort

Ensure every `<dl>` (definition list) element contains only properly nested `<dt>` and `<dd>` children, with no stray text, divs, or other elements placed directly inside it.

On Webflow

  1. Open the Webflow Designer for your project.
  2. Use the Navigator panel (left sidebar) or Ctrl/Cmd+F to find any DL elements on your canvas.
  3. Click the `<dl>` element and inspect its children in the Navigator — remove or restructure any direct children that are not `<dt>`, `<dd>`, or `<div>` wrappers.
  4. To add or change child elements, use the Add Element panel (+) and drag DT or DD elements inside the DL.
  5. Publish the site and run an axe browser extension scan on the live URL to confirm the fix.
Code · Webflow (html)
<!-- In Webflow Designer: Add Element → DL → inside it add only DT and DD (or DIV wrappers) -->
<dl class="spec-list">
  <div class="spec-list__row">
    <dt class="spec-list__term">Material</dt>
    <dd class="spec-list__desc">Stainless Steel</dd>
  </div>
  <div class="spec-list__row">
    <dt class="spec-list__term">Dimensions</dt>
    <dd class="spec-list__desc">10 × 5 × 3 cm</dd>
  </div>
</dl>

Use this as a reference when manually building a DL element in the Webflow Designer canvas. Add DT and DD as child elements only.

DlitemModerate effort

Wrap every `<dt>` (term) and `<dd>` (description) element inside a parent `<dl>` element so description list items have their required container.

On Webflow

  1. Open your Webflow Designer and navigate to the page or global component (Symbol) containing the flagged content.
  2. In the Navigator panel, look for any `DT` or `DD` elements. Check their parent: if the parent is a `Div Block` or anything other than a `DL` element, the structure is wrong.
  3. To fix: select the group of `DT`/`DD` elements, then wrap them by adding a new element. In the Add Elements panel, search for 'dl' — Webflow supports native `<dl>`, `<dt>`, and `<dd>` elements under the 'Typography' or 'HTML Elements' section.
  4. Place a `DL` element where the group should be, then move each `DT` and `DD` into it using drag-and-drop in the Navigator.
  5. If this is inside a CMS Collection List, edit the Collection Item component and restructure the bound fields to output inside a `DL` wrapper.
  6. Publish the site and verify with browser DevTools.
Code · Webflow (html)
<!-- Paste into a Webflow HTML Embed element -->
<dl style="margin: 0; padding: 0; list-style: none;">
  <div style="display: flex; gap: 0.5rem; margin-bottom: 0.5rem;">
    <dt style="font-weight: 600; min-width: 140px;">Material</dt>
    <dd style="margin: 0;">100% organic cotton</dd>
  </div>
  <div style="display: flex; gap: 0.5rem; margin-bottom: 0.5rem;">
    <dt style="font-weight: 600; min-width: 140px;">Dimensions</dt>
    <dd style="margin: 0;">30 × 20 × 10 cm</dd>
  </div>
  <div style="display: flex; gap: 0.5rem;">
    <dt style="font-weight: 600; min-width: 140px;">Shipping time</dt>
    <dd style="margin: 0;">3–5 business days</dd>
  </div>
</dl>

Use Webflow's Custom HTML Embed element (Add panel → Components → HTML Embed) for any key-value content that needs a <dl> structure.

Document titleQuick win

Add a descriptive, non-empty `<title>` element to every page so browsers, screen readers, and search engines can identify it.

On Webflow

  1. Open the Webflow Designer for your project.
  2. Select the page in the Pages panel (left sidebar).
  3. Click the gear icon next to the page name to open Page Settings.
  4. Under 'SEO Settings', fill in the 'Title Tag' field with your descriptive title.
  5. For CMS Collection pages: open the Collection Template page settings → use CMS field bindings (the purple '+' button) to bind the Title Tag to the item's name or SEO title field.
  6. Publish the site and confirm the browser tab shows the correct title.
Code · Webflow (html)
<!-- Webflow renders this automatically from Page Settings → SEO Settings → Title Tag -->
<!-- For CMS Collection pages, bind the Title Tag field to: Item Name – Site Name -->
<!-- Rendered output example: -->
<title>Blue Canvas Sneakers – Acme Footwear</title>

Webflow generates the <title> tag from the 'Title Tag' field in Page Settings. For CMS pages, bind it to the item name field. This is the rendered HTML output Webflow produces — no manual code needed unless using custom code embeds.

Empty headingQuick win

Find every empty heading tag on your store and add visible, descriptive text (or remove the tag entirely) so screen readers and search engines can interpret your page structure.

On Webflow

  1. Open your Webflow project in the Designer and navigate to the page or component with the empty heading.
  2. In the Navigator panel (left sidebar), find the Heading element with no text node child — it will show as 'Heading' with no label.
  3. Double-click the Heading element on the canvas and type descriptive text.
  4. If the heading exists only for layout/styling (e.g. an empty spacer), select it, open Element Settings (D key), and change the Tag to `div` or `p` instead of `h1`–`h6`.
  5. For CMS-bound headings that may be empty when a field is blank: in the Collection template, bind the Heading to the CMS field, then in the element's settings add a Conditional Visibility rule to hide the Heading when the field is empty.
  6. Publish and verify with the axe DevTools browser extension.
Code · Generic HTML (html)
<!-- ❌ WRONG: empty heading — announced as 'heading' with no label -->
<h2></h2>

<!-- ✅ CORRECT option 1: add visible, descriptive text -->
<h2>Featured Products</h2>

<!-- ✅ CORRECT option 2: visually-hidden text (visible to screen readers, hidden on screen) -->
<!-- Add this CSS once in your stylesheet: -->
<!--
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
-->
<h2><span class="sr-only">Featured Products</span></h2>

<!-- ✅ CORRECT option 3: aria-label when removing the tag is not possible -->
<h2 aria-label="Featured Products"></h2>

<!-- ✅ CORRECT option 4: not a heading at all — use a div instead -->
<div class="decorative-divider"></div>

The wrong pattern and the two correct alternatives — visible text, or visually-hidden text for screen readers only.

Empty table headerQuick win

Add visible or screen-reader-accessible text to every empty `<th>` table header cell so assistive technology users can understand what each column or row represents.

On Webflow

  1. In the Webflow Designer, open the page containing the table.
  2. In the Navigator panel (left sidebar), expand the Table structure and find the empty `<th>` element.
  3. Click the element on the canvas and type the header text directly, OR open the Element Settings panel (D key) and add a custom attribute `aria-label` with a meaningful value.
  4. To add a visually-hidden label, create a `<span>` inside the `<th>`, add your text, then apply a combo class (e.g., `sr-only`) with CSS: `position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;`
  5. Publish and verify with axe DevTools.
Code · Webflow (html)
<!-- Add this CSS in Project Settings → Custom Code → <head> tag -->
<style>
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
</style>

<!-- Insert this markup via an HTML Embed element on the Webflow canvas -->
<table>
  <thead>
    <tr>
      <!-- Top-left corner cell with visually hidden label -->
      <th scope="col"><span class="sr-only">Category</span></th>
      <th scope="col">Starter</th>
      <th scope="col">Professional</th>
      <th scope="col">Enterprise</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Storage</th>
      <td>10 GB</td>
      <td>100 GB</td>
      <td>Unlimited</td>
    </tr>
    <tr>
      <th scope="row">Users</th>
      <td>1</td>
      <td>10</td>
      <td>Unlimited</td>
    </tr>
  </tbody>
</table>

Use Webflow's Embed element to insert a fully accessible table, or add the sr-only style via Project Settings → Custom Code → Head Code.

Error not associatedModerate effort

Add `aria-invalid="true"` and a linked `aria-describedby` error message to every form field that fails validation, so screen reader users are told exactly what went wrong.

On Webflow

  1. Open your Webflow project in the Designer.
  2. Select the Form Block containing the field with validation errors, then click on the individual input element.
  3. In the Element Settings panel (right sidebar), scroll to 'Custom Attributes' and add: Name = aria-describedby, Value = FIELDNAME-error (matching the id you will give the error element).
  4. Add a Text Block element directly after the input inside the same Form Block wrapper; set its ID to FIELDNAME-error and set its initial visibility to hidden (toggle display:none via class).
  5. In the Webflow Interactions panel (or in a custom <script> added via Project Settings → Custom Code → Footer Code), write JavaScript that on form submission detects invalid fields and: sets aria-invalid='true' on the input, reveals the error Text Block, and populates its text content.
  6. Publish the site and test with VoiceOver (Mac) or NVDA (Windows).
Code · Webflow (javascript)
<script>
(function () {
  'use strict';

  // Find every Webflow form on the page
  document.querySelectorAll('.w-form form').forEach(function (form) {

    form.addEventListener('submit', function (e) {
      var hasError = false;

      form.querySelectorAll('[aria-describedby]').forEach(function (input) {
        var errorId  = input.getAttribute('aria-describedby');
        var errorEl  = document.getElementById(errorId);
        var label    = form.querySelector('[for="' + input.id + '"]');
        var name     = (label ? label.textContent.trim() : input.name) || 'This field';

        if (!input.value.trim()) {
          input.setAttribute('aria-invalid', 'true');
          if (errorEl) {
            errorEl.textContent = name + ' is required.';
            errorEl.removeAttribute('hidden');
          }
          if (!hasError) { input.focus(); }
          hasError = true;
        } else {
          input.setAttribute('aria-invalid', 'false');
          if (errorEl) {
            errorEl.textContent = '';
            errorEl.setAttribute('hidden', '');
          }
        }
      });

      if (hasError) { e.preventDefault(); }
    });

    // Clear errors as the user types
    form.addEventListener('input', function (e) {
      var input   = e.target;
      var errorId = input.getAttribute('aria-describedby');
      if (!errorId) return;
      var errorEl = document.getElementById(errorId);
      if (input.value.trim()) {
        input.setAttribute('aria-invalid', 'false');
        if (errorEl) {
          errorEl.textContent = '';
          errorEl.setAttribute('hidden', '');
        }
      }
    });
  });
})();
</script>

Paste into Project Settings → Custom Code → Footer Code. Targets all Webflow form inputs that have aria-describedby set via the Designer.

Focus not visibleQuick win

Add a clearly visible focus indicator to every interactive element so keyboard users can always see which element is currently selected.

On Webflow

  1. Open your Webflow Designer and click the Style Manager panel (the paintbrush icon).
  2. Search for any class that applies `outline: none` to All Links, Buttons, or Inputs — Webflow often does this by default on the base `a` and `button` tags.
  3. Select the All Links element (or Body, Button, Input as needed) in the Navigator, switch to the Focus state in the Style panel, and set a visible Outline (e.g. 3px solid #005FCC).
  4. Alternatively, go to Project Settings → Custom Code → Head Code and add a `<style>` block with the universal CSS rule from the snippet below.
  5. Publish and test by tabbing through the published URL.
Code · Webflow (html)
<style>
  /* Visible focus indicator fix — WCAG 2.4.7 */
  *:focus-visible {
    outline: 3px solid #005FCC !important;
    outline-offset: 2px !important;
    border-radius: 2px;
  }

  *:focus {
    outline: 3px solid #005FCC !important;
    outline-offset: 2px !important;
  }

  *:focus:not(:focus-visible) {
    outline: none !important;
  }
</style>

Paste into Project Settings → Custom Code → Head Code section, or apply per-element via the Focus state in the Style panel.

Frame titleQuick win

Add a descriptive `title` attribute to every `<iframe>` and `<frame>` element on your store so screen-reader users know what each embedded frame contains before entering it.

On Webflow

  1. Open your project in the Webflow Designer. Select the Embed element or HTML Embed component that contains your <iframe>.
  2. Click the element to open the Embed Code editor and add title="…" to the <iframe> tag, then click Save & Close.
  3. For iframes in custom code added via Project Settings → Custom Code (Head/Body code), edit the script/embed there and add the title attribute.
  4. Publish your site and use the axe DevTools browser extension on your published Webflow URL to verify all iframes now have descriptive titles.
Code · Webflow (html)
<iframe
  src="https://www.youtube.com/embed/VIDEO_ID"
  title="Product demo: how to assemble the XYZ Widget"
  width="560"
  height="315"
  frameborder="0"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
  allowfullscreen
></iframe>

Paste this into a Webflow Embed element (the full block, including the iframe with title).

Heading orderModerate effort

Restructure your page headings so they follow a logical, sequential order (H1 → H2 → H3) with no levels skipped.

On Webflow

  1. Open the Webflow Designer and navigate to the page or component to fix.
  2. Click on any Heading element on the canvas. In the Element Settings panel (the gear icon on the right), find the 'Tag' dropdown (showing H1–H6) and change it to the correct semantic heading level.
  3. For heading elements inside Symbols or Components, open the Symbol/Component in edit mode, update the tag there, and the change will propagate to all instances.
  4. For CMS-driven pages (e.g., product or blog templates), open the Collection Template in the Designer, find the heading binding, and update the tag in the same Element Settings panel.
  5. Publish the site and verify with the axe DevTools browser extension on the live URL.
Code · Webflow (html)
<!-- Webflow Designer: set each Heading element's Tag in the
     Element Settings panel (gear icon) on the right sidebar.
     The resulting exported HTML should follow this pattern: -->

<section class="category-section">
  <!-- Tag set to H1 in Element Settings -->
  <h1 class="category-heading">Running Shoes</h1>
</section>

<section class="filters-section">
  <!-- Tag set to H2 in Element Settings -->
  <h2 class="section-heading">Filter Options</h2>

  <div class="filter-group">
    <!-- Tag set to H3 in Element Settings -->
    <h3 class="filter-group-heading">Shoe Type</h3>
  </div>
</section>

<section class="products-section">
  <!-- Tag set to H2 in Element Settings -->
  <h2 class="section-heading">All Products</h2>

  <div class="product-card">
    <!-- Tag set to H3 in Element Settings (nested under H2) -->
    <h3 class="product-title">UltraRun Pro 5</h3>
  </div>
</section>

In the Webflow Designer, set each Heading element's Tag via Element Settings → Tag dropdown. The exported HTML will look like this correct structure.

Html has langQuick win

Add a valid `lang` attribute to the `<html>` element so browsers, screen readers, and search engines know what language your page is written in.

On Webflow

  1. In the Webflow Designer, open your Project Settings (the gear icon in the top-left).
  2. Go to the Publishing tab (or General tab depending on your Webflow version).
  3. Find the 'Locale' or 'Language' field and set your primary language — Webflow uses this to set the `lang` attribute on the `<html>` tag.
  4. For Webflow Localization (multi-language), each locale automatically gets its correct `lang` attribute.
  5. Publish your site and verify via View Source.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Your Page Title</title>
</head>
<body>
  <!-- page content -->
</body>
</html>

Add the lang attribute to the opening <html> tag of every HTML page. Use a valid BCP 47 language tag.

Html lang validQuick win

Add a valid BCP 47 language code to the `lang` attribute on your site's `<html>` element so browsers and assistive technologies know what language your content is written in.

On Webflow

  1. In the Webflow Designer, click the 'W' logo (top-left) → Project Settings → General tab.
  2. Scroll to the 'Localization' or 'SEO' section. Under 'Language', set your primary site language — Webflow will inject this as the `lang` attribute on the `<html>` element.
  3. If you use Webflow Localization (multi-language), each locale page will receive its own correct `lang` attribute automatically.
  4. For a custom `lang` value not available via the UI, go to Project Settings → Custom Code and add a script that rewrites `document.documentElement.lang` — though this is rarely needed.
  5. Publish your site and verify via page source or axe DevTools.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Page Title</title>
  </head>
  <body>
    <!-- page content -->
  </body>
</html>

Place this as the very first tag in your HTML document. Replace 'en' with your actual BCP 47 language code.

Image altModerate effort

Add descriptive alt text to every meaningful image, and mark purely decorative images with an empty alt attribute or role="presentation".

On Webflow

  1. Select any image element on the canvas → in the right-hand Element Settings panel (the gear icon), find the 'Alt Text' field → type a description.
  2. To mark a decorative image, check the 'Decorative image (no alt needed)' checkbox in the same panel — Webflow will output alt="" automatically.
  3. For CMS-driven images (e.g., product photos from a Collection): in the CMS Collection settings, ensure you have an 'Alt Text' plain-text field → bind the image's Alt Text property to that field in the Designer.
  4. Run Webflow's built-in Accessibility Audit (Audit panel → Accessibility) to see a list of all images flagged as missing alt text.
Code · Generic HTML (html)
<!-- Meaningful image: describe the content -->
<img src="red-leather-bag.jpg" alt="Red leather crossbody bag with gold clasp" width="600" height="400">

<!-- Image used as a link: describe the destination/action -->
<a href="/sale">
  <img src="summer-sale-banner.jpg" alt="Shop our summer sale — up to 40% off">
</a>

<!-- Decorative image: empty alt, screen readers skip it -->
<img src="decorative-swoosh.svg" alt="" role="presentation">

<!-- WRONG — never omit alt entirely on an <img> -->
<!-- <img src="product.jpg"> -->

<!-- WRONG — never use filename or generic words -->
<!-- <img src="IMG_4821.jpg" alt="image"> -->

Correct patterns for meaningful, linked, and decorative images.

Image redundant altQuick win

Remove or clear the alt text on images whose description is already provided by visible text immediately surrounding them, so screen readers don't read the same information twice.

On Webflow

  1. In the Webflow Designer, click the image element on the canvas.
  2. In the right-hand Element Settings panel (the gear icon), find the 'Alt Text' field.
  3. If the image is inside a link block or card that already contains the product name as visible text, clear the Alt Text field entirely — Webflow will render alt="".
  4. Alternatively, check the 'Decorative' checkbox if available in your Webflow version, which also sets alt="".
  5. For CMS-bound images (e.g., product photos from a CMS collection), go to the CMS Collection settings and clear the Alt Text field in the relevant CMS items, or update the field binding so it outputs an empty string when redundant.
  6. Publish the site and verify with the axe browser extension.
Code · Webflow (html)
<!-- Webflow published output — correct pattern -->
<!-- Image inside a CMS link block that also renders the item name as text -->
<a href="/products/blue-vase" class="product-card w-inline-block">
  <img
    src="https://uploads-ssl.webflow.com/.../blue-vase.jpg"
    loading="lazy"
    alt=""
    class="product-image"
  >
  <div class="product-name">Blue Vase</div>
</a>

In the Webflow Designer, set the Alt Text field to empty for images inside link blocks that already contain visible text. The published output will look like this.

Input button nameQuick win

Add a visible label or accessible name to every input button so assistive technologies can announce what the button does.

On Webflow

  1. In the Webflow Designer, select the button or Form Block Submit button on the canvas.
  2. For a standard Submit Button inside a Form Block: in the right-hand Settings panel find the 'Value' or 'Button Label' field and enter a descriptive label (e.g. 'Submit Order'). This populates the `value` attribute.
  3. For a standalone Button element (rendered as `<button>`, not `<input>`): double-click the element on the canvas and type a visible label.
  4. If you used an Embed component with raw HTML containing an `<input type="submit">`, click the Embed component → open the editor and add `value="Submit"` to the tag.
  5. Publish the site and re-test with the axe browser extension.
Code · Generic HTML (html)
<!-- CORRECT: Submit button with descriptive value -->
<input type="submit" value="Place Order">

<!-- CORRECT: Button with descriptive value -->
<input type="button" value="Add to Cart">

<!-- CORRECT: Reset button with descriptive value -->
<input type="reset" value="Clear Form">

<!-- CORRECT: Icon-only button — use aria-label when no visible text is possible -->
<input type="submit" value="" aria-label="Search products">

<!-- WRONG: Empty value — screen readers cannot announce purpose -->
<!-- <input type="submit" value=""> -->

Use a descriptive value attribute on every input button type. Never leave value empty.

Input image altQuick win

Add a descriptive `alt` attribute to every `<input type="image">` element so screen readers can announce its purpose to visually impaired shoppers.

On Webflow

  1. Open your project in the Webflow Designer.
  2. If the `<input type="image">` was added via an Embed element, click the Embed component on the canvas to open its HTML editor, find the `<input>` tag, and add `alt="[descriptive text]"`.
  3. If it is a native Webflow Form element using an image submit button, select the element on the canvas, open the Element Settings panel (D key), and fill in the Alt Text field.
  4. Publish the site and verify with axe DevTools or WAVE.
Code · Generic HTML (html)
<!-- BEFORE (broken): -->
<input type="image" src="buy-now.png">

<!-- AFTER (fixed): -->
<input type="image" src="buy-now.png" alt="Buy now">

<!-- Search button example: -->
<input type="image" src="search-icon.png" alt="Search products">

<!-- Custom checkout submit example: -->
<input type="image" src="checkout-btn.png" alt="Proceed to checkout">

Replace the `alt` value with a short, action-oriented description of what the button does.

LabelQuick win

Add a visible or programmatic label to every form input so assistive technologies can identify it for users.

On Webflow

  1. Open your project in the Webflow Designer and select the Form Block or standalone input element.
  2. Click on the specific Input element. In the right panel under 'Label', ensure a Label element is present and connected to the input via its 'For' attribute matching the input's ID.
  3. To add a visually hidden label: add a `<label>` element in the form, set its text, then apply a custom 'visually-hidden' combo class with the CSS: `position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;`.
  4. For inputs where you only want `aria-label` (no visible label), select the input, go to Element Settings (gear icon) → Custom Attributes, and add `aria-label` with the descriptive value.
  5. Publish and test with axe DevTools.
Code · Webflow (html)
<!-- Webflow: visible label linked to input -->
<!-- In the Designer, add a Label element, set its 'For' field to match the Input ID. -->
<!-- If using a custom HTML Embed block: -->

<div class="form-field">
  <label for="contact-email" class="field-label">Email address</label>
  <input
    class="w-input"
    id="contact-email"
    name="contact-email"
    type="email"
    autocomplete="email"
    placeholder="you@example.com"
    required
  >
</div>

<!-- Webflow: icon-only search — add via Custom Attributes panel:
     Attribute name:  aria-label
     Attribute value: Search products
-->

Paste into a Custom Code Embed if adding a raw form, or set via Designer Element Settings → Custom Attributes for aria-label on existing inputs.

Label title onlyModerate effort

Add a visible text label to every form field so it is not labeled only by a hidden tooltip (title attribute) or supplementary description (aria-describedby).

On Webflow

  1. Open the Webflow Designer. Click the Form Block containing the offending input, then click into the specific Input element.
  2. In the Element Settings panel (right side), find the 'Label' toggle — make sure it is turned ON and that the label text is filled in with a meaningful name (e.g. 'Email address', not just 'Email').
  3. If you need a visually-hidden label (screen-reader only), create a Label element, add a custom class (e.g. 'sr-only'), and in the Style panel set: position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap;
  4. For input elements where no label element is desired, select the Input in the Designer → Settings panel → add a custom attribute: Name = aria-label, Value = 'Search' (or the appropriate field name).
  5. Do NOT rely on the Placeholder field as a substitute for a label — placeholders disappear when the user starts typing.
  6. Publish and test with axe DevTools in the browser.
Code · Webflow (css)
/* Visually-hidden utility — paste into Webflow's Custom Code or a global stylesheet */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

Add this class in Webflow Designer → Style panel (or via global CSS) and apply it to any Label element you want screen-reader-visible but visually hidden.

Landmark banner is top levelModerate effort

Move your site's `<header>` element (or `role="banner"` landmark) so it is a direct child of `<body>`, not nested inside another landmark region.

On Webflow

  1. Open your project in the Webflow Designer.
  2. In the Navigator panel, locate the element tagged as 'Header' (Webflow uses a dedicated Header element that outputs `<header>`).
  3. Drag the Header element to be a direct child of the Body node in the Navigator — it must not sit inside a Section, Div Block, or any other element that carries a landmark role.
  4. If any wrapping Div Block has an ARIA Role set (via Element Settings → ARIA), remove that role or change it to 'none'.
  5. Publish and verify with axe DevTools.
Code · Webflow (html)
<body>
  <!-- Webflow exports the Header element as <header> at the top level -->
  <header class="navbar w-nav" role="banner">
    <a href="/" class="brand w-nav-brand">
      <img src="/images/logo.svg" alt="My Store home">
    </a>
    <nav role="navigation" aria-label="Primary" class="nav-menu w-nav-menu">
      <a href="/shop" class="nav-link w-nav-link">Shop</a>
      <a href="/about" class="nav-link w-nav-link">About</a>
    </nav>
  </header>

  <main class="main-content">
    <!-- page sections -->
  </main>

  <footer class="footer">
    <!-- footer -->
  </footer>
</body>

In Webflow's Navigator, the Header element must sit directly under Body. The exported code should look like this.

Landmark complementary is top levelModerate effort

Move your <aside> element (or role="complementary") to the top level of the page so it is not nested inside another landmark region.

On Webflow

  1. Open your Webflow project in the Designer and locate the page or component containing the sidebar.
  2. In the Navigator panel (left sidebar), find the element set to Tag: `<aside>` (or a Div with a custom attribute `role="complementary"`).
  3. Check its parent in the Navigator tree. If it is nested inside an element tagged as `<main>`, `<header>`, `<nav>`, or `<footer>`, drag it out in the Navigator so it becomes a sibling of `<main>` under the same parent wrapper Div.
  4. Set the parent wrapper Div's display to Flex or Grid (Layout panel on the right) so `<main>` and `<aside>` sit side by side visually.
  5. Select the `<aside>` element, open Element Settings (D key), and add a custom attribute: Name `aria-label`, Value `[Descriptive sidebar name]`.
  6. Publish the project and verify with axe DevTools on the published URL.
Code · Webflow (html)
<!-- In Webflow Designer:
  1. Select the sidebar element.
  2. Set its HTML Tag to: aside
  3. In Element Settings > Custom Attributes, add:
     Name:  aria-label
     Value: Related products
  4. In Navigator, ensure this <aside> is a sibling of the <main> element,
     not a child of it.
-->

<!-- Resulting rendered HTML (read-only reference): -->
<aside aria-label="Related products" class="sidebar-wrapper">
  <!-- sidebar content blocks -->
</aside>

Custom attribute to add in Webflow Designer on the aside element — Element Settings panel.

Landmark contentinfo is top levelQuick win

Move your site's footer element (or role="contentinfo") so it is a direct child of the page body, not nested inside another landmark region.

On Webflow

  1. Open your project in the Webflow Designer.
  2. In the Navigator panel (left side), find your Footer section/element. Confirm it sits at the root level of the page structure — a direct child of the Body element, not nested inside a Main Wrapper, Section, or any element that has a landmark role.
  3. If the Footer is nested inside a Main Wrapper or other landmark container: click the Footer element in the Navigator, then drag it out to be a direct child of Body (below the Main Wrapper).
  4. In Webflow, the built-in 'Footer' element automatically receives the correct semantic role. Ensure you are using the native Footer element type (not a plain Div inside another landmark).
  5. Publish the site and verify with the axe DevTools browser extension.
Code · Webflow (html)
<body>
  <div class="navbar-wrapper" role="banner">
    <!-- Header / Nav content -->
  </div>

  <main class="main-wrapper">
    <!-- Page sections and content blocks -->
  </main>

  <!-- CORRECT in Webflow: Footer element is a direct child of body -->
  <footer class="footer-wrapper">
    <div class="footer-content">
      <p>© 2024 My Store</p>
      <nav aria-label="Footer links">
        <a href="/privacy">Privacy</a>
        <a href="/terms">Terms</a>
      </nav>
    </div>
  </footer>
</body>

In Webflow's Designer, the Navigator panel should show Footer as a direct child of Body, after Main Wrapper. This is the exported HTML equivalent of the correct structure.

Landmark main is top levelQuick win

Move the `<main>` element (or `role="main"`) so it is a direct child of `<body>`, not nested inside any other landmark element such as `<header>`, `<nav>`, `<aside>`, or `<footer>`.

On Webflow

  1. In the Webflow Designer, open the page or the site's global layout.
  2. In the Navigator panel (left sidebar), find the element that has the 'Main' tag or has 'role=main' set — typically a Section or Div element labeled 'Main Wrapper' or similar.
  3. Check its parent element in the Navigator. If the parent is a 'Header' element, a Nav, or any other landmark-tagged container, drag the Main element out so it is a direct child of the Body node in the Navigator.
  4. To set or verify the HTML tag: select the element → go to Element Settings (D key) → HTML Tag → choose 'main'.
  5. Ensure the Header and Footer elements are siblings of Main, not parents.
  6. Publish the site and verify with axe DevTools or WAVE in your browser.
Code · Webflow (html)
<!-- Webflow rendered HTML — correct structure -->
<body>
  <header class="header-wrapper" role="banner">
    <nav class="nav" aria-label="Main">
      <!-- nav links -->
    </nav>
  </header>

  <!-- Main is a sibling of header, direct child of body -->
  <main class="main-wrapper">
    <section class="hero">
      <h1>Shop Our Collection</h1>
    </section>
  </main>

  <footer class="footer-wrapper">
    <!-- footer content -->
  </footer>
</body>

Webflow rendered output (read-only for reference). In the Designer Navigator, drag 'Main' element to be a direct child of Body, not inside a Header element.

Landmark no duplicate bannerModerate effort

Ensure your page has only one banner landmark (one `<header>` or `role="banner"` element that is not nested inside another landmark), so screen reader users can navigate your site reliably.

On Webflow

  1. Open your project in the Webflow Designer.
  2. In the Navigator panel, scan every page and the Global Navbar symbol for elements tagged as 'Header' (which outputs `<header>`).
  3. Select any element that is currently a 'Header' tag but should not be the page banner → in the right-hand Element Settings panel, change the Tag from 'Header' to 'Div Block'.
  4. Check all Symbols (Components) — a reusable symbol with a `<header>` tag included inside a content section will create a duplicate. Change its tag to 'Div Block' in the Symbol's edit view.
  5. Also check CMS Collection page templates and landing page templates for rogue `<header>` tags.
  6. Publish and verify with the axe DevTools extension.
Code · Webflow (html)
<!-- Webflow exported HTML (simplified) -->
<!-- In the Designer: Navbar symbol Tag = 'Header', promo bar Tag = 'Div Block' -->

<!DOCTYPE html>
<html data-wf-domain="yoursite.webflow.io" lang="en">
<head>
  <meta charset="utf-8">
  <title>Store</title>
</head>
<body>

  <!-- Webflow Navbar component with Tag set to 'Header' in Designer -->
  <header class="navbar w-nav" role="banner">
    <a href="/" class="brand w-nav-brand">
      <img src="images/logo.svg" alt="Store Logo">
    </a>
    <nav role="navigation" class="nav-menu w-nav-menu" aria-label="Main navigation">
      <a href="/shop" class="nav-link w-nav-link">Shop</a>
      <a href="/about" class="nav-link w-nav-link">About</a>
    </nav>
  </header>

  <!-- Promo strip: Tag set to 'Div Block' in Webflow Designer — NOT a banner -->
  <div class="promo-strip" role="region" aria-label="Current promotion">
    <p>Free shipping on orders over $50</p>
  </div>

  <main role="main">
    <!-- page content -->
  </main>

  <footer>
    <!-- footer content -->
  </footer>

</body>
</html>

Exported Webflow HTML showing correct structure — one Navbar set to <header>, promo strip as <div>.

Landmark no duplicate contentinfoModerate effort

Ensure your page has only one footer/contentinfo landmark by removing or re-marking any duplicate `<footer>` elements or `role="contentinfo"` attributes.

On Webflow

  1. Open your project in the Webflow Designer. In the Navigator panel (left sidebar), locate the Footer component — it is typically a Section or Div Block with a Tag set to <footer>.
  2. Click the Footer element, then in the Element Settings panel (right sidebar, gear icon), confirm the HTML Tag is set to 'footer'. There should be exactly one such element per page.
  3. Search the Navigator for any other elements whose tag is set to 'footer' or that have a custom attribute role="contentinfo". Select each duplicate, change its HTML Tag to 'div', and add an aria-label attribute if it needs a descriptive name.
  4. If a third-party embed (Embed widget) injects a <footer> or role="contentinfo", click the Embed element, edit the HTML code, and replace <footer> with <div>.
  5. Publish the site and test with axe DevTools on the published URL.
Code · Webflow (html)
<!-- Webflow Embed widget — CORRECT: use <div>, not <footer> -->
<div class="promo-footer-bar" aria-label="Promotional information" style="background:#f5f5f5; padding:12px; text-align:center;">
  <p style="margin:0;">Free returns on all orders. <a href="/returns">Learn more</a></p>
</div>

<!-- The actual site footer is set in Webflow Designer by:
     1. Selecting the Footer element
     2. Opening Element Settings (gear icon)
     3. Confirming HTML Tag = 'footer'
     Only ONE element should have Tag = 'footer' on the entire page. -->

In the Webflow Designer, set the HTML Tag of secondary footer-like sections to 'div' via Element Settings. For Embed widgets, use this corrected HTML.

Landmark no duplicate mainQuick win

Find and remove every duplicate `<main>` element or `role="main"` attribute so that exactly one main landmark exists on each page.

On Webflow

  1. Open the Webflow Designer for the affected page or template.
  2. In the Navigator panel (left sidebar) expand the page structure and look for any element explicitly tagged as '<main>' (a 'Main' element in the HTML tag dropdown) or any element with a custom attribute 'role' set to 'main'.
  3. There should be exactly one. Select any duplicates, open the Element Settings panel (D key), change the HTML tag from 'main' to 'div' or another appropriate element, and remove any 'role="main"' custom attribute.
  4. Also check global symbols (navbar, footer, CMS Collection wrappers) for inadvertent '<main>' tags.
  5. Publish the site and verify with the axe browser extension.
Code · Webflow (html)
<!-- Webflow rendered output (for reference — edit via the Designer, not raw HTML) -->
<header class="navbar-wrapper"><!-- nav content --></header>

<!-- Set this element's HTML tag to 'main' in the Webflow Element Settings panel -->
<main class="main-wrapper">
  <!-- page sections go here -->
</main>

<footer class="footer-wrapper"><!-- footer content --></footer>

<!-- Any other wrappers (hero, product grid, etc.) should use 'div' or 'section', NOT 'main' -->

In Webflow Designer, set the HTML tag of your central content wrapper to 'main' and all others to 'div'. This is the rendered output Webflow should produce.

Landmark one mainQuick win

Add a single `<main>` landmark element (or `role="main"`) to every page so assistive technologies can identify and navigate directly to the primary content.

On Webflow

  1. Open your project in the Webflow Designer. In the Navigator panel, locate the element that wraps the main page content (usually a `div` named 'Main Content' or similar).
  2. Click that element to select it, then in the Element Settings panel (the gear icon on the right), find the 'Tag' dropdown and change it from `div` to `main`.
  3. Repeat this for every page template (Home, Collection, Product, CMS Template, etc.) in your project via the Pages panel.
  4. If you use a Symbol or Component for the page wrapper, update it once and it propagates everywhere.
  5. Publish your site and verify with the axe DevTools extension.
Code · Webflow (html)
<!-- Webflow outputs this when you set the element tag to 'main' in the Designer -->
<main class="main-content w-full">
  <!-- page sections / CMS content -->
</main>

In the Webflow Designer, select the main content div → Element Settings panel → Tag dropdown → choose 'main'. No manual code needed; this is what Webflow outputs.

Landmark uniqueModerate effort

Add a unique aria-label (or aria-labelledby) to every repeated landmark role on the page so screen-reader users can tell them apart.

On Webflow

  1. Open your project in the Webflow Designer.
  2. Click on the Navbar, Section, or other repeated landmark element on the canvas.
  3. In the right-side Element Settings panel (the gear icon), scroll down to the 'Custom Attributes' section.
  4. Click the '+' icon to add a new attribute. Set the Name to aria-label and the Value to a unique, descriptive label (e.g. 'Main navigation').
  5. Repeat for every other instance of the same landmark type on the page, giving each a distinct label (e.g. 'Footer navigation' for the second <nav>).
  6. Publish the site, then verify with the axe DevTools browser extension on the live URL.
Code · Webflow (html)
<!-- Rendered HTML after adding Custom Attributes in Webflow Designer -->

<!-- Primary navbar component -->
<nav aria-label="Main navigation" class="navbar w-nav">
  <!-- Webflow nav content -->
</nav>

<!-- Footer nav component -->
<nav aria-label="Footer navigation" class="footer-nav">
  <!-- Webflow footer links -->
</nav>

<!-- Sidebar / filter aside -->
<aside aria-label="Product filters" class="sidebar">
  <!-- filter widgets -->
</aside>

In the Webflow Designer, select the element → Element Settings (gear icon) → Custom Attributes. Add Name: aria-label, Value: your label. This is the rendered output.

Link in text blockQuick win

Increase the color contrast between inline links and their surrounding body text to at least 3:1, or add a non-color visual distinction (such as underline) so links are identifiable without relying on color alone.

On Webflow

  1. In the Webflow Designer, open the Style Panel.
  2. Select a link element inside a text block (or use the global 'All Links' selector: type 'a' in the selector field).
  3. Update the Color property to a compliant hex value with ≥3:1 contrast against your body text color.
  4. Under Typography, ensure Text Decoration is set to 'Underline' — this is the simplest route to full compliance.
  5. Publish the site and test on a live paragraph containing links.
Code · Webflow (css)
/* Webflow: global link style for inline text links */
a {
  color: #0057d8; /* update to your chosen compliant color */
  text-decoration: underline;
}

Apply via the global 'a' selector in the Style Panel, or paste into Project Settings → Custom Code → Head Code inside a <style> tag.

Link nameQuick win

Add a descriptive, screen-reader-accessible text label to every link on your store so assistive technology can announce its purpose.

On Webflow

  1. Open the Webflow Designer. Click the link or image element that is flagged.
  2. In the right-hand Settings panel (the gear icon), scroll down to the 'Accessibility' section.
  3. For a Link Block wrapping an icon or image: enter a descriptive label in the 'Label (aria-label)' field (e.g. 'View cart').
  4. For a plain Link element with no visible text: type the descriptive label in the 'Label (aria-label)' field, or add a Text element inside the link and apply a 'visually-hidden' class (set width:1px; height:1px; overflow:hidden; position:absolute; clip:rect(0,0,0,0); white-space:nowrap).
  5. For SVG icons placed inside Link Blocks: select the SVG element and in Settings → Accessibility, set aria-hidden='true' so screen readers ignore the decorative graphic and rely solely on the link's aria-label.
  6. Publish and verify with axe DevTools or NVDA + Chrome.
ListModerate effort

Fix all HTML lists so that only valid child elements appear directly inside `<ul>` and `<ol>` tags, and only `<ul>` or `<ol>` elements wrap `<li>` items.

On Webflow

  1. Open Webflow Designer for your project.
  2. In the Navigator panel (left sidebar), locate any List element (tagged as `<ul>` or `<ol>`) — click it to select it.
  3. In the Elements panel, verify that every child of the List is a List Item (`<li>`). If you see a Div Block, Text Block, or other element as a direct child of the List, drag it inside an existing List Item or wrap it: right-click → Wrap in List Item.
  4. For nav menus: select the Navbar component → open its settings — Webflow's Navbar outputs `<nav>` with an unordered list; ensure no extra Div Blocks are placed as direct children of that list in the Navigator.
  5. For CMS-generated lists: open the Collection List settings and ensure the Collection List Wrapper (`<ul>`) contains only Collection Items (`<li>`).
  6. Publish the site and validate with the axe browser extension.
Code · Webflow (html)
<!-- Expected Webflow rendered output -->
<ul class="nav-list w-list-unstyled">
  <li class="nav-item"><a href="/page-1">Page 1</a></li>
  <li class="nav-item"><a href="/page-2">Page 2</a></li>
  <li class="nav-item"><a href="/page-3">Page 3</a></li>
</ul>

Webflow outputs this structure for a List element — verify in the Navigator that no extra Div Blocks appear as direct children of the List.

ListitemModerate effort

Ensure every `<li>` element is a direct child of a `<ul>` or `<ol>` element (or an element explicitly given `role="list"`), so that screen readers can correctly announce list structure to users.

On Webflow

  1. Open your project in the Webflow Designer.
  2. In the Navigator panel (left sidebar), find the element flagged as a list item. Check what its parent element is — it should be a List element (Webflow's `<ul>`) or an Ordered List (`<ol>`).
  3. If the List Item is nested inside a Div Block or other non-list container, select the List Item(s), cut them, then add a List element (Add Elements panel → Typography → List) in the correct location, and paste the List Items inside it.
  4. Alternatively, right-click the parent Div Block in the Navigator → wrap in a List element if that option is available, or manually restructure by adding a List, moving items in, then deleting the old wrapper.
  5. If the list items are generated by a CMS Collection List, open the Collection List settings and ensure the Collection List Wrapper is set to render as `<ul>` or `<ol>` (the default), not overridden with a `<div>` via the Tag settings.
  6. Publish the site and run axe DevTools in the browser on the published URL to verify the listitem rule no longer fires.
Code · Webflow (html)
<!-- Correct Webflow-generated HTML for an unordered list -->
<ul class="your-list-class w-list-unstyled">
  <li class="your-list-item-class">Feature one</li>
  <li class="your-list-item-class">Feature two</li>
  <li class="your-list-item-class">Feature three</li>
</ul>

<!-- Correct Webflow-generated HTML for a nav menu -->
<nav class="nav-menu" aria-label="Main navigation">
  <ul class="nav-list w-list-unstyled">
    <li class="nav-item"><a href="/shop" class="nav-link">Shop</a></li>
    <li class="nav-item"><a href="/about" class="nav-link">About</a></li>
    <li class="nav-item"><a href="/contact" class="nav-link">Contact</a></li>
  </ul>
</nav>

Webflow generates this HTML — use the Designer's Navigator to ensure List Items sit inside a List (ul) or Ordered List (ol) element, not inside a Div Block. The output should look like this.

Meta refreshQuick win

Remove or disable the `<meta http-equiv="refresh">` tag that forces an automatic page reload, or replace any redirect use-case with a server-side redirect instead.

On Webflow

  1. Open your Webflow project in the Designer. Go to Project Settings → Custom Code → Head Code (for site-wide injection) or open the specific page → Page Settings → Custom Code → Inside <head> tag.
  2. Find and delete any `<meta http-equiv="refresh" ...>` snippet.
  3. For redirects, use Webflow's built-in 301 redirect tool: Project Settings → Hosting → 301 Redirects → Add Redirect.
  4. Publish the site and confirm via browser View Source that the tag is absent.
Code · Generic HTML (html)
<!-- REMOVE — self-refresh every 30 seconds (WCAG fail) -->
<!-- <meta http-equiv="refresh" content="30"> -->

<!-- REMOVE — redirect to another URL after 5 seconds (WCAG fail) -->
<!-- <meta http-equiv="refresh" content="5; url=https://example.com/new-page"> -->

<!-- ALLOWED — 0-second instant redirect (no time limit imposed on user) -->
<!-- Only use this as a fallback alongside a server-side redirect -->
<meta http-equiv="refresh" content="0; url=https://example.com/new-page">

REMOVE this tag from your page's <head>. Both forms below are problematic (self-refresh and timed redirect).

Meta viewportQuick win

Remove `user-scalable=no` (and any `maximum-scale` below 5) from your site's `<meta name="viewport">` tag so visitors can pinch-to-zoom on mobile devices.

On Webflow

  1. In the Webflow Designer, open Project Settings (the gear icon in the top toolbar).
  2. Go to the Custom Code tab.
  3. In the Head Code section, check whether a custom <meta name="viewport"> tag has been added — if it contains `user-scalable=no`, remove that parameter.
  4. Webflow's default platform-generated viewport tag does not include `user-scalable=no`, so if the issue exists it was likely added manually in custom code — simply delete or correct it.
  5. Publish your site after saving.
Code · Generic HTML (html)
<meta name="viewport" content="width=device-width, initial-scale=1">

Place this in the <head> of every HTML page. This is the correct, accessibility-compliant viewport tag.

Meta viewport largeQuick win

Remove or raise the `maximum-scale` value in your meta viewport tag so users can pinch-zoom on mobile devices.

On Webflow

  1. In the Webflow Designer, open Project Settings (gear icon, top left).
  2. Go to the 'Custom Code' tab.
  3. In the 'Head Code' field, paste a corrected <meta name="viewport"> tag (Webflow's default is already zoom-permissive, so this is only needed if a previous developer added a restrictive override).
  4. Publish the site.
Code · Webflow (html)
<meta name="viewport" content="width=device-width, initial-scale=1">

In Project Settings → Custom Code → Head Code, paste this if a prior developer added a restrictive override.

Nested interactiveModerate effort

Remove or restructure nested interactive elements so that no focusable control (button, link, input, etc.) is placed inside another interactive control, and never rely on tabindex="-1" or aria-hidden="true" to hide an inner interactive element from assistive technologies.

On Webflow

  1. In the Webflow Designer, open the Navigator panel (left sidebar) and locate the nested interactive structure — e.g., a Button element inside a Link Block, or a Link Block inside another Link Block.
  2. Select the outer interactive element and change its tag/type to a non-interactive Div Block (Element Settings → Tag: div) if it was only providing layout, keeping the inner interactive element as the actual control.
  3. Alternatively, select the inner interactive element and change its type to a Div Block styled identically, leaving only the outer Link Block or Button as the interactive control.
  4. Never place a Button element inside a Link Block or a Link Block inside another Link Block in Webflow's element tree.
  5. Publish the site and verify in the browser with Tab-key navigation and the axe DevTools extension.
Code · Webflow (html)
<!-- ❌ WRONG in Webflow exported HTML: Button inside Link Block -->
<!--
<a href="/products/widget" class="w-inline-block product-card">
  <button class="btn w-button">Shop Now</button>
</a>
-->

<!-- ✅ CORRECT: single Link Block styled as a button -->
<a href="/products/widget" class="btn w-button">Shop Now</a>

Exported Webflow HTML — ensure Link Blocks never contain Button elements. Use a single Link Block.

Object altQuick win

Add a meaningful text alternative (via `title` attribute or `aria-label`) to every `<object>` element so screen readers can describe its content to users who cannot see it.

On Webflow

  1. Open your project in the Webflow Designer.
  2. Locate the page containing the <object> element. Webflow's built-in components do not produce <object> tags, so this is almost certainly inside an Embed element.
  3. Click the Embed element on the canvas, then click the '</>' icon to open the HTML Embed Code Editor.
  4. In the code, find the <object> tag and add `title='Descriptive label here'` to it.
  5. Click 'Save & Close', then publish the site.
  6. Verify the fix using the axe DevTools browser extension on the published URL.
Code · Generic HTML (html)
<!-- BEFORE (fails accessibility check) -->
<object data="product-catalog.pdf" type="application/pdf" width="600" height="400">
  <p>Your browser does not support PDFs.</p>
</object>

<!-- AFTER: Option 1 — title attribute (preferred for <object>) -->
<object
  data="product-catalog.pdf"
  type="application/pdf"
  width="600"
  height="400"
  title="Winter 2024 Product Catalog (PDF)">
  <p>Your browser does not support PDFs.
    <a href="product-catalog.pdf">Download the catalog</a>.
  </p>
</object>

<!-- AFTER: Option 2 — aria-label (acceptable alternative) -->
<object
  data="product-catalog.pdf"
  type="application/pdf"
  width="600"
  height="400"
  aria-label="Winter 2024 Product Catalog (PDF)">
  <p>Your browser does not support PDFs.
    <a href="product-catalog.pdf">Download the catalog</a>.
  </p>
</object>

<!-- AFTER: Option 3 — purely decorative, hide from AT -->
<object
  data="decorative-animation.svg"
  type="image/svg+xml"
  width="200"
  height="200"
  aria-hidden="true">
</object>

Add a `title` attribute to any <object> element — this is the primary fix. Use aria-label as an alternative if ARIA is already in use on the element.

Page has heading oneQuick win

Add a single, descriptive `<h1>` heading to every page so assistive technologies and search engines can identify the page's main topic.

On Webflow

  1. Open your project in the Webflow Designer and navigate to the page.
  2. In the left panel, find the element used as the main page title. Click it to select it.
  3. In the right-hand Element Settings panel, confirm the element tag is 'H1'. If it's set to 'Div', 'Paragraph', or another tag, click the tag dropdown and change it to 'Heading → H1'.
  4. If no heading exists, press 'A' to open the Add panel, drag a Heading element onto the canvas, set its tag to H1, and type the page title.
  5. Ensure only one H1 per page by searching the Navigator panel for any duplicate H1 elements and downgrading them to H2.
  6. Publish the site and verify with the axe browser extension or Webflow's built-in Audit panel (the triangle icon in the top toolbar).
Code · Generic HTML (html)
<main>
  <h1>Women's Running Shoes</h1>
  <!-- rest of page content -->
</main>

Place this after your site navigation, as the first element inside <main>. Style it with CSS as needed — the visual size is independent of the semantic tag.

Presentation role conflictModerate effort

Remove conflicting ARIA attributes (global ARIA properties and tabindex) from any element that has been marked as purely presentational with role="presentation" or role="none".

On Webflow

  1. Open the Webflow Designer and select the element flagged by the accessibility checker (use the Navigator panel to find it by tag/class).
  2. With the element selected, open the Element Settings panel (the gear icon, 'D' shortcut) and scroll to the Custom Attributes section.
  3. Review any custom attributes set: if role="presentation" or role="none" is present alongside an aria-* attribute or tabindex, remove the conflicting attribute(s).
  4. If the element is a decorative image, open the Image Settings and check the 'Decorative image' checkbox — Webflow will automatically add alt="" and remove conflicting roles/labels.
  5. Publish your site and run axe DevTools in Chrome to verify the fix.
Code · Webflow (html)
<!-- Webflow Designer → Element Settings → Custom Attributes -->
<!-- Decorative divider: set role to 'none', ensure NO aria-* attributes are added -->
<!-- Example rendered output you should see in Published HTML: -->
<div class="decorative-divider" role="none"></div>

<!-- Meaningful icon with label: remove role="none", keep aria-label -->
<img class="icon-cart" src="cart.svg" alt="Shopping cart" />

Custom attribute panel in Webflow: if you previously set role='none' and aria-label on the same element, remove aria-label. For decorative images, tick 'Decorative image' in Image Settings instead.

RegionModerate effort

Wrap all visible page content inside HTML landmark regions (e.g. `<header>`, `<main>`, `<nav>`, `<footer>`) so screen-reader users can navigate the page by section.

On Webflow

  1. In the Webflow Designer, open the Navigator panel (the layers view) for the page you want to fix.
  2. Select the outermost wrapper for your site header. In the Element Settings panel (D key), change the HTML tag from 'div' to 'header'. If you have more than one nav, add a custom attribute: 'aria-label' → 'Main navigation' on the `<nav>` element.
  3. Select your main content area wrapper and change its tag to 'main'. Ensure there is only one `<main>` per page.
  4. Select your footer wrapper and change its tag to 'footer'.
  5. For any Section (`<section>`) elements, add a custom attribute 'aria-label' with a descriptive value (e.g., 'Featured products') in the Element Settings panel.
  6. Publish the site and verify with the axe DevTools extension. Repeat for any CMS-template pages by editing the Collection Template.
Role img altModerate effort

Add a meaningful aria-label (or aria-labelledby) to every element with role="img" so screen readers can announce what the image represents.

On Webflow

  1. In the Webflow Designer, select the element flagged (often an Icon element or an Embed with an SVG).
  2. Open the Element Settings panel (D key) → scroll to 'Custom Attributes'.
  3. Add a new attribute: Name = aria-label, Value = [your description, e.g. 'Verified purchase badge'].
  4. If the element is decorative, add aria-hidden with value true instead.
  5. For SVG embeds added via an HTML Embed component, edit the embed code and add aria-label directly on the <svg role="img"> tag or add a <title> child element.
  6. Publish the site and validate with the axe browser extension.
Code · Webflow (html)
<!-- Webflow HTML Embed: accessible SVG icon -->
<!-- Paste this into an HTML Embed block where you need a labelled icon -->
<svg
  role="img"
  aria-labelledby="wf-icon-secure-title"
  width="32"
  height="32"
  viewBox="0 0 32 32"
  xmlns="http://www.w3.org/2000/svg"
  focusable="false"
>
  <title id="wf-icon-secure-title">Secure checkout — 256-bit SSL encryption</title>
  <path d="M16 2a8 8 0 0 1 8 8v2h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V14a2 2 0 0 1 2-2h2v-2a8 8 0 0 1 8-8zm0 2a6 6 0 0 0-6 6v2h12v-2a6 6 0 0 0-6-6z"/>
</svg>

<!-- For Webflow's built-in Icon element, use the Custom Attributes panel:
     Name:  aria-label
     Value: Secure checkout — 256-bit SSL encryption
     (No code needed for that approach) -->

Use in an HTML Embed component (+ icon → Embed) or set via Element Settings → Custom Attributes for non-embed elements.

Scrollable region focusableQuick win

Make every scrollable region on your site reachable by keyboard by adding tabindex="0" (or placing focusable content inside it) so keyboard and assistive-technology users can scroll it.

On Webflow

  1. Open the Webflow Designer for the relevant page.
  2. Select the Div Block or element that has overflow set to Scroll or Auto (found in the Style panel → Overflow).
  3. In the Element Settings panel (the gear icon on the right), scroll down to Custom Attributes and add: Name = tabindex, Value = 0 and Name = aria-label, Value = [Region name, e.g. 'Product description'].
  4. In the Style panel, add a :focus state and set an Outline (e.g. 2px solid #005fcc) so sighted keyboard users see focus.
  5. Publish the site and test by pressing Tab to reach the element, then arrow keys to scroll.
Code · Webflow (html)
<!-- Webflow Custom Attributes to set in the Designer panel: -->
<!-- Attribute 1: Name = tabindex    | Value = 0              -->
<!-- Attribute 2: Name = aria-label  | Value = Product description -->
<!-- Attribute 3: Name = role        | Value = region         -->

<!-- The resulting rendered HTML will look like: -->
<div
  class="product-description-scroll"
  tabindex="0"
  aria-label="Product description"
  role="region"
>
  <!-- Rich text / CMS content here -->
</div>

In the Webflow Designer, select the scrollable Div Block → Element Settings (gear icon) → Custom Attributes. Add the attributes below. No code file to edit directly.

Select nameQuick win

Add a visible or programmatic label to every `<select>` dropdown so assistive technologies can announce its purpose to users.

On Webflow

  1. Open your project in the Webflow Designer. In the Navigator or on-canvas, click the `<select>` (Select Field element) that is unlabeled.
  2. In the left-side Elements panel, drag a 'Label' element from the Form Elements section and place it immediately before the Select element inside the same form wrapper.
  3. With the Label element selected, type the descriptive label text (e.g., 'Sort by', 'Choose size'). Then, in the Label element's Settings panel, set the 'For' field to the exact ID of the Select element.
  4. Click the Select element and in its Settings panel set a unique Element ID (e.g., `sort-select`) — this must match the 'For' value you set on the label.
  5. If you want the label visually hidden, add a custom class (e.g., `visually-hidden`) to the Label element and add the CSS for that class in Webflow's Style panel under Global CSS: `.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }`
  6. Publish the site and verify with the axe DevTools browser extension.
Code · Webflow (css)
/* Add to your Webflow project's global custom CSS */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

Add this CSS class in Webflow's Global CSS (via a style tag in your site's <head> custom code or in the Webflow Style Manager). Apply the class 'visually-hidden' to any Label element you want hidden visually but readable by screen readers.

Skip linkQuick win

Add a valid, focusable target element (with a matching `id`) for every "Skip to main content" link so keyboard and assistive-technology users can bypass repeated navigation.

On Webflow

  1. In the Webflow Designer, open your site's global layout — select the Body element in the Navigator panel.
  2. Add a Link Block element as the very first child of the Body. Set its text to 'Skip to main content' and its href to `#main-content`.
  3. Select the element that wraps your main page content (often a Section or Div with class `main-content`). In the Element Settings panel, set its ID to `main-content`.
  4. Add a custom attribute `tabindex` with value `-1` to the main content element via the Element Settings → Custom Attributes panel.
  5. Style the skip link using Webflow's Style panel: set position to Fixed, top to -100%, then add an interaction or :focus state that sets top to 0 (use 'Element trigger → Focus' in the Interactions panel or add custom CSS in Project Settings → Custom Code → Head Code).
  6. Publish and test with keyboard Tab navigation on the published URL.
Svg img altModerate effort

Add a meaningful text alternative to every SVG image so screen readers can describe it to visually impaired shoppers.

On Webflow

  1. In the Webflow Designer, click on the SVG element (Icon, SVG embed, or image set to an SVG file) to select it.
  2. In the Element Settings panel (the gear icon on the right), scroll to the 'Accessibility' section. Set 'Role' to 'img' and type a meaningful description in the 'Label (aria-label)' field.
  3. For SVGs embedded via an Embed component (raw HTML): double-click the Embed block, locate the `<svg>` tag in the HTML editor, and manually add `role="img"` and `aria-label="Descriptive text"` to the opening tag, or add `<title id="t1">Descriptive text</title>` as the first child and `aria-labelledby="t1"` to `<svg>`.
  4. For decorative SVGs, in the Accessibility section of Element Settings, set 'State' to 'Decorative (hidden from assistive tech)' — Webflow adds `aria-hidden="true"` automatically.
  5. Publish and verify using the Webflow built-in accessibility panel or Chrome DevTools > Accessibility.
Code · Webflow (html)
<!-- Paste into a Webflow Embed block for a meaningful SVG -->
<svg
  role="img"
  aria-labelledby="wf-svg-title-1"
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 24 24"
  width="48"
  height="48"
  focusable="false"
>
  <title id="wf-svg-title-1">Secure checkout — padlock icon</title>
  <path d="M12 1a5 5 0 00-5 5v3H5a2 2 0 00-2 2v10a2 2 0 002 2h14a2 2 0 002-2V11a2 2 0 00-2-2h-2V6a5 5 0 00-5-5zm0 2a3 3 0 013 3v3H9V6a3 3 0 013-3zm0 9a2 2 0 110 4 2 2 0 010-4z" fill="currentColor"/>
</svg>

Use this inside a Webflow Embed component when you need to add a labelled SVG that the Designer's native Icon element doesn't expose accessibility fields for.

TabindexModerate effort

Remove or replace all positive tabindex values (tabindex="1" or higher) on your page elements, using tabindex="0" for focusable elements and tabindex="-1" only when programmatic focus is needed.

On Webflow

  1. Open your project in the Webflow Designer.
  2. Select the element flagged with a positive tabindex — check its settings in the right-hand panel under the Element Settings tab (the gear icon).
  3. Look for the 'Tab Index' field in the element settings panel and change the value to '0' (to include in natural tab order) or leave blank/remove to use the default.
  4. For elements you cannot configure via the Designer panel, switch to the project's Custom Code section: go to Project Settings → Custom Code and check the Head and Body code injection areas for hardcoded tabindex attributes.
  5. Also check any Embed components on the canvas that contain raw HTML with tabindex set — click the Embed element and edit the HTML directly in the embed editor.
  6. Publish the site and verify the tab order by pressing Tab through the page in a browser.
Code · Webflow (html)
<!-- ❌ WRONG: positive tabindex inside a Webflow Embed block -->
<!-- <div tabindex="4" class="w-custom-widget">Featured Product</div> -->

<!-- ✅ CORRECT: tabindex set to 0 for a non-native focusable element -->
<div
  tabindex="0"
  role="region"
  aria-label="Featured Product"
  class="w-custom-widget">
  Featured Product
</div>

<!-- ✅ CORRECT: native button — no tabindex attribute needed at all -->
<button type="button" class="w-button">Shop Now</button>

Use this in a Webflow Embed component (Add panel → Embed) or in Project Settings → Custom Code for any raw HTML that contains tabindex attributes.

Valid langQuick win

Add or correct the `lang` attribute on every HTML element that contains content in a different language, using a valid BCP 47 language code.

On Webflow

  1. Open your Webflow project in the Designer.
  2. To set the site-wide HTML lang attribute: go to Project Settings → Localization (or SEO) → find the 'HTML lang attribute' field and enter the correct BCP 47 code (e.g. en, fr, de). For Webflow Localization (paid feature), each locale gets its own lang code automatically.
  3. To add a lang attribute to a specific element (e.g. a div with French text): select the element in the Designer → open the Element Settings panel (D key) → scroll to Custom Attributes → click + and enter Name: lang, Value: fr (or the correct code).
  4. Publish your site and inspect the live HTML to confirm the attribute is present with the correct value.
Code · Webflow (html)
<!-- Example: a div in Webflow with a French-language text block -->
<!-- In Designer: select element → Settings → Custom Attributes → Name: lang, Value: fr -->
<!-- Rendered output in published site: -->
<div lang="fr">
  <p>Bienvenue dans notre boutique en ligne.</p>
</div>

Add a custom attribute to any Webflow element via Designer → Element Settings → Custom Attributes. This is the rendered output you should verify in the published HTML.

Security (OWASP) · 26 fixes

Dmarc policy noneModerate effort

Strengthen your DMARC policy from p=none (monitor-only) to p=quarantine, then p=reject, so that emails fraudulently sent from your domain are blocked rather than silently allowed through.

On Webflow

  1. Webflow does not send transactional email natively; you likely use a form submission handler, Memberstack, or an ESP integration. Confirm which services send mail on your domain's behalf from your rua reports.
  2. In Webflow → Project Settings → Hosting → Custom Domain, Webflow shows any DNS records it requires. Ensure those are published.
  3. Log in to your DNS provider (Webflow hosts DNS for Webflow-managed domains under Project Settings → Hosting → DNS), find the _dmarc TXT record, and change p=none to p=quarantine.
  4. Monitor reports, fix any failing senders, then advance to p=reject.
Code · Generic HTML (dns)
v=DMARC1; p=quarantine; sp=quarantine; pct=100; rua=mailto:dmarc-reports@yourdomain.com; ruf=mailto:dmarc-failures@yourdomain.com; ri=86400; aspf=r; adkim=r; fo=1;

This is the DNS TXT record value for _dmarc.yourdomain.com — paste this as the record content at your DNS provider. Replace the rua/ruf addresses with your own reporting inbox or DMARC reporting service address.

Hsts disabledQuick win

Set a valid HTTP Strict-Transport-Security (HSTS) header with a max-age of at least 31536000 (one year) and includeSubDomains to force browsers to always use HTTPS for your store.

On Webflow

  1. Webflow sets HSTS automatically for sites hosted on Webflow's infrastructure (webflow.io or custom domains with Webflow hosting) — no manual configuration is needed.
  2. Ensure your custom domain is published and SSL is active: go to Site Settings → Publishing → Custom Domain → confirm the green SSL lock icon.
  3. If you export Webflow code and self-host it, you must set the HSTS header in your own web server (Apache/Nginx) or CDN — follow the generic server steps above.
  4. Verify with DevTools on your live Webflow-hosted URL.
Code · Generic HTML (html)
Strict-Transport-Security: max-age=31536000; includeSubDomains

This is an HTTP response header — it cannot be set in HTML meta tags. It must be set by your server, CDN, or platform. Shown here for reference only.

Hsts max age too shortQuick win

Increase your HSTS max-age directive to at least 31536000 seconds (1 year) so browsers enforce HTTPS connections for a meaningful period.

On Webflow

  1. Webflow hosts sites on its own CDN and manages HTTPS automatically. Custom HTTP response headers are available on CMS and Business plans and above.
  2. To set or update the HSTS header: Webflow Designer → Project Settings → Hosting → Custom Headers section (or 'HTTP Headers' tab depending on plan).
  3. Add a new response header: Name: Strict-Transport-Security Value: max-age=31536000; includeSubDomains
  4. If you already have an HSTS header entry with max-age=86400, edit that entry and replace the value with max-age=31536000; includeSubDomains.
  5. Publish the site after saving. Verify with browser DevTools → Network tab → response headers on any page request.
Code · Generic HTML (http)
Strict-Transport-Security: max-age=31536000; includeSubDomains

This is an HTTP response header — it must be sent by your server, NOT as an HTML meta tag. The meta tag is not supported for HSTS.

Https not availableQuick win

Enable HTTPS by installing a valid SSL/TLS certificate and redirecting all HTTP traffic to the secure HTTPS version of your store.

On Webflow

  1. Webflow automatically provisions a free SSL certificate (via Fastly/Let's Encrypt) for all published sites on a paid hosting plan.
  2. In your Webflow Dashboard, open the project and go to Publishing → Custom Domains.
  3. Confirm the SSL badge next to your domain shows 'SSL Active'. If it shows 'Pending', wait up to 24 hours for DNS propagation, then retry.
  4. Webflow automatically redirects HTTP to HTTPS for all hosted sites — no additional configuration is needed.
  5. In Project Settings → Hosting, ensure 'Enable SSL' is toggled on (it is on by default).
Info disclosure serverQuick win

Remove or suppress the HTTP `Server` response header so your web server no longer advertises its software name and version number to the public internet.

On Webflow

  1. Webflow hosting is managed by Webflow — the `Server` header is not configurable from the Webflow Designer or Project Settings.
  2. For sites on Webflow's own hosting (*.webflow.io or custom domain via Webflow DNS), contact Webflow Support to report the finding.
  3. The recommended merchant-accessible fix is to use *Cloudflare as a proxy*: in Webflow Project Settings > Hosting, note your site's IP/CNAME, then configure your domain in Cloudflare with the proxy enabled. Add a Cloudflare *Transform Rule* (Rules > Transform Rules > Modify Response Header) to remove the `Server` header.
  4. Verify by running `curl -I https://yourdomain.com` — the `Server` header should disappear or show `cloudflare`.
Code · Generic HTML (html)
<!-- The Server header cannot be controlled from HTML.
     Apply the fix at your web server, CDN, or hosting control panel
     using one of the platform-specific configurations shown below. -->

There is no HTML tag to set or suppress the Server header — it is purely a server/CDN-level configuration. Use the server-specific snippets below.

Info disclosure x powered byQuick win

Remove or suppress the X-Powered-By HTTP response header so your server stops advertising which technology stack, hosting platform, or software version powers your store.

On Webflow

  1. Webflow is a hosted platform; standard Webflow sites do not allow direct server response header customization.
  2. Webflow does not emit X-Powered-By on standard hosted projects.
  3. If you are exporting Webflow code and self-hosting it on a Node/Express or Apache/Nginx server, apply the server-level fix (see code snippets below) on your own infrastructure.
  4. For Webflow Hosting, if a scanner flags this, contact Webflow support — it may be a CDN/edge configuration issue on their side.
Code · Generic HTML (html)
<!-- X-Powered-By is an HTTP response header set by the server, not in HTML markup.
     Use the server-side code snippets below for your specific platform. -->

There is no HTML tag for this — it is a server-side HTTP response header. Use the server/framework snippets below.

Insecure cookieModerate effort

Set the HttpOnly, Secure, and SameSite flags on all session and authentication cookies to prevent them from being stolen or misused by attackers.

On Webflow

  1. Webflow manages its own session cookies at the platform level — contact Webflow support for flags on cookies set by Webflow itself.
  2. For cookies set in custom JavaScript (via Webflow's Embed blocks or Project Settings → Custom Code), move cookie creation to a serverless backend (e.g. Cloudflare Workers, Netlify Functions) and set the Set-Cookie response header with all three flags there.
  3. In Webflow Designer: go to Project Settings → Custom Code and audit any document.cookie assignments in <script> blocks; remove them and replace with a server-side approach.
  4. If using Webflow Memberships (beta/released), test login cookies in DevTools and report missing flags to Webflow support.
Code · Generic HTML (http)
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Strict

Correct Set-Cookie HTTP response header — all three flags present. Your server must emit this header, not JavaScript.

Missing content security policyModerate effort

Add a Content-Security-Policy (CSP) HTTP response header to every page to block unauthorized scripts, styles, and resources from loading in your visitors' browsers.

On Webflow

  1. Webflow does not support custom HTTP response headers on its hosting.
  2. Go to Project Settings → Custom Code → Head Code and add a <meta http-equiv='Content-Security-Policy' content='default-src self; object-src none;'> tag as a partial measure.
  3. For full header support, export your Webflow site and host it on a server you control (Netlify, Vercel, AWS), or route Webflow through Cloudflare and set the header via a Transform Rule.
  4. On Netlify: add a netlify.toml file with [[headers]] for /* and add Content-Security-Policy to the [headers.values] block.
Missing dmarcQuick win

Add a DMARC DNS TXT record at _dmarc.yourdomain.com to tell receiving mail servers how to handle emails that fail authentication checks for your domain.

On Webflow

  1. Webflow does not manage DNS. If your domain was purchased through Webflow, DNS is managed via Webflow's dashboard: go to Designer → Project Settings → Hosting → click the domain → Edit DNS.
  2. Add a TXT record: Name = _dmarc, Value = v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com, TTL = 3600.
  3. If your DNS is managed externally (Cloudflare, etc.), log in there and add the same TXT record.
  4. Webflow does not send transactional commerce email natively — you'll be using an integration (Shopify Buy Button, Foxy.io, or a custom setup). Verify DKIM/SPF at whichever mail provider handles your outgoing email before tightening DMARC policy.
Code · Generic HTML (dns)
_dmarc.yourdomain.com. 3600 IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; ruf=mailto:dmarc-forensic@yourdomain.com; sp=none; adkim=r; aspf=r"

Add this as a DNS TXT record at your domain registrar or DNS provider. Replace yourdomain.com and the rua address with your actual domain.

Missing permissions policyQuick win

Add a Permissions-Policy HTTP response header to your store to explicitly restrict which browser features (camera, microphone, geolocation, etc.) pages and embedded third-party scripts are allowed to access.

On Webflow

  1. Webflow hosted sites do not natively expose custom response header configuration from the Designer or dashboard.
  2. Use Cloudflare proxy: point your Webflow custom domain through Cloudflare, then in Rules → Transform Rules → Modify Response Header → add 'Permissions-Policy' header with value 'camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()'.
  3. If self-hosting Webflow exports on your own server (Node/Nginx/Apache), add the header at the server configuration level using the snippets below.
  4. Verify in DevTools → Network → Response Headers.
Code · Generic HTML (meta tag — limited support) (html)
<!-- NOT recommended — use a real HTTP header instead. Shown for reference only. -->
<meta http-equiv="Permissions-Policy" content="camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()">

Meta tag delivery is not recommended — use HTTP headers instead. Included for reference only; browser support is partial.

Missing referrer policyQuick win

Add a Referrer-Policy HTTP response header set to strict-origin-when-cross-origin to control what URL information browsers send when users navigate away from your store.

On Webflow

  1. Webflow does not allow custom HTTP response headers from the Designer or dashboard for sites hosted on Webflow's infrastructure.
  2. Inject a <meta> tag site-wide: open your Project Settings → Custom Code → Head Code section, paste: <meta name="referrer" content="strict-origin-when-cross-origin">, then publish your site.
  3. Alternatively, export your Webflow site and host it on your own server or Vercel/Netlify, where you can set headers natively (see Headless / Custom steps).
  4. For Webflow-hosted sites, routing traffic through Cloudflare and setting the header via a Transform Rule is the recommended production-grade solution.
Code · Generic HTML (html)
<meta name="referrer" content="strict-origin-when-cross-origin">

Add inside the <head> element of every HTML page as a fallback when HTTP header control is unavailable.

Missing security txtQuick win

Create and publish a /.well-known/security.txt file with at least a Contact: line so security researchers know how to report vulnerabilities responsibly.

On Webflow

  1. In your Webflow project, go to Project Settings → Hosting → Advanced Publishing Settings.
  2. Webflow does not natively support placing arbitrary files at /.well-known/. Use a Cloudflare Worker in front of your Webflow site to intercept requests to /.well-known/security.txt and return the file contents.
  3. Create a Worker script (see code snippets), then add a Worker Route in Cloudflare for yourdomain.com/.well-known/security.txt.
  4. Verify by visiting https://yourdomain.com/.well-known/security.txt.
Code · Generic HTML (text)
Contact: mailto:security@yourdomain.com
Expires: 2026-12-31T23:59:00.000Z
Preferred-Languages: en
Policy: https://yourdomain.com/security-policy
Acknowledgments: https://yourdomain.com/security-acknowledgments

Save this as security.txt and place it at /.well-known/security.txt on your web server. Update all placeholder values before publishing.

Missing spfQuick win

Add a DNS TXT record containing a valid SPF policy to your domain so that email receivers can verify which servers are authorised to send mail on your behalf.

On Webflow

  1. If your domain is registered with Webflow: Webflow Dashboard → Webflow Workspace → Domains → click your domain → DNS Settings.
  2. Add a new TXT record: Name = '@', Value = your SPF string. Save.
  3. If the domain is registered elsewhere (GoDaddy, Namecheap, Cloudflare), log into that provider's DNS panel and add the TXT record there.
  4. Webflow does not send transactional store emails directly from your domain by default; these are typically handled by an integrated email provider. Check your Webflow Ecommerce settings and any connected email tools for their required SPF include: hostnames.
  5. Combine all required include: entries into a single SPF TXT record (you can only have one SPF record per domain).
  6. Verify with MXToolbox SPF Lookup.
Code · Generic HTML (dns)
v=spf1 include:_spf.google.com include:sendgrid.net include:spf.mailgun.org -all

Add this as a DNS TXT record at your domain apex ('@'). Replace the include: values with the exact hostnames from each of your email providers' SPF documentation. Only one SPF TXT record is allowed per domain — combine all providers into one record.

Missing strict transport securityQuick win

Add the Strict-Transport-Security (HSTS) response header to every HTTPS page so browsers always use an encrypted connection to your store.

On Webflow

  1. Webflow hosts sites on its own CDN with HTTPS enforced. The platform manages HSTS headers server-side.
  2. Go to your Webflow Project Settings → Hosting and confirm SSL is enabled for your custom domain.
  3. Publish your site and verify the Strict-Transport-Security header is present using browser DevTools → Network tab → select your homepage request → Response Headers.
  4. If the header is absent, contact Webflow Support. There is no dashboard toggle for HSTS in Webflow's current admin.
  5. If your domain is proxied through Cloudflare, enable HSTS in Cloudflare → SSL/TLS → Edge Certificates → HSTS section and set max-age=31536000 with includeSubDomains.
Code · Generic HTML (html)
<!-- HSTS cannot be set via an HTML meta tag. It MUST be sent as an HTTP response header. Use the server-level examples below. -->
<!-- Correct header value -->
<!-- Strict-Transport-Security: max-age=31536000; includeSubDomains -->

Meta tags cannot set HSTS — it must be a real HTTP response header. This is a reminder of the correct header value to use at the server level.

Missing x content type optionsQuick win

Add the `X-Content-Type-Options: nosniff` HTTP response header to every page and asset your store serves.

On Webflow

  1. Webflow does not provide a UI for custom HTTP response headers on its hosting.
  2. Use Cloudflare in front of your Webflow site: add your domain to Cloudflare, set Cloudflare as your DNS, then Rules → Transform Rules → Modify Response Header → add `X-Content-Type-Options: nosniff` → Save & Deploy.
  3. If you export your Webflow site and self-host it (Nginx/Apache), add the header directly to your server config as shown in the Generic HTML snippets.
Code · Generic HTML (http)
X-Content-Type-Options: nosniff

This is an HTTP response header — it is sent by the server, not placed in HTML markup. The examples below show where and how to set it.

Missing x frame optionsQuick win

Add the X-Frame-Options HTTP response header set to DENY or SAMEORIGIN to prevent your store's pages from being embedded in iframes on other sites.

On Webflow

  1. Webflow does not currently support setting arbitrary custom HTTP response headers from the Designer or project settings for standard hosted plans.
  2. For Webflow Enterprise: contact your Webflow account manager — Enterprise plans may support custom header configuration.
  3. For all Webflow plans: proxy your domain through Cloudflare → Rules → Transform Rules → Response Header Modification → Set X-Frame-Options = SAMEORIGIN → Save & Deploy.
  4. If you export your Webflow site and host it yourself (e.g., on Netlify or Vercel), add the header in netlify.toml or vercel.json — see the Headless / Custom entry.
  5. Verify with browser DevTools or securityheaders.com after the CDN rule or hosting config is live.
Code · Generic HTML (http)
X-Frame-Options: SAMEORIGIN

This is an HTTP response header — it must be sent by the server, not a meta tag. It should appear in the HTTP response headers for every page.

Passive scan onlyModerate effort

Set up regular active security testing (DAST) on a staging copy of your store to catch vulnerabilities that passive header/cookie/TLS scans cannot find.

On Webflow

  1. Duplicate your Webflow project as a staging copy: in the Webflow Dashboard, click the three-dot menu on your project → Duplicate.
  2. Publish the duplicate to its free webflow.io subdomain (not your custom domain) — it will not be indexed by default.
  3. Run OWASP ZAP → Automated Scan against the webflow.io staging URL, adding authentication context if you use Webflow's built-in Memberships or a third-party auth integration.
  4. For Webflow Commerce stores, specifically test the checkout flow and any custom-code embeds (Project Settings → Custom Code) for XSS.
  5. Fix vulnerabilities in the duplicate project's Designer or Custom Code settings, verify with re-scan, then replicate the same changes in your production project and re-publish.
  6. Check Webflow's security headers via Project Settings → Hosting — Webflow manages most headers centrally, but custom headers can be added via reverse proxy (e.g., Cloudflare) if needed.
Code · Generic HTML (html)
<meta name="robots" content="noindex, nofollow">

Add this meta tag to your staging site's <head> to prevent search engines from indexing it while DAST scanning is in progress.

Ssl cert expiring soonQuick win

Renew your SSL/TLS certificate before it expires to keep your store secure, trusted, and visible in search results.

On Webflow

  1. Webflow automatically provisions and renews SSL certificates for all sites published to webflow.io subdomains and custom domains added through the Webflow dashboard.
  2. To verify: go to Webflow Dashboard → select your project → Publishing → Custom Domains. Each domain shows an SSL status indicator.
  3. If SSL shows as 'Pending' or 'Error', confirm your domain's DNS A-record (for apex domains) or CNAME (for www) matches the values shown in Webflow's domain settings exactly.
  4. Click 'Check DNS' in the Webflow domains panel to trigger a re-check after updating DNS records.
  5. If the issue persists, remove and re-add the custom domain, then re-publish the site to trigger fresh certificate provisioning.
Code · Generic HTML (html)
<!-- Place this in your maintenance/holding page only if HTTPS is temporarily unavailable -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

Meta-refresh redirect for maintenance: not applicable to SSL renewal. Instead, use this HTML to display a temporary notice if your cert has lapsed and you need to redirect HTTP traffic while fixing HTTPS.

Ssl cert invalidModerate effort

Install a valid SSL/TLS certificate that exactly matches your store's domain name, so browsers trust your site and customer data is encrypted in transit.

On Webflow

  1. Webflow automatically provisions SSL certificates for all published sites, including custom domains, via Cloudflare.
  2. Go to your Webflow Dashboard → select your project → Publishing → Custom Domains.
  3. If the domain shows an SSL error, verify the DNS records at your registrar match exactly what Webflow specifies (an A record and a CNAME, or Webflow-provided nameservers).
  4. Once DNS is correctly configured, click 'Publish' or 'Re-verify SSL' in the Webflow Publishing panel. Webflow/Cloudflare will auto-provision the certificate (usually within minutes).
  5. If you transferred your domain to Webflow's registrar, SSL is managed entirely by Webflow and no manual action is needed after transfer.
Ssl errorModerate effort

Replace or reissue your SSL/TLS certificate so it is valid for the exact domain name your store uses, eliminating the hostname mismatch error.

On Webflow

  1. Webflow automatically provisions SSL certificates for published sites on webflow.io subdomains and for custom domains added through the Webflow dashboard.
  2. Go to: Webflow Designer → Site Settings (gear icon) → Publishing → Custom Domains.
  3. Add or re-verify your custom domain. Webflow will display the required DNS records (A record and CNAME) — ensure these are set at your domain registrar.
  4. Click 'Publish' after DNS records are confirmed; Webflow will provision an SSL certificate automatically.
  5. If the certificate shows a mismatch, remove the custom domain from Webflow, confirm DNS has fully propagated (use a DNS checker), then re-add the domain.
  6. Webflow does not support uploading custom SSL certificates — certificates are fully managed by the platform.
Ssl not accessibleModerate effort

Enable HTTPS on your store by opening port 443 and installing a valid SSL/TLS certificate so every page is served over a secure connection.

On Webflow

  1. Webflow automatically provisions free SSL certificates and manages port 443 for all sites hosted on Webflow's infrastructure.
  2. In Webflow Designer, go to Project Settings → Hosting.
  3. Ensure your custom domain is added and DNS is configured as instructed (A record or CNAME per Webflow's dashboard guidance). Webflow will auto-provision the certificate.
  4. Confirm 'HTTPS Redirect' is enabled in Project Settings → Hosting → Custom Domain section — this ensures port-80 traffic redirects to 443.
  5. If you see an SSL error, remove and re-add the custom domain in Project Settings → Hosting to trigger re-provisioning.
  6. For Webflow sites exported and hosted on your own server, open port 443 in your server firewall and install a TLS certificate (see generic steps).
Vulnerable component axiosModerate effort

Update the Axios JavaScript library to its latest stable release to eliminate known security vulnerabilities in your storefront code.

On Webflow

  1. In the Webflow Designer, open Project Settings → Custom Code tab. Search the Head Code and Footer Code sections for any <script> tags loading Axios.
  2. Update any found CDN <script> src URLs to the latest Axios version (e.g. https://cdn.jsdelivr.net/npm/axios@latest/dist/axios.min.js) and save/publish.
  3. Also check individual Page Settings (click the gear icon on each page in the Pages panel) → Custom Code sections for page-level script tags.
  4. If Axios is bundled in a custom JS file uploaded to Assets, rebuild the bundle locally with `npm install axios@latest && npm run build` and re-upload the compiled file to Webflow Assets, then republish the site.
Code · Webflow (html)
<script src="https://cdn.jsdelivr.net/npm/axios@1.7.9/dist/axios.min.js" crossorigin="anonymous"></script>

Paste this into Webflow Project Settings → Custom Code → Footer Code (replacing any older Axios script tag).

Vulnerable component jqueryModerate effort

Update jQuery to the latest stable version (3.7+) to eliminate known cross-site scripting (XSS) and prototype-pollution vulnerabilities that attackers actively exploit.

On Webflow

  1. In the Webflow Designer, open Project Settings (gear icon) → Custom Code tab.
  2. Inspect the 'Head Code' and 'Footer Code' sections for any <script> tags that load jQuery — update any found to 'https://code.jquery.com/jquery-3.7.1.min.js'.
  3. Also open each Page's settings (Pages panel → page gear icon → Custom Code) and repeat the check for page-level head/footer code.
  4. Webflow itself does not bundle jQuery in its core output unless you add it manually, so the risk is limited to custom code you or a developer added.
  5. If you use Webflow Apps or embedded third-party widgets that inject jQuery, contact those vendors for updates.
  6. Publish your site and use DevTools → Network to verify only the updated jQuery version loads.
Code · Generic HTML (html)
<!-- Remove your old jQuery script tag and replace with this -->
<script
  src="https://code.jquery.com/jquery-3.7.1.min.js"
  integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
  crossorigin="anonymous"></script>

Replace any old jQuery <script> tag in your HTML <head> with this. Get the current SRI hash from https://code.jquery.com/

Weak spfQuick win

Add a hard-fail (-all) or soft-fail (~all) terminator to your SPF DNS record so that mail servers reject or flag email sent from servers not explicitly authorized by your domain.

On Webflow

  1. Webflow does not provide a full DNS editor for custom records on all plans. DNS for Webflow sites is typically managed externally at your registrar or a DNS provider.
  2. Log in to your DNS provider (Cloudflare, GoDaddy, Namecheap, etc.) where your domain's nameservers point.
  3. Find the DNS / Zone management section and locate the TXT record for `@` (root domain) starting with `v=spf1`.
  4. Edit the record value to end with `-all` or `~all`. Save the change.
  5. Verify with an SPF validation tool after propagation.
Code · Generic HTML (dns)
v=spf1 include:_spf.google.com include:sendgrid.net -all

DNS TXT record value for your root domain (@). Replace the include/redirect mechanisms with the ones your email service provider specifies, and always end with -all or ~all.

X content type options weakQuick win

Set the X-Content-Type-Options response header to exactly `nosniff` (once, not duplicated) on every page and asset your store serves.

On Webflow

  1. Webflow automatically sends `X-Content-Type-Options: nosniff` on all hosted projects.
  2. If a duplicate is found, check whether you have Cloudflare or another reverse proxy on your custom domain: Cloudflare Dashboard → Rules → Transform Rules → Modify Response Headers — delete any rule that also sets `X-Content-Type-Options`.
  3. For Webflow sites exported and self-hosted, add the header in your own server config (Apache `.htaccess` or Nginx server block) and ensure only one source sets it.
X frame options weakQuick win

Replace the weak X-Frame-Options header value with DENY or SAMEORIGIN to prevent clickjacking attacks on your store.

On Webflow

  1. Webflow does not provide a UI to set custom HTTP response headers for standard hosted sites.
  2. If your Webflow site is published to a custom domain and proxied through Cloudflare (common setup): log in to Cloudflare → your domain → Rules → Transform Rules → Modify Response Header → Add Rule → set Header Name: X-Frame-Options, Value: DENY, Action: Set.
  3. Also add a second rule for Content-Security-Policy with value: frame-ancestors 'none';
  4. If not using Cloudflare: contact Webflow support to report the issue, or export your site and host it on a server where you control headers (e.g. Netlify — see below).
  5. For Webflow exported sites on Netlify: add a netlify.toml file to the repo root with the headers block shown in the code snippets below.
Code · Generic HTML (html)
<!-- X-Frame-Options MUST be sent as an HTTP response header, not a meta tag. -->
<!-- Use the server-side or platform snippets below instead. -->

<!-- The modern equivalent you CAN set via meta tag (limited support): -->
<meta http-equiv="Content-Security-Policy" content="frame-ancestors 'none';">

If you cannot set server-level headers, use this meta-equivalent only as a last resort — note that X-Frame-Options via meta tag is NOT supported by browsers (it must be an HTTP header). This snippet is shown for illustration only; use server-side headers.

Site Lifecycle · 12 fixes

Cms versionModerate effort

Update your CMS and ecommerce platform to the latest stable version immediately to close known security vulnerabilities and maintain vendor support.

On Webflow

  1. Webflow's hosting platform is SaaS — core updates are managed by Webflow automatically.
  2. For Webflow sites using custom code (Page Settings > Custom Code or Site Settings > Custom Code), audit any JavaScript libraries loaded from CDNs and update version numbers in the script tags to the latest stable release.
  3. If using Webflow Apps (the Apps Marketplace), open the Apps panel in the Designer and check for update notices on installed apps.
  4. Webflow Commerce: payment integrations (Stripe) are managed by Webflow, but review your Webflow plan to ensure you are on a current commerce plan with full support.
Code · Generic HTML (html)
<!-- REMOVE or suppress this tag from your <head> — it advertises your CMS version to attackers -->
<!-- <meta name="generator" content="WordPress 5.8.1" /> -->

<!-- In WordPress, add this to your theme's functions.php to remove it: -->
<!-- remove_action('wp_head', 'wp_generator'); -->

If your site injects a generator meta tag exposing your CMS version publicly, remove it to reduce information exposure to attackers. This does not replace upgrading — it is a secondary hardening step.

Domain expiryQuick win

Log in to your domain registrar, confirm your domain's expiry date, and enable auto-renew so your store never goes offline due to an expired domain.

On Webflow

  1. For domains purchased through Webflow: go to Webflow Dashboard → select your project → Publishing → Custom Domains. Click the gear icon next to the domain. Webflow renews these automatically; verify the payment method under Account Settings → Billing.
  2. For externally registered domains: log in to your registrar directly. Webflow's domain panel will show the domain as 'Connected' but cannot manage the renewal — you must do it at the registrar.
Code · Generic HTML (html)
<!-- No HTML change required. Domain renewal is managed at your domain registrar's dashboard (e.g. GoDaddy, Namecheap, Cloudflare Registrar). Log in, find the domain, enable auto-renew, and confirm a valid payment method is on file. -->

No code change is needed on your website — this fix is completed entirely in your registrar's dashboard. There is no markup or configuration file involved.

Gtm auditModerate effort

Install Google Tag Manager on your store and configure it to fire GA4 with full ecommerce event tracking (view_item, add_to_cart, purchase, and more).

On Webflow

  1. In the Webflow Designer, go to Project Settings › Custom Code.
  2. Paste the GTM <script> snippet in the 'Head Code' field and the <noscript> snippet in the 'Footer Code' field. Save and republish your site.
  3. For ecommerce events, Webflow's native ecommerce does not auto-populate a GA4 data layer — add custom scripts on product pages and the order confirmation page to push data layer events, or use a third-party connector such as Analyzify.
  4. In GTM, create GA4 Event tags for the data layer events and publish.
  5. Verify in GTM Preview and GA4 DebugView.
Code · Generic HTML (html)
<!-- Google Tag Manager (paste in <head>) -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
<!-- End Google Tag Manager -->

<!-- Google Tag Manager (noscript) (paste immediately after <body>) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

Replace GTM-XXXXXXX with your real Container ID. First snippet goes in <head>, second immediately after <body>.

Html langQuick win

Add a lang attribute to your site's <html> tag that matches the primary language of your store's content.

On Webflow

  1. In the Webflow Designer, open Project Settings (the gear icon at the top left).
  2. Go to the General tab and find the Language field (or Locale settings if you are on a localization plan).
  3. Set the primary language code (e.g., 'en'). Webflow injects this as the lang attribute on the <html> element automatically.
  4. For multi-locale projects using Webflow Localization, each locale is assigned its own language code, which Webflow outputs on the <html> tag per locale automatically.
  5. Publish your site and verify with View Source that <html lang="en"> (or your code) appears.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Your Store</title>
</head>
<body>
  <!-- page content -->
</body>
</html>

Place this as the very first tag in your HTML document, before <head>.

Lifecycle oos schema not updatedModerate effort

Update the `offers.availability` value in your Product structured data from `InStock` to `OutOfStock` (or `PreOrder`) whenever a product sells out, so Google's records always match your actual inventory.

On Webflow

  1. Webflow Ecommerce generates Product schema, but its automatic schema support is limited. Many Webflow stores add Product JSON-LD via custom embed code blocks.
  2. In the Webflow Designer, open the product template page (CMS → Products → Product Template). Look for any Embed elements containing JSON-LD (`<script type='application/ld+json'>`).
  3. If the JSON-LD is in an Embed, update the `availability` field to use a Webflow CMS dynamic binding: bind it to a CMS field that stores the stock status (e.g. a field called 'In Stock' toggle or an 'Availability' option field). You will need to create that CMS field if it does not exist.
  4. Set up a workflow (manual or via Zapier/Make) to update that CMS availability field whenever a product's stock hits zero.
  5. For fully custom JSON-LD with dynamic values, use Webflow's Finsweet CMS attributes or a third-party script that pulls live inventory. Validate with Google's Rich Results Test.
Code · Webflow (html)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "{{wf {\"path\":\"name\",\"type\":\"PlainText\"}  }}",
  "description": "{{wf {\"path\":\"description\",\"type\":\"PlainText\"}  }}",
  "sku": "{{wf {\"path\":\"sku\",\"type\":\"PlainText\"}  }}",
  "offers": {
    "@type": "Offer",
    "priceCurrency": "USD",
    "price": "{{wf {\"path\":\"price\",\"type\":\"Price\"}  }}",
    "availability": "{{wf {\"path\":\"availability-schema-url\",\"type\":\"PlainText\"}  }}"
  }
}
</script>
<!-- IMPORTANT: Create a CMS field called 'availability-schema-url' (Plain Text).
     Populate it with 'https://schema.org/InStock' or 'https://schema.org/OutOfStock'
     for each product. Use a Zap or Make scenario to set it to OutOfStock
     when stock hits 0. -->

Add this as a Custom Embed element on the Webflow Product Template page. Replace CMS field bindings with your actual Webflow CMS field names via the Designer's dynamic binding panel.

Lifecycle orphaned productsModerate effort

Add internal links pointing to every orphaned product page from relevant category pages, collection pages, or other on-site content so search crawlers and shoppers can discover them.

On Webflow

  1. Open the Webflow Designer and go to the CMS → Products collection.
  2. Open each orphaned product's CMS item and ensure it is assigned to the correct Category (a reference or multi-reference field). Publish.
  3. In the Designer, open your Category Collection Page template and confirm a Collection List element is bound to the Products collection, filtered by category — this makes newly categorized products automatically appear.
  4. Add the Category to your navigation: in the Designer, open your Nav symbol/component and add a link to the relevant Category page.
  5. For related products on the Product Page template, add a Collection List filtered to the same category and set a limit of 4 — orphaned products now correctly assigned to the category will appear automatically.
Code · Generic HTML (html)
<!-- Add this inside a relevant category page, blog post, or 'Featured Products' section -->
<a href="/products/your-orphaned-product-slug">Product Display Name</a>

Add a simple text link from any existing page (category, homepage, blog post) pointing directly to the orphaned product URL.

Lifecycle products missing from sitemapQuick win

Add all canonical product URLs to your XML sitemap so search engines discover and index every product page quickly.

On Webflow

  1. Go to Webflow Designer → Project Settings (top-left) → SEO tab → confirm 'Auto-generate sitemap' is ON.
  2. In the CMS Collections panel, find your Products collection → open Collection Settings → confirm 'Include in sitemap' is enabled.
  3. For individual CMS items (products): open the item in the CMS, scroll to SEO settings, and ensure the 'Exclude from sitemap' toggle is OFF.
  4. Republish your site after making any changes: click Publish in the Designer.
  5. Visit yourdomain.com/sitemap.xml to verify the product URLs now appear, then submit in Google Search Console → Indexing → Sitemaps.
Code · Generic HTML (xml)
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

  <!-- Example product page entry -->
  <url>
    <loc>https://www.example.com/products/blue-running-shoes</loc>
    <lastmod>2024-06-01</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>

  <!-- Repeat one <url> block per canonical product page -->
  <url>
    <loc>https://www.example.com/products/red-yoga-mat</loc>
    <lastmod>2024-05-20</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>

</urlset>

Standard XML sitemap structure. Place this at yourdomain.com/sitemap.xml (or reference it from a sitemap index file).

Lifecycle products too deepModerate effort

Move deeply buried product pages closer to the homepage by flattening your site's navigation structure so every product is reachable within 4 clicks (ideally 3) from the homepage.

On Webflow

  1. In the Webflow Designer, open the Pages panel and review your CMS Collection pages and static pages hierarchy.
  2. For Webflow Commerce: go to the Ecommerce → Products panel and review product categories. Add buried products to a top-level category so they appear on a shallower Collection List page.
  3. On your homepage canvas, add a Collection List element, bind it to Products, and filter/sort it to feature the previously deep products — this creates a direct 1-click homepage path.
  4. In the Webflow Navigator, check your Navbar component. Add direct links to important collection pages so they appear in the primary navigation rather than only in dropdown sub-menus.
  5. Publish and then use a crawler (e.g. Screaming Frog pointed at your webflow.io or custom domain) to confirm click depth is resolved.
Code · Generic HTML (html)
<!-- Featured Products section on homepage or top category page -->
<section aria-label="Featured Products">
  <h2>Featured Products</h2>
  <ul>
    <li><a href="/products/product-name-1">Product Name 1</a></li>
    <li><a href="/products/product-name-2">Product Name 2</a></li>
    <li><a href="/products/product-name-3">Product Name 3</a></li>
  </ul>
</section>

Add direct internal links to deep products from your homepage or top-level category pages. Place this markup in a 'Featured Products' or 'Quick Links' section.

Mixed contentModerate effort

Ensure every resource (images, scripts, stylesheets, fonts, iframes) on your store is loaded over HTTPS, never HTTP, so browsers never block or warn about mixed content.

On Webflow

  1. Webflow serves hosted assets over HTTPS. Mixed content usually originates from custom embed components or custom code in Page Settings.
  2. In the Webflow Designer, select any Embed component on affected pages → click the element → edit the HTML and replace 'http://' with 'https://' in all src/href attributes.
  3. Go to Page Settings (gear icon per page) → Custom Code (Head/Body) and update any HTTP resource URLs.
  4. Go to Project Settings → Custom Code (site-wide Head/Body) and do the same.
  5. Publish and verify with browser DevTools Console.
Code · Generic HTML (html)
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

Add this meta tag inside <head> to instruct browsers to automatically upgrade HTTP sub-requests to HTTPS. This is a safety net — fix the root URLs too.

Mobile viewportQuick win

Add a responsive viewport meta tag to every page so mobile browsers render your site at the correct width and scale.

On Webflow

  1. Webflow adds the viewport meta tag automatically to all published sites.
  2. In the Webflow Designer, go to Page Settings (the gear icon on any page) → SEO tab → confirm no conflicting meta tags are entered in the Custom Code → Head Code field.
  3. For site-wide custom code, go to Project Settings → Custom Code → Head Code and check for any duplicate or conflicting viewport tags.
  4. Remove duplicates, publish, and verify with Chrome DevTools mobile emulation.
Code · Generic HTML (html)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Page Title</title>
</head>
<body>
  <!-- page content -->
</body>
</html>

Place the viewport meta tag as early as possible in <head>, before any CSS or other meta tags.

Server versionQuick win

Remove or suppress HTTP response headers that advertise your server software name and version to the public internet.

On Webflow

  1. Webflow's hosting layer controls 'Server' headers — they cannot be changed from the Webflow Designer or Project Settings.
  2. For custom code or API calls made from Webflow Logic or embedded scripts, ensure any server-side code you control suppresses its own headers.
  3. Add Cloudflare as a proxy (set Webflow's custom domain DNS to Cloudflare, enable proxy/orange-cloud), then use Rules → Transform Rules → Modify Response Header to remove 'X-Powered-By'.
  4. Verify in DevTools that the header is absent or neutralised after Cloudflare is active.
Code · Generic HTML (html)
<!-- HTTP response headers cannot be set via HTML meta tags.
     Use the server-side or CDN configuration snippets below. -->

Meta tags cannot suppress HTTP response headers — this must be done at the server/CDN layer. The snippets below cover the most common server/framework approaches.

Ssl expiryQuick win

Renew your TLS/SSL certificate before it expires and enable automatic renewal so your store never goes offline or shows security warnings.

On Webflow

  1. Webflow automatically provisions and renews SSL certificates for all published sites on paid hosting plans.
  2. Verify: go to your Webflow Dashboard → Project Settings → Hosting, and confirm your custom domain shows a green SSL indicator.
  3. If SSL shows as 'Pending' or 'Error', try removing and re-adding your custom domain, then re-publish your site.
  4. Ensure your domain's DNS A record and CNAME (for www) point correctly to Webflow's servers.
Code · Generic HTML (html)
<!-- SERVER-SIDE redirect is strongly preferred. Example nginx config snippet below. -->
<!-- nginx: force HTTPS -->
<!-- server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
} -->

Verify your HTTPS redirect is in place at the server/app level. This meta-refresh is a last-resort fallback only — implement a proper server-side 301 redirect instead.

Generative Engine Optimization · 9 fixes

Ai crawler blockedQuick win

Your robots.txt explicitly blocks one or more AI assistants from crawling your store, so those engines can't discover, index, or cite your products when shoppers ask for recommendations.

On Webflow

  1. Open Project Settings > SEO > Indexing > robots.txt.
  2. Remove the 'Disallow: /' rule under the AI bot's user-agent, or add an explicit 'User-agent: <bot>' then 'Allow: /'.
  3. Publish the site and verify at https://yourdomain.com/robots.txt.
Ai crawler not specifiedQuick win

Your robots.txt doesn't explicitly allow several AI crawlers. They aren't blocked, but a number of AI engines act on explicit permission, so naming them removes any ambiguity.

On Webflow

  1. Open Project Settings > SEO > Indexing > robots.txt.
  2. Remove the 'Disallow: /' rule under the AI bot's user-agent, or add an explicit 'User-agent: <bot>' then 'Allow: /'.
  3. Publish the site and verify at https://yourdomain.com/robots.txt.
Geo brand name inconsistentModerate effort

Your brand name appears in multiple different formats across your site (schema, og:site_name, footer, logo). AI engines may treat the variants as separate entities.

On Webflow

  1. Add or extend JSON-LD via an Embed element on the Collection (product) template, binding CMS fields for name/price/image/sku.
  2. Add a global Organization JSON-LD with sameAs in Project Settings > Custom Code (head).
  3. Publish and validate with Google's Rich Results Test.
Geo eeat trust signals weakModerate effort

Your store is missing trust signals (E-E-A-T) that AI engines and shoppers look for before recommending a store: clear policies, real contact details, and a credible business identity.

On Webflow

  1. Add or extend JSON-LD via an Embed element on the Collection (product) template, binding CMS fields for name/price/image/sku.
  2. Add a global Organization JSON-LD with sameAs in Project Settings > Custom Code (head).
  3. Publish and validate with Google's Rich Results Test.
Geo faqpage missing productsModerate effort

Your product pages don't use FAQPage schema, so AI assistants can't pull direct answers to common buyer questions (sizing, materials, shipping) about your products.

On Webflow

  1. Add or extend JSON-LD via an Embed element on the Collection (product) template, binding CMS fields for name/price/image/sku.
  2. Add a global Organization JSON-LD with sameAs in Project Settings > Custom Code (head).
  3. Publish and validate with Google's Rich Results Test.
Geo price not crawlableModerate effort

Your product prices appear to be rendered by JavaScript and may not be present in the static HTML, so AI crawlers (which often don't run JS) can't read or cite them.

On Webflow

  1. Add or extend JSON-LD via an Embed element on the Collection (product) template, binding CMS fields for name/price/image/sku.
  2. Add a global Organization JSON-LD with sameAs in Project Settings > Custom Code (head).
  3. Publish and validate with Google's Rich Results Test.
Geo schema org missing sameasModerate effort

Your Organization schema has no sameAs links to your social profiles, Wikipedia, or other authoritative sources - so AI engines can't confidently connect the dots about your brand.

On Webflow

  1. Add or extend JSON-LD via an Embed element on the Collection (product) template, binding CMS fields for name/price/image/sku.
  2. Add a global Organization JSON-LD with sameAs in Project Settings > Custom Code (head).
  3. Publish and validate with Google's Rich Results Test.
Geo schema product incompleteModerate effort

Your Product structured data is missing fields AI engines rely on to cite your products accurately (price, availability, currency, brand, ratings).

On Webflow

  1. Add or extend JSON-LD via an Embed element on the Collection (product) template, binding CMS fields for name/price/image/sku.
  2. Add a global Organization JSON-LD with sameAs in Project Settings > Custom Code (head).
  3. Publish and validate with Google's Rich Results Test.
Llms txt missingQuick win

Your store has no llms.txt - the emerging standard (like robots.txt, but for AI) that tells language models which content to read and cite. SEOLZ has generated a ready-to-upload file from your own pages.

On Webflow

  1. Webflow can't serve an arbitrary root .txt directly; host llms.txt on your CDN or use a reverse proxy / Cloudflare Worker mapping /llms.txt to the generated content.
  2. Verify at https://yourdomain.com/llms.txt.