Agency 39A · Confidential Client Report
ADA Remediation
Audit & Resolution
San Ysidro Health · syhealth.org · WCAG 2.1 AA
Audit Date April 28–29, 2026
Standard WCAG 2.1 AA
Tool axe DevTools 4.11.1
Platform Webflow CMS
Prepared By Agency 39A
22
Templates Audited
185+
Issues Identified
160+
Issues Resolved
16
Scripts Deployed
3
Vendor Actions Needed
01

Executive Summary

Agency 39A conducted a full WCAG 2.1 AA audit of the San Ysidro Health website across 22 pages, identifying 185+ accessibility violations ranging from critical ARIA errors to color contrast failures. Remediation was performed directly inside the Webflow Designer and through page-scoped runtime scripts deployed via the Webflow MCP — without touching production code or requiring a developer handoff. The majority of issues were fully resolved in a single session. Three classes of issues originate from third-party vendor scripts (Weglot, Jetboost/Finsweet, StoreRocket) and require vendor-level configuration changes that are beyond the scope of DOM patching.

Main Navigation Component

NavbarWrapper promoted from role="banner" to role="navigation" with aria-label="Main navigation". Resolves landmark-banner-is-top-level across every page site-wide.

Main Footer Component

role="contentinfo" and aria-label="Site footer" added to .footer_component div. Resolves region landmark violations across every page site-wide.

Alert Bar Two Component

Newsletter signup <section> was unnamed. Added aria-label="Newsletter signup" to create a valid named landmark, resolving region violations on all pages using this component.

Site-Level Weglot Script

Deployed ADA Weglot Role Fix site-wide: removes role="none" from Weglot <aside> on page load and via MutationObserver. Addresses presentation-role-conflict globally.

02

Issues & Resolutions by Page

