CurrencyTransfer.com · New header navigation · Technical SEO and accessibility review

Nav SEO & Accessibility Parity

Does the new menu give search engines and assistive tech what the current one does, at stage 1 (static prototype in the theme) and stage 2 (WordPress walker)? Verdicts, the fixes that must land first, the sitewide link-equity delta, and the markup contract the walker has to satisfy.

24 September 2026Prototype build sha256 b4780480… (24 Sep 13:39)Production fetched 13:44 UTC via Cloudflare APOFull memo: docs/nav-audit-2026-09-22/seo-crawlability-parity.md
Stage 1 · prototype dropped into the Dante child theme
PASS WITH FIXES

Every menu link is a real <a href> in the server HTML on both surfaces; nothing is injected on open; panels hide with CSS display:none or the hidden attribute and stay in the DOM; no nofollow; JS only adds aria-*. The mobile sheet carries the same 36 hrefs as desktop, so mobile-first indexing sees the whole menu.

Three hard stops before it ships: the preview noindex,nofollow meta, the prototype's click interception that kills every link, and the hidden "Full design" markup carrying ten links to URLs that 404 today. Five further required fixes below.

Stage 2 · WordPress walker rendering the same design
PASS WITH FIXES

Server-rendered, page-independent fragments cached in a transient, two passes for desktop and mobile: crawl-neutral by construction. Four plan points would break parity with stage 1 and need amending: <button> tabs (§5.7), the per-item hide flags, rendering "proposed" items whose pages do not exist, and group titles as heading tags.

With the contract below adopted by both stages, nav-parity-check.py proves neutrality mechanically: run it stage 1 vs stage 2 on staging and require exit 0.

Required fixes before stage 1 ships

For the prototype owner (complete-menu/build.py); the theme drop-in inherits what the build emits.

  1. Do not carry the preview robots meta into the theme. HARD STOP<meta name="robots" content="noindex,nofollow"> is line 6 of the prototype head. Nothing from that head may be copied. Acceptance: curl -s URL | grep -ci noindex = 0 on /, /business-foreign-exchange and a corridor page, on staging and production.
  2. Remove the prototype chrome and the link interception. HARD STOPThe inline script's document-level click handler calls preventDefault() on every a[href] and shows a toast. Delete the dev bar, toast, hero-copy swap, context selector, measure(), placement-hash and setMode() code; ship only dropdown, keyboard, sheet and tab behaviour.
  3. Ship the Launch markup only. HARD STOPEach dropdown holds four .mm-inner variants (data-mode="all"|"live" × wide/medium) and each mobile tab two panes. The "all" variants are hidden but in the DOM: 12 extra hrefs, 10 of them 404 today. Emit only live, drop data-mode, replace body:not(.live-only) … [data-mode=all], body.live-only … [data-mode=live]{display:flex} with .mm-inner.mm-lg{display:flex} (same for mm-md). The Proposed pill, its title, data-nopage and Trade Options leave with them. Acceptance: script reports desktop 34 distinct hrefs, mobile 36, all 200.
  4. Drop the trailing slash on the six currency-pair hrefs./currencies/transfer-aed-to-gbp/ 301s to the slash-less URL on production (both UAs); the page canonical and all 812 sitemap entries are slash-less. Six sitewide links through a redirect on every page. Note: the project CLAUDE.md table shows the slashed form; production disagrees and wins.
  5. Make /business-foreign-exchange#industries resolve.No element with id="industries" exists on the hub page. Add the id to the sector-grid row in the same release, or link to the hub without the fragment.
  6. Give the mobile sheet a <nav> landmark and stop reusing Dante's class names.The sheet is a role="dialog" with tablist and tabpanels and zero <nav> inside. Wrap the tab strip and panes in <nav id="mobile-menu" aria-label="Mobile"> (the theme's sf_mobile_menu() already emits that id). Desktop: rename nav.mega-menu → nav#main-navigation.ct-nav, ul.menu → ul.ct-nav__list, li.menu-item → li.ct-nav__item; Dante's functions.js binds dcMegaMenu to #main-navigation .menu and click handlers to .menu-item a, and the homepage critical CSS has 88 #main-navigation rules. Drop the empty nav.search-nav the parent emits.
  7. Group titles must not be heading tags.Eight <h3 class="b-h5"> in the sheet; header.php echoes the mobile menu (line 265) before the header (line 284), so they would precede every page's <h1>. Use <div class="b-h5" role="heading" aria-level="2"> and give desktop .mm-hdr the same role, so both landmarks use one mechanism and SEO tools counting h1–h6 see nothing.
  8. Hide decorative glyphs from accessible names.Measured names: "AED → GBP UAE to UK", "All industries →", "All countries & currencies →", "Sign Up ↗"; the .chev and header SVGs lack aria-hidden. Add aria-hidden="true" to .arrow, .pair/.pair7, .nearr; aria-hidden="true" focusable="false" on the SVGs.

