About this audit
stripe.com scored 6.3/10
Excellent platform engineering — sandboxed third parties, strict email auth, clean AI-crawler surface — undercut by two keyboard blockers sitting on the sales-lead path and a marketing template that emits 153 <h1> elements on the pricing page.
This is a full example audit — 15 evidence-led findings across accessibility, performance, SEO, structured data, privacy and security, each with a measurement, a screenshot and a concrete fix. The headline issue: Contact-sales form: 43 invisible controls still accept keyboard focus — the 8th Tab lands on a hidden 'Back' button.
What we found
criticalContact-sales form: 43 invisible controls still accept keyboard focus — the 8th Tab lands on a hidden 'Back' button
/contact/sales renders every step of the sales funnel into the DOM at once and hides the inactive ones with opacity: 0; pointer-events: none — nothing else. We counted 11 .ContactSalesFormStep.isHidden containers, every one of them computing to visibility: visible, display: block, with no aria-hidden, no inert and no h
criticalFive buttons across four pages have no accessible name — including the call-booking date arrows
axe-core reports button-name (critical impact, WCAG 2.0 A, criterion 4.1.2 Name, Role, Value) on 5 elements across 3 audited pages, and we confirmed a sixth instance of the same component live on the English homepage. Each is an icon-only <button> with no aria-label, no title, no text node and no <title> inside its SVG
warning/pricing ships 153 <h1> elements against 4 <h2> — the document outline carries no hierarchy
Fetching the server-rendered HTML (so this is exactly what a crawler and a screen reader receive), /pricing contains 153 <h1> elements, 4 <h2> and 6 <h3>. /atlas has 36 H1s / 3 H2s, /contact/sales 15 H1s / 4 H2s. The cause is a shared component: Copy__title renders as <h1> regardless of nesting depth — on /pricing alon
warningAtlas: the brand orange puts the 'Start your company' CTA at 2.68:1 — 12 elements below AA
The Atlas accent #ff7600 fails WCAG AA everywhere it carries text. axe reports 12 failing elements on /atlas (color-contrast, serious, WCAG 2.1 AA, criterion 1.4.3), and a live scan of the page found 41 elements rendering in that colour. The one that matters commercially is the page's primary call to action. Start your
warning98 further contrast failures — 74 of them on the price fine print at /pricing
Beyond Atlas, axe reports 98 more elements below WCAG AA (color-contrast, serious, criterion 1.4.3): 74 on /pricing, 12 on / and 12 on /br. On /pricing the failures land on the qualifiers that change what a customer actually pays. #707f98 on #fafbfd at 3.91:1 covers + 0.5% for manually entered cards, + 1.5% for interna
warning11 of 23 pricing tooltips wrap a link inside a button — the subscription terms are unreachable by keyboard
axe reports nested-interactive (serious, WCAG 2.0 A, criterion 4.1.2) on 11 elements of /pricing. We reproduced it live: of the page's 23 .TooltipButton controls, 11 contain an <a href> inside the <button>. The nested links are not decorative. They point at support.stripe.com/questions/subscription-and-cancellation-ter
warningNo <main> landmark on 3 of 6 pages, and no skip link on any of them
Checking the server-rendered HTML of all six pages: /pricing, /atlas and /contact/sales contain zero <main> elements and zero [role="main"]. /, /br and /personalize each have exactly one. Separately, none of the six pages ships a skip link — no anchor matching "skip to" or "skip nav" exists in any of them. We confirmed
warningSix remaining WCAG violations: unreachable scroll regions, a malformed list, a focusable aria-hidden figure
Four further axe rules fire across the audited pages, all serious impact, 6 failing elements in total. Each is small and independently fixable. scrollable-region-focusable (WCAG 2.1.1 / 2.1.3) hits .bento-dialog-graphics on the homepage at mobile and .TestimonialCarousel__track on /atlas at all three viewports: both sc
warningHomepage LCP 4,759 ms and /br 5,908 ms on mobile — both roughly double the 2,500 ms target
Lighthouse (mobile, lab) scores the homepage at 46 and /br at 38. Largest Contentful Paint is 4,759 ms on / and 5,908 ms on /br, against a 2,500 ms "good" threshold; First Contentful Paint is 3,709 ms and 5,458 ms. Total Blocking Time is 1,139 ms and 1,162 ms — main-thread work heavy enough that taps go unanswered — an
warning273 KiB of unused JavaScript on the homepage — two chunks account for 191,889 bytes of it
Lighthouse's unused-javascript audit reports "Est savings of 273 KiB" on the homepage (279,335 bytes) and "Est savings of 291 KiB" on /br (298,140 bytes). Total page weight is 2,714 KiB and 2,828 KiB respectively. Two files carry most of it. pages/_app-7923da34be0042d5.js is 359,126 bytes with 75,424 unused; pages/inde
mobile/pricing runs 34,849 px tall at 375 px — the first per-product price sits 3,724 px down
Measured live at a 375 px viewport, /pricing is 34,849 px tall — 42.9 phone screens — and carries 6,204 DOM elements. The summary cards (Standard at 2.9% + 30¢, and Custom) are above the fold and read well. But the first per-product price card, #payments, begins at y = 3,724 px: about 4.6 screens of scrolling. #link fo
warning/pricing is the only page of six whose title omits the brand — 14 characters, 'Pricing & Fees'
Five of the six audited pages carry a branded title: Stripe | Financial Infrastructure to Grow Your Revenue (54 chars), Stripe Atlas | Incorporate your startup in Delaware: C corp or LLC (66), Stripe | Contact our Sales Team, Stripe | Personalize, and the pt-BR equivalent. /pricing is titled Pricing & Fees — 14 charact
warning/atlas and /contact/sales ship zero JSON-LD, and no page carries a BreadcrumbList
Structured-data coverage splits along the same template line as everything else. / and /br each ship one valid block containing a WebSite + Organization @graph — and it is genuinely thorough: legalName, both founders as Person, a contactPoint with five availableLanguage values, six office Place entries with full Postal
warning8 stylesheet fetches fail CORS on every Next.js page load — the CDN adds the header for /chunks/ but not /css/
Every load of / produces 8 identical CORS failures, one per stylesheet, on mobile, tablet and desktop alike: Access to XMLHttpRequest at 'https://b.stripecdn.com/mkt-ssr-statics/assets/_next/static/css/9d3a49263f73db6f.css' from origin 'https://stripe.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origi
warningYour tag-sandbox CSP allowlists *.google.com but not Google's country domains — remarketing pixels are blocked outside .com regions
Stripe runs all third-party marketing tags inside a CSP-sandboxed iframe at b.stripecdn.com/stripethirdparty-srv/assets/v33.1/GoogleTagManager.html. That is a good design — it is why a scan of the main document finds zero trackers and only two script hosts. But that sandbox's own img-src directive allowlists *.google.c