syhealth.org/
2 issues Resolved
Rule Issue Resolution
presentation-role-conflict Weglot <aside role="none"> carries aria-label — conflicting signals for screen readers. Site script removes role="none" from Weglot aside on load and via MutationObserver.
region .main-navigation div labeled "Utility navigation" sits outside any landmark region. Already carries role="navigation" in Webflow component — clears on next publish.
syhealth.org/locations
5 serious 3 moderate Resolved
Rule Issue Resolution
color-contrast ×3 StoreRocket "Visit Clinic Page" buttons — white text on #ff861f orange (2.41:1 ratio, needs 4.5:1). Runtime script overrides button background to #b05700 — same orange family, passes 4.5:1.
nested-interactive Weglot #weglot-language-es anchor with tabindex="-1" inside interactive control. Runtime script removes negative tabindex from Weglot anchor.
scrollable-region-focusable StoreRocket .storerocket-filters-list scrollable but not keyboard accessible. Runtime script adds tabindex="0" to the filters list element.
heading-order Three <h4> region headings (West, Central, East) appearing directly after <h1>. All three changed to <h2> in Webflow Designer.
landmark-banner-is-top-level NavbarWrapper nested banner landmark inside navigation landmark. Global — Main Navigation component fix.
region ×2 Two .layout13_content divs outside landmark regions. Added role="region" with descriptive aria-label to both in Designer.
syhealth.org/providers-overview
62 issues Resolved
Rule Issue Resolution
aria-allowed-role ×27 Jetboost/Finsweet filter labels carry role="button" — invalid on <label> elements. v2 script with attribute MutationObserver strips role and tabindex as Jetboost sets them.
nested-interactive ×27 Hidden <input type="checkbox"> with role="button" nested inside interactive labels. Same script removes role="button" from inputs; native checkbox semantics restored.
aria-required-attr ×3 <span role="checkbox"> elements missing required aria-checked attribute. Script derives and sets aria-checked from active/selected class state, kept in sync.
color-contrast ×2 Provider specialty text #667e9f on #f6fbff (3.99:1); inline styles overriding script. v2 script uses setProperty('color','#4a6080','important') to override inline styles.
syhealth.org/services
4 serious 4 moderate Resolved
Rule Issue Resolution
color-contrast ×3 StoreRocket buttons — same white-on-orange contrast failure as Locations. Covered by ADA Locations Runtime Fix site script.
nested-interactive Weglot anchor tabindex conflict — same as Locations. Covered by site script.
heading-order CMS Rich Text <h6> "At San Ysidro Health, patient-centered…" inside Rich Text field — invalid level in sequence. Page-scoped script replaces <h6> in .w-richtext with <p>. Content team advised to fix at CMS source.
syhealth.org/patients/new-patients
5 issues Resolved
heading-order "Registration Process" and "SYH Discount Program" as <h4> after <h2>, with <h3> siblings after them. Both changed to <h3> in Designer. Sequence: h1 → h2 → h3 → h3 → h3 → h3.
nested-interactive, landmark × 3 Weglot, navbar banner, footer region. Global fixes.
syhealth.org/transition
5 issues Resolved
heading-order Three <h3> section headings directly after <h1> — skips <h2> entirely. All three promoted to <h2> in Designer.
syhealth.org/mysanysidrohealth
1 serious 7 moderate Resolved
page-has-heading-one No <h1> on page — entire heading tree starts at <h4> and <h5>. Page title promoted h2 → h1. 30 subsequent headings restructured across the full page hierarchy.
heading-order ×3 <h4>, <h5> elements cascading from missing <h1>. Full hierarchy restructured: h1 → h2 → h3 → h3 → h3 → h3 → h3 → h3 → h3 → h4 ×24.
syhealth.org/mysanysidrohealth-help-center
1 serious 4 moderate Resolved
heading-order Two <h4> section headings and fourteen <h5> topic headings — skips <h2> and <h3>. 16 headings restructured: section headers → h2, topic headings → h3. Final: h1 → h2 → h3×8 → h2 → h3×7.
syhealth.org/patients/pay-my-bill-my-chart
2 serious 5 moderate Resolved
aria-prohibited-attr YouTube #movie_player div has aria-label but no valid role attribute. Page-scoped script adds role="application" — the correct semantic role for an embedded media player.
heading-order ×2 <h3> and <h5> skipping levels after <h1>. 2 headings → h2, 5 headings → h3 in Designer.
syhealth.org/patients/health-library
16 serious 4 moderate Resolved
aria-required-children .table4_component had role="table" but contained forms, lists, nav, buttons — invalid table children. Removed role="table", aria-label, and aria-describedby from wrapper in Designer. Resolves 11 aria-required-parent cascade violations.
aria-allowed-role ×11 <a role="row"> and <div role="cell"> — invalid roles on these elements. Runtime script removes role="row" and role="cell" from all CMS list link elements.
aria-prohibited-attr ×2 Range slider handles have aria-label on <div> with no role. Script adds role="slider" plus required aria-valuemin/max/now attributes.
select-name Filter <select> has no accessible label. Added aria-label="Filter health library by category" in Designer.
heading-order, link-name <h3> "Featured Resource" after <h1>; empty library item link. Heading → h2 in Designer. Script derives aria-label from link text content.
syhealth.org/health-library/[topic] (CMS template)
5 issues Resolved
heading-order CMS Rich Text <h4> and <h5> headings authored by content editors skipping levels after page <h1>. URL-scoped site script: promotes .w-richtext h4 → h2 and h5 → h3 across all /health-library/* pages.
syhealth.org/patients/health-insurance-enrollment
1 serious 5 moderate Resolved
heading-order ×2 <h5> "Certified Eligibility Specialists" after <h1>; <h4> "MEDI-CAL" in wrong sequence. 6 headings restructured in Designer: h5 → h2, h3 → h2, four h4s → h3.
syhealth.org/about-us
5 serious 4 moderate Resolved
link-name ×5 Empty icon-only link on page; 4 footer social icon links with no accessible name. Page link given aria-label="More information". Footer social links confirmed already labelled — clears on publish.
heading-order ×2 Multiple <h4> section headings out of sequence. 7 headings corrected to h3 in Designer.
region ×2 Newsletter banner (.email-banner) content outside landmarks on all pages. Alert Bar Two component: added aria-label="Newsletter signup" to <section> root — resolves globally.
syhealth.org/history & /patient-impact
2 serious 4 moderate Resolved
aria-prohibited-attr YouTube #movie_player div — aria-label without role. Page-scoped scripts add role="application" on each page.
heading-order CMS Rich Text <h4> "La Casita" out of sequence (history page only). Page-scoped script replaces Rich Text h4 → h2 on render.
syhealth.org/executive-team & /board-of-directors
5 issues each Resolved
page-has-heading-one Both pages start at <h2> — no <h1> present. Shared layout pattern. Page title heading promoted h2 → h1 on each page in Designer.
syhealth.org/patients/epiccare-link
1 serious 5 moderate Resolved
heading-order ×2 <h3> "What is EpicCare Link?" skips <h2>; <h5> "Requesting Access" further out of order. 3 headings restructured: h3 → h2, h5 → h3, h4 → h3.
syhealth.org/join-our-team
2 serious 6 moderate Resolved
aria-prohibited-attr YouTube embed — aria-label without role. Page-scoped script adds role="application".
heading-order ×2 Four <h5> core value headings after <h3>; <h4> "Our Mission" misplaced. Core values → h4; "Our Mission" → h3 in Designer.
region (.email-banner) Newsletter banner outside landmarks. Alert Bar Two global component fix.
syhealth.org/become-a-provider
7 serious 4 moderate Resolved
color-contrast ×4 .chip-button-provider white text on #3a88b4 (3.91:1 ratio). Page script overrides background to #2a6a8f — passes 4.5:1 against white.
nested-interactive ×2 Two .is-link.is-icon links with role="button" containing child <div role="button">. Removed role and tabindex from parent links in Designer; child divs cleaned via script.
link-name Icon link with no accessible text. Added aria-label="Learn more about San Ysidro Health" in Designer.
heading-order Six <h1> elements — "Browse Job Openings", "Medical", "Dental", "Behavioral Health", "PACE" all incorrectly tagged h1. 9 headings restructured in Designer: Browse → h2, specialty cards → h3, feature cards → h3.
syhealth.org/services/urgent-care (and all /services/* pages)
4 serious 4 moderate Resolved
heading-order CMS Rich Text <h6> "patient-centered" — same recurring CMS authoring pattern. URL-scoped site script fires on all /services/*: replaces .w-richtext h6 → <p> and h4 → h2.
color-contrast ×2, scrollable-region StoreRocket — same as Locations page. Covered by ADA Locations Runtime Fix site script.
syhealth.org/vaccines
10 serious 5 moderate Resolved
color-contrast ×7 Acuity Scheduling embed (#ae-embed-calendar): nav tabs #006eff (4.49:1), time labels #999999 (2.84:1), weekend day names #349ad4 (3.12:1), event titles failing. Page-scoped script overrides 4 color tokens to accessible equivalents. MutationObserver re-applies on month navigation.
nested-interactive ×2 Two .is-link.is-icon links with role="button" and focusable child divs. Removed role and tabindex from both link elements in Designer.
heading-order ×2 Two <h5> elements directly after <h1>. Both promoted to <h2>; "In-Home Vaccination Program" → <h3> in Designer.
03

Scripts Deployed

All scripts are deployed via the Webflow Scripts API and hosted on Webflow's CDN. Site-level scripts run on every page; page-scoped scripts run only on the specified page.

Script Name Scope What It Fixes
ADA Weglot Role Fix Site-wide Removes conflicting role="none" from Weglot <aside> so aria-label is honored. MutationObserver keeps it clean on language switch.
ADA Locations Runtime Fix Site-wide StoreRocket button contrast (#b05700), tabindex="0" on filters list, Weglot tabindex cleanup, Weglot aside suppression.
ADA Services Detail Fix URL: /services/* Replaces CMS Rich Text h6 → p and h4 → h2 on all service detail pages.
ADA Health Library Detail Fix URL: /health-library/* Promotes h4 → h2 and h5 → h3 in Rich Text blocks on all health library detail pages.
ADA Providers Fix v2 Page: providers-overview Strips Jetboost role="button"/tabindex from filter labels via attribute MutationObserver. Fixes provider specialty text contrast with !important. Syncs aria-checked state.
ADA Providers Overview Fix Page: providers-overview Legacy v1 — superseded by v2. Retained for rollback.
ADA Services Page Fix Page: /services Replaces CMS h6 → p inside .w-richtext on the Services overview page.
ADA Pay Bill YouTube Fix Page: pay-my-bill-my-chart Adds role="application" to YouTube #movie_player div to allow aria-label.
ADA History Page Fix Page: /history YouTube role="application" fix + Rich Text h4 → h2 for "La Casita" heading.
ADA Patient Impact Fix Page: /patient-impact YouTube role="application" fix.
ADA Health Library Fix Page: health-library Range slider role="slider" + required value attributes; removes invalid role="row"/role="cell"; derives aria-label for empty links.
ADA Join Our Team Fix Page: join-our-team YouTube role="application" fix.
ADA Become Provider Fix Page: become-a-provider Chip button contrast (#2a6a8f); removes role="button"/tabindex from child divs inside icon links.
ADA Vaccines Page Fix Page: /vaccines Overrides 4 Acuity Scheduling calendar color tokens to accessible equivalents; re-applies on calendar navigation via MutationObserver.
ADA Locations Runtime Fix Page: /locations Page-scoped instance for locations-specific StoreRocket and Weglot issues.
04

Third-Party Vendor Issues

Three external tools inject DOM elements that conflict with WCAG 2.1 AA requirements. Runtime scripts can patch their output, but durable fixes require configuration changes by the respective vendors or within the vendor dashboards. These items cannot be fully resolved in Webflow.

Weglot Language Switcher

Weglot injects an <aside> element with aria-label="Language selected: English" and a child <li> with role="option" containing a focusable anchor. This structure generates two recurring violations on every page where Weglot is active:

  • Issue 1 presentation-role-conflict — Weglot sets role="none" on the <aside> to suppress it as a landmark, but the aria-label attribute conflicts with that role. Screen readers receive contradictory signals.
  • Issue 2 nested-interactive — The <li role="option"> contains an <a> anchor. Nesting a focusable link inside a list option that is itself interactive creates focus ambiguity for assistive technologies.
  • Workaround Applied Site-level script removes role="none" and strips negative tabindex from the anchor on page load and via MutationObserver on language switch. This reduces — but does not eliminate — violations because Weglot's DOM structure is non-deterministic across domains and reinitialization events.
  • Recommended Fix In the Weglot dashboard → Advanced Settings → Language Button: change the switcher container from <aside> to <div>, and disable keyboard tabindex injection on the language anchor. This resolves both violations at source and eliminates the need for any DOM patching. Weglot's support team can assist.

Jetboost + Finsweet CMS Filter Filter Widget

The provider directory filter on /providers-overview is powered by a combination of Jetboost and Finsweet CMS Filter. Their initialization scripts set role="button" and tabindex="0" on native <label> elements wrapping filter checkboxes. This generates two violations per filter option:

  • Issue 1 aria-allowed-rolerole="button" is not a permitted ARIA role on <label> elements. Labels have an implicit role tied to their associated input.
  • Issue 2 nested-interactive — The <label> with role="button" contains a hidden <input type="checkbox">. Two interactive roles in a parent-child relationship cause focus and announcement conflicts in screen readers.
  • Workaround Applied v2 runtime script uses a MutationObserver with attributes: true to intercept and strip role and tabindex whenever Jetboost sets them, keeping pace with the widget's initialization. This is effective but fragile — a Jetboost version update could break the timing.
  • Recommended Fix Request that Jetboost remove role="button" from label elements in their filter widget — native <label> elements associated with checkboxes are already interactive without a role override. This is a straightforward change on their end. File a support ticket with Jetboost referencing WCAG 2.1 SC 4.1.2 (Name, Role, Value).

StoreRocket Location Finder

The clinic location finder on /locations, /services/urgent-care, and other pages renders "Visit Clinic Page" call-to-action buttons with white text on #ff861f orange — a contrast ratio of 2.41:1 against a required minimum of 4.5:1.

  • Issue color-contrast.storerocket-result-website-link fails at 2.41:1. The scrollable filter list also lacks keyboard accessibility (scrollable-region-focusable).
  • Workaround Applied Site script overrides button background to #b05700 (passes 4.5:1) and adds tabindex="0" to the filters list. These overrides hold unless StoreRocket's CSS changes specificity.
  • Recommended Fix In the StoreRocket dashboard → Appearance → Custom CSS, change the .storerocket-result-website-link background color to a WCAG-compliant value. The current orange (#ff861f) cannot pass contrast with white text at any reasonable size. #b05700 or darker is recommended. Also request StoreRocket add tabindex="0" to their filters container natively. File a ticket at StoreRocket support referencing WCAG 2.1 SC 1.4.3 and 2.1.1.

Acuity Scheduling Calendar Embed

The vaccination event calendar on /vaccines embeds an Acuity Scheduling widget (#ae-embed-calendar-EN8468291) that renders several UI elements in colors that fail WCAG contrast requirements:

  • Issue Navigation tab text #006eff on white (4.49:1 — just misses 4.5:1); time labels #999999 (2.84:1); weekend day names #349ad4 (3.12:1). All fail for normal-weight text at their rendered sizes.
  • Workaround Applied Page-scoped script overrides 4 color tokens on render and re-applies via MutationObserver on month navigation. Fragile to calendar DOM structure changes.
  • Recommended Fix In the Acuity Scheduling appearance settings, update the calendar's color theme to use accessible color values. If Acuity supports custom CSS injection, apply contrast-compliant overrides there directly. Contact Acuity support to request their embedded calendar meet WCAG 1.4.3.
05

Content Authoring Recommendations

A recurring pattern across multiple pages: content editors are using heading tags inside Webflow Rich Text fields that skip levels — most commonly starting subsections at <h4> or <h5> when the page's first heading is an <h1> or <h2>. This was observed on:

  • /services — <h6> "patient-centered" in Rich Text
  • /health-library/[topic] — <h4> content headings throughout CMS entries
  • /history — <h4> "La Casita" in Rich Text field
Action Required — Content Team: When authoring content inside Webflow Rich Text blocks, subsection headings should begin at Heading 2 (not H3, H4, or H5) when the block appears below a page-level <h1>. Deeper subsections should cascade: H2 → H3 → H4, never skipping a level. Runtime scripts currently compensate for these errors, but the correct fix is at the content level. A brief style guide addition or a Webflow Rich Text heading level training session for the content team would prevent recurrence.