Recommended, not blocking

Conditions on the stage-2 plan

Link-equity delta: production header → Launch mode

Sitewide header links are the strongest internal-link signal the site sends, and because no static page is in any sitemap (the misc sitemap holds only the homepage), they are also how Google discovers and re-crawls those pages. Production carries 15 distinct hrefs; Launch carries 36; Full design 49. Status is the production response on 24 Sep (Googlebot UA).

LOSS loses its sitewide link GAIN gains a sitewide link SAME linked before and after FULL ONLY Full design only, not in Launch
DeltaURLProdLaunchFullStatusNotes
LOSS/product"Solutions" tab · "Online Currency Exchange"desktop + mobile––200Loses its only sitewide link. Remaining internal links found: two homepage body cards; none in the footer, none on 20 sampled template pages, not in any sitemap. Decide: 301 to the Business hub, give it a slot, or a footer link.
LOSShttps://www.payplexo.com"PayPlexo"desktop + mobile––200Sister brand; the outbound link disappears sitewide. Owner decision.
GAIN/personal-transfers/overseas-property–yesyes200Personal › Why you're transferring
GAIN/personal-transfers/expat-money-transfers–yesyes200Personal › Why you're transferring
GAIN/personal-transfers/large-money-transfers–yesyes200Personal › Why you're transferring
GAIN/saudi-expat-guide–yesyes200Personal › Why you're transferring (Launch placement)
GAIN/currencies/transfer-aed-to-gbpalso aed-to-usd, sar-to-gbp, sar-to-eur, gbp-to-eur, gbp-to-aud–yes ×6yes ×6301 → slash-lessPersonal › Send money from. Prototype links the slashed form; fix 4 removes the redirect.
GAIN/countries–yesyes200"All countries & currencies" (takes the Supported Currencies role)
GAIN/faq–yesyes200Personal › Tools & help; already in the footer
GAIN/global-mobility-solutions–yesyes200Business › For employers
GAIN/business-foreign-exchange/import-and-exportalso /technology, /marketplaces–yes ×3yes ×3200Business › Industries. The other 8 sector pages keep their links from the hub body (11 sector links there).
GAIN/business-foreign-exchange#industries–yesyes200 (no anchor)"All industries". Fragment target missing on the hub (fix 5).
GAIN/partnerships–yesyes200Business › Work with us
GAIN/team · /reviews · /press–yes ×3yes ×3200Company › Who we are; team and reviews already in the footer
GAIN/partners · /security-regulation–yes ×2yes ×2200Company › Trust & contact; both already in the footer
SAME/business-foreign-exchange · /personal-transfers · /aboutyesyesyes200Tabs and overview rows. Anchor text changes: "About" → "Company" + "About CurrencyTransfer …"
SAME/spot-transfers · /forward-contracts · /rate-alerts · /market-orders · /mass-payments · /currency-options · /api-solutionsyes (Solutions)yesyes200Redistributed across Personal › Tools & help and Business › Manage risk / Pay at scale; descriptions join the anchor text. Rate Alerts stays.
SAME/contact-usmobile onlyyesyes200Now on desktop too ("Book a call", "Contact us")
SAMEhttps://app.currencytransfer.com · /sign-upyesyesyes301 → /sign-in · 200Utility links; move from inside the desktop nav to the header cells
FULL ONLY/personal-transfers/salary-and-bonus-repatriation · /personal-transfers/pension-and-inheritance-transfers · /buying-property-in-spain · /buying-property-in-israel · /employee-programme · /affiliate-networks · /agencies · /partnerships/agencies · /multi-currency-accounts · /trade-options––yes404Present in the prototype DOM as hidden "all" variants (fix 3). Trade Options is medium layout only.
FULL ONLY/lp/revenue-protection-links · /lp/international-workforce––yes200Live landing pages, deliberately excluded from Launch
FULL ONLY/refer-and-earn––desktop only200Plaque; not in the sheet, so Full design is not at mobile parity for it. Footer link exists.

Nothing loses its only internal link: /product keeps two homepage body links. It does lose its only sitewide one, and it is not in a sitemap, so its crawl frequency and internal PageRank drop sharply. Sign this off knowingly or give it a slot.

Evidence in brief

CheckProduction todayPrototype, Launch mode
Raw HTMLByte-identical for Chrome, Googlebot and Googlebot-smartphone UAs. Desktop nav#main-navigation 14 links; nav#mobile-menu 16 links (+ close link); a third, empty nav.search-nav. No aria-labels, no headings in nav, no nofollow. Three href="#" controls.Live URL byte-identical to source. Header 3 links; nav[aria-label=Main] 77 anchors / 34 distinct hrefs (wide + medium variants); sheet 39 anchors / 36 distinct. All in server HTML.
After JSdcMegaMenu adds classes, one icon span, wraps the sub-menu; adds/removes no links. Sub-menu display:none → block on hover-intent. Homepage: chain held until the hero paints; dropdown inert for ~2 s then works.Only aria-haspopup/expanded/controls added to the 3 tabs. Hover/Enter opens panels: 16 / 14 / 7 links visible, all pre-existing. Sheet: 16 / 14 / 7 (+2 utility) per tab.
No JSAll links present; dropdown never opens.All links present; tabs follow their hub href (verified); panels never open; burger inert.
Mobile parityMobile-only: /contact-us.desktop+header 36 = sheet 36. Full design: /refer-and-earn, /trade-options desktop only.
Canonical formSlash-less everywhere; /faq/, /about/, /countries/, /currencies/…/ all 301 to slash-less.6 currency hrefs slashed (301); #industries target missing; everything else 200.
Structured dataOrganization, WebSite + SearchAction, WebPage, BreadcrumbList. No SiteNavigationElement.None. Recommendation: do not add SiteNavigationElement (no Google use; sitelinks come from structure and links). Keep BreadcrumbList.

Lighthouse 12.8.2 · SEO / Accessibility · 24 Sep 13:49–13:50 UTC

Prototype · mobile
50 SEO · 96 A11y
Prototype · desktop
50 SEO · 96 A11y
Production / · mobile
100 SEO · 88 A11y
Production / · desktop
100 SEO · 88 A11y

Prototype failures are all preview chrome: is-crawlable (the noindex meta), meta-description, robots-txt (pages.dev serves the page for /robots.txt), and contrast on two dev-bar elements. Production's 88 comes from the comparison grid's ARIA roles and contrast in the cookie dialog and calculator, none in the header. No nav-related audit (link-name, crawlable-anchors, heading-order, aria-*, target-size) fails on any run.

Markup contract for stage 2

Both stages must satisfy every clause. The parity script checks C2, C3 (as far as static HTML shows), C5, C6, C7 and C8.

C1 · Landmarks
Exactly two menu landmarks, server-rendered on every page: <nav id="main-navigation" class="ct-nav" aria-label="Main"> and <nav id="mobile-menu" aria-label="Mobile"> inside #mobile-menu-wrap › #ct-sheet[role=dialog]. No other <nav> in the header. Log in / Sign up sit in the header outside #main-navigation and in the sheet footer outside #mobile-menu; the logo link / is in the header.
C2 · Tuple list
Per landmark, the ordered list of (href, accessible name, depth, section, group) is fixed by the Launch prototype after fixes 3–8 and must be reproduced exactly, order included. Depth: desktop tab 1, overview row 1, panel link 2; mobile overview 0, list link 1. Section = tab; group = nearest preceding group title in the same panel.
C3 · Everything in server HTML
Every anchor of every panel and pane is in the initial HTML on every URL. Script never creates, moves or removes a link; it may only toggle aria-expanded, aria-controls, aria-selected, tabindex, sheet aria-hidden, pane hidden and state classes.
C4 · Hidden state
Desktop panels display:none via CSS inlined in <head>, opened by a class on the item. Sheet visibility:hidden + aria-hidden="true" when closed; inactive panes carry hidden. Never display:none on an individual link, no lazy rendering.
C5 · Hrefs
Root-relative or absolute on www.currencytransfer.com; lower-case, no trailing slash anywhere (including /currencies/…), no query string; the only fragment is #industries while its id exists. Every target 200 at deploy. No rel="nofollow", target="_blank" or title. Items without a live target are omitted, not hidden.
C6 · Tabs and names
Desktop tabs are <a href="{hub}">{Label}</a> in the server HTML (Personal → /personal-transfers, Business → /business-foreign-exchange, Company → /about); JS may add aria-expanded/aria-controls and intercept the click. Mobile tabs are <button role="tab">; the hub link is the overview row. Accessible name = visible label + description where rendered, nothing else: badges, arrows, chevrons, pills and icons are aria-hidden. Launch mode has no pills.
C7 · Group titles
<div … role="heading" aria-level="2"> in both landmarks. No h1–h6 inside either.
C8 · Mobile parity
set(hrefs in #mobile-menu ∪ sheet footer) == set(hrefs in #main-navigation ∪ header utility). Script output must read desktop-only=['/']; mobile-only=none; stage 2 asserts it after the walker's two passes.
C9 · Classes and ids
Class names are free, except menu, menu-item, mega-menu, std-menu, sub-menu, parent never appear inside either landmark. Ids main-navigation, mobile-menu, mm-*, bp-*, bt-* stay stable across stages.
C10 · Page independence
Both fragments byte-identical on every URL: no current-menu-item, no server-side aria-current, no page-specific text.
C11 · Robots
No robots directives in the fragments or their head contribution.

Contract tuple snapshot: Launch prototype, normalised

Extracted by nav-parity-check.py --mode live from the prototype build sha256 b4780480…, then normalised for the fixes that change hrefs or names (slash-less currency URLs, glyphs and badges removed from names). Wide and medium layout variants that produce the same (href, name) are listed once; where the medium variant renders a description that the wide one does not, both names appear. Section is the tab; group is the nearest preceding group title. This is the list the stage-2 walker must reproduce in this order; the authoritative version is whatever the script extracts from the stage-1 staging render after the fixes.

Desktop landmark: nav#main-navigation 42 tuples · 34 distinct hrefs

#DepthSectionGroupHrefAccessible name
11Personal–/personal-transfersPersonal
21Personal–/personal-transfersPersonal Transfers Everything we do for private clients · minimum transfer £5,000 or equivalent
32PersonalWhy you're transferring/personal-transfers/overseas-propertyBuying property abroad
42PersonalWhy you're transferring/personal-transfers/expat-money-transfersExpat money transfers
52PersonalWhy you're transferring/personal-transfers/large-money-transfersLarge one-off transfers
62PersonalWhy you're transferring/forward-contractsFix a rate for a future payment
72PersonalWhy you're transferring/saudi-expat-guideSaudi Arabia expat guide
82PersonalSend money from/currencies/transfer-aed-to-gbpUAE to UK
92PersonalSend money from/currencies/transfer-aed-to-usdUAE to US
102PersonalSend money from/currencies/transfer-sar-to-gbpSaudi Arabia to UK
112PersonalSend money from/currencies/transfer-sar-to-eurSaudi Arabia to Europe
122PersonalSend money from/currencies/transfer-gbp-to-eurUK to Europe
132PersonalSend money from/currencies/transfer-gbp-to-audUK to Australia
142PersonalSend money from/countriesAll countries & currencies
152PersonalTools & help/spot-transfersSpot Transfers Same-day transfers at the live rate
162PersonalTools & help/rate-alertsRate Alerts Get told when your rate arrives
172PersonalTools & help/faqHelp & FAQs Answers on accounts, security and fees
181Business–/business-foreign-exchangeBusiness
191Business–/business-foreign-exchangeBusiness Foreign Exchange Dedicated specialist, hedging and payments for companies
202BusinessManage currency risk/forward-contractsForward Contracts Lock in a rate for future payments
212BusinessManage currency risk/market-ordersMarket Orders Set a target rate, we execute
222BusinessManage currency risk/currency-optionsCurrency Options Buy and sell FX without deposits
232BusinessPay at scale/spot-transfersSpot Transfers
242BusinessPay at scale/mass-paymentsMass Payments Send thousands of payments at once
252BusinessPay at scale/api-solutionsAPI Solutions Automate payments and rates by API
262BusinessFor employers/global-mobility-solutionsGlobal Mobility & Payroll
272BusinessIndustries/business-foreign-exchange/import-and-exportImport & Export
282BusinessIndustries/business-foreign-exchange/technologyTechnology & SaaS
292BusinessIndustries/business-foreign-exchange/marketplacesOnline Marketplace Sellers
302BusinessIndustries/business-foreign-exchange#industriesAll industries
312BusinessWork with us/partnershipsPartner with us
322BusinessWork with us/contact-usBook a call
332BusinessPay at scale/spot-transfersSpot Transfers Same-day international payments
342BusinessFor employers/global-mobility-solutionsGlobal Mobility & Payroll Relocation and payroll payments, automated
351Company–/aboutCompany
361Company–/aboutAbout CurrencyTransfer International payments platform, founded 2010, London
372CompanyWho we are/teamOur team
382CompanyWho we are/reviewsReviews
392CompanyWho we are/pressPress
402CompanyTrust & contact/partnersPayment partners The regulated firms that execute your transfers
412CompanyTrust & contact/security-regulationSecurity & regulation How client funds are safeguarded
422CompanyTrust & contact/contact-usContact us

Mobile landmark: nav#mobile-menu (+ sheet footer) 38 tuples · 36 distinct hrefs

#DepthSectionGroupHrefAccessible name
10Personal–/personal-transfersPersonal Transfers Dedicated concierge, £5,000 minimum transfer
21PersonalWhy you're transferring/personal-transfers/overseas-propertyBuying property abroad
31PersonalWhy you're transferring/personal-transfers/expat-money-transfersExpat money transfers
41PersonalWhy you're transferring/personal-transfers/large-money-transfersLarge one-off transfers
51PersonalWhy you're transferring/forward-contractsFix a rate for a future payment
61PersonalWhy you're transferring/saudi-expat-guideSaudi Arabia expat guide
71PersonalTools & help/spot-transfersSpot Transfers
81PersonalTools & help/rate-alertsRate Alerts
91PersonalTools & help/faqHelp & FAQs
101PersonalSend money from/currencies/transfer-aed-to-gbpUAE to UK
111PersonalSend money from/currencies/transfer-aed-to-usdUAE to US
121PersonalSend money from/currencies/transfer-sar-to-gbpSaudi Arabia to UK
131PersonalSend money from/currencies/transfer-sar-to-eurSaudi Arabia to Europe
141PersonalSend money from/currencies/transfer-gbp-to-eurUK to Europe
151PersonalSend money from/currencies/transfer-gbp-to-audUK to Australia
161PersonalSend money from/countriesAll countries & currencies
170Business–/business-foreign-exchangeBusiness Foreign Exchange Tech-powered corporate currency exchange
181BusinessManage currency risk/forward-contractsForward Contracts
191BusinessManage currency risk/market-ordersMarket Orders
201BusinessManage currency risk/currency-optionsCurrency Options
211BusinessPay at scale/mass-paymentsMass Payments
221BusinessPay at scale/api-solutionsAPI Solutions
231BusinessPay at scale/global-mobility-solutionsGlobal Mobility & Payroll
241BusinessIndustries/business-foreign-exchange/import-and-exportImport & Export
251BusinessIndustries/business-foreign-exchange/technologyTechnology & SaaS
261BusinessIndustries/business-foreign-exchange/marketplacesOnline Marketplace Sellers
271BusinessIndustries/business-foreign-exchange#industriesAll industries
281BusinessWork with us/partnershipsPartner with us
291BusinessWork with us/contact-usBook a call
301CompanyCompany information/aboutAbout us
311CompanyCompany information/teamOur team
321CompanyCompany information/reviewsReviews
331CompanyCompany information/partnersPayment partners
341CompanyCompany information/security-regulationSecurity & regulation
351CompanyCompany information/pressPress
361CompanyCompany information/contact-usContact us
370––https://app.currencytransfer.comLog in
380––https://app.currencytransfer.com/sign-upSign Up

Header, outside both landmarks: / "CurrencyTransfer" (logo), https://app.currencytransfer.com "Log in", https://app.currencytransfer.com/sign-up "Sign up".

The parity script

docs/nav-audit-2026-09-22/nav-parity-check.py, Python 3 standard library. Extracts the tuple list from a URL or file (no JS), reports the checks above, diffs two sources. Exit 0 identical, 1 differences, 2 error.

python3 nav-parity-check.py https://staging.currencytransfer.com/                       # report one source
python3 nav-parity-check.py complete-menu/index.html --mode live                          # prototype, Launch mode
python3 nav-parity-check.py STAGE1_URL STAGE2_URL --key name                              # stage 1 vs stage 2: require exit 0
python3 nav-parity-check.py A B --key href                                                # href sets + "same href, name changed"
python3 nav-parity-check.py A B --key strict                                              # adds depth, section, group

Proof runs on 24 Sep: source file vs live prototype (Launch) → 0 / 0 differences, 85 common tuples, order identical, exit 0. Production home vs production Business page → 0 / 0, 33 common, exit 0. Prototype vs production (--key href) → 9 tuples only in production (/product ×2, PayPlexo ×2, three # controls, Log in and Sign up moving from the nav to the header kind), 23 hrefs only in the prototype, 16 name changes, exit 1. Launch vs Full design → 40 tuples only in Full design (the 13 hrefs above), exit 1.

Prepared 24 September 2026 from the served production HTML, the live prototype and the theme source (read-only). Playwright on Chromium; Lighthouse 12.8.2 on Chrome for Testing 151. Unlisted page: do not index.