Raxx · internal docs

internal · gated

getraxx.com — Unified Navigation Design

Date: 2026-05-27 Status: TARGET — design only, implementation pending operator approval Author: ux-designer Counterpart audit: docs/qa/getraxx-navigation-audit-2026-05-27.md (qa-agent, in flight)


The Problem

LandingNav.jsx implements two different behaviors for the same label depending on which page the user is on:

This means the same nav item has three possible behaviors: anchor scroll, cross-page anchor, and route navigation. A user who learns "Pricing brings me to the pricing section" is wrong half the time.


All primary nav links are React Router routes. Always. Full stop.

Anchor scrolling is removed from the nav entirely. It becomes an in-page UX concern only (hero CTAs, section-internal links) rather than a nav concern.

Reasoning:

  1. Consistency. A nav link does one thing regardless of where you are. "Pricing" goes to /pricing. Always.
  2. The anchor-scroll-from-nav pattern was a workaround for when /pricing didn't exist as a route. It now exists.
  3. Shareable URLs. /#pricing is not a stable URL. /pricing is.
  4. The "in-page scroll" use case is served by hero CTAs (href="#pricing", href="#waitlist"), which remain meaningful only when the user is already on /. These stay.
  5. Mobile menus with cross-page anchors are fragile — the page loads at top, then scrolls, causing visual jank.

The one exception to preserve: The hero CTAs on / (href="#waitlist", href="#pricing") continue to anchor-scroll within the landing page. These are in-page CTAs, not nav items. They are not part of LandingNav.


Sitemap

getraxx.com
├── Marketing
│   ├── /                    Landing (hero + pillars + pricing teaser + waitlist)
│   ├── /pricing             Full pricing page (tiers + comparison table + FAQ)
│   └── /about               About page
├── Conversion
│   ├── /waitlist            Standalone waitlist signup
│   └── /contact             Contact page (mailto: support@raxx.app)
└── Legal
    ├── /privacy             Privacy Policy (BLR content pending #586)
    └── /terms               Terms of Service (BLR content pending #588)

Orphans / pre-launch hidden: None currently. All routes are reachable. Not in nav (footer-only): /privacy, /terms, /contact


Primary Nav Spec — LandingNav

Desktop (>= 640px)

[ RAXX• ]    [ Product ]  [ Pricing ]  [ About ]    [ Join the waitlist → ]
  home link     /about*      /pricing    /about        /waitlist (CTA button)

Wait — "Product" is currently a scroll target, not a route. See "Product" section below.

Label Destination Element type Notes
RAXX wordmark / <Link to> Always routes home
Product /#pillars <a href> See "Product" decision below
Pricing /pricing <Link to> Route always — no anchor variant
About /about <Link to> Already a route in current code
Join the waitlist /waitlist <Link to> CTA button, always a route

"Product" — the only deliberate exception

/about exists. /pricing exists. /waitlist exists. /product does not exist.

The "Product" link currently points to /#pillars (the PillarsSection). Two options:

Option A — Keep Product as cross-page anchor (recommended for v1): - Product → always /#pillars, implemented as <a href="/#pillars"> - On / this causes a scroll. On sub-pages this causes a route-then-scroll. - This is acceptable because it's a single consistent href — no conditional logic. - Label clarifies intent: it's a product overview, not a product page. - When react-router processes /#pillars from a sub-page, the browser navigates to / and the fragment fires scroll. Works correctly in all modern browsers with BrowserRouter.

Option B — Create /product route (deferred, out of scope for this design): - File a product-manager card to build a dedicated /product page. - Until it exists, use Option A. - Mark the nav item with data-brand-placeholder="product-page-link" so it's findable when the page ships.

This design specifies Option A for v1. No conditional logic. Product is always /#pillars.

What changes from current code

Current behavior Target behavior
isRoot check: if on /, pricingHref = '#pricing'; else /pricing Always /pricing — no conditional
isRoot check: if on /, productHref = '#pillars'; else /#pillars Always /#pillars — no conditional
<a href={pricingHref}> (plain anchor, no router) <Link to="/pricing"> (React Router)
<a href={productHref}> (plain anchor) <a href="/#pillars"> (stays plain anchor, cross-page)

The isRoot / useLocation logic is removed entirely from LandingNav. The component becomes stateless with respect to routing.


About | Privacy | Terms | Documentation | Contact | [Cookie preferences]
Primary nav repeat:
  Product  |  Pricing  |  About  |  Join the waitlist

Legal:
  Privacy  |  Terms

Support:
  Contact  |  Documentation

[Cookie preferences]  (conditional on FLAG_GETRAXX_COOKIE_BANNER)
┌─────────────────────────────────────────────────────────────────────┐
│  RAXX™                                                               │
│  © 2026 Raxx. All rights reserved.                                   │
│  A product of MooseQuest LLC, operating as Raxx.                     │
│                                                                       │
│  Product  Pricing  About  Waitlist  |  Privacy  Terms  |  Contact    │
│  Documentation  [Cookie preferences]                                  │
└─────────────────────────────────────────────────────────────────────┘

On mobile the link row wraps naturally. No structural change needed.

Rationale for repeating primary nav in footer: Standard accessibility practice. Users who scroll to the bottom and want to navigate should not have to scroll back up. The footer nav is a convenience duplicate, not a separate information architecture.

Label Destination Element type Group
Product /#pillars <a href> Primary
Pricing /pricing <Link to> Primary
About /about <Link to> Primary
Waitlist /waitlist <Link to> Primary
Privacy /privacy <Link to> Legal
Terms /terms <Link to> Legal
Contact mailto:support@raxx.app <a href> Support
Documentation https://docs.raxx.app <a href> Support

Homepage Anchor Sections — Section ID Map

The landing page (/) uses hash anchors for in-page scroll CTAs. These are distinct from the nav. Feature-developer must preserve these IDs exactly.

Section Component id value Target of
Pillars section PillarsSection pillars /#pillars from nav + footer "Product" link
Pricing teaser PricingTeaser pricing Hero CTA "See pricing" (href="#pricing" on /)
Waitlist form WaitlistSection waitlist Hero CTA "Join the waitlist" (href="#waitlist" on /) + PricingTeaser tier CTAs

Verification note for qa-agent: Confirm PillarsSection.jsx has id="pillars" on its root <section>. The source I reviewed does not include PillarsSection — this is an assumption.


Per-Page User Journey Flows

/ — Landing

Entry: Direct, organic search, referral link

Primary journey:
  Hero → [Join the waitlist] → /waitlist
  Hero → [See pricing] → scroll to #pricing on this page → tier CTA → /waitlist

Secondary journeys:
  Nav [Pricing] → /pricing
  Nav [About]   → /about
  Footer [Privacy] / [Terms] → /privacy, /terms

Exit / conversion:
  All paths funnel to /waitlist

/pricing

Entry: Nav "Pricing" from any page, direct URL, organic

Primary journey:
  Tier CTA (any tier) → /waitlist
  Hero "Join the waitlist" → /waitlist

Secondary journeys:
  Nav [About] → /about
  Nav [Product] → /#pillars (back to homepage, scroll to pillars)
  FAQ "Can I cancel?" → no link needed; informational
  Footer [Privacy] / [Terms] → legal pages

Missing journey (gap to address):
  From /pricing, user wants to understand the product before committing →
  "Learn how it works" link pointing to /#pillars (or future /product page).
  RECOMMENDATION: Add a secondary text link below the tier grid:
    "How does the structure work? → See it in action"  →  /#pillars

/waitlist

Entry: Any CTA across the site, direct URL

Primary journey:
  [default] Email input → submit → success state ("You're on the list.")
  [flag OFF] Static "Waitlist signups are opening soon." message

Error paths (per feedback_signup_never_blocks):
  Network error → error message shown, form stays editable, retry available
  Already subscribed → should surface as informational, not blocking (verify backend 4xx copy)

Post-conversion:
  No next step currently designed. RECOMMENDATION:
    After success state, add:
    "While you wait — see how Raxx works" → /#pillars
    "See full pricing details" → /pricing

Secondary journeys:
  Nav [Pricing] → /pricing
  Nav [About]   → /about

/about

Entry: Nav "About", footer link

Primary journey:
  Read about the product → [Join the waitlist] CTA → /waitlist

Missing journey (gap to address):
  About page has no primary CTA visible in the source code (AboutPage.jsx
  content not audited here — assumed based on [issue #125](https://github.com/raxx-app/TradeMasterAPI/issues/125) stub language).
  RECOMMENDATION: Ensure /about has a bottom CTA section:
    "Ready to test your structure?" → /waitlist (primary)
    "See pricing" → /pricing (secondary)

/contact

Entry: Footer "Contact" link (currently mailto:)

Current state:
  ContactPage.jsx exists as a route but "Contact" in footer is a mailto: link,
  not a route link. The route /contact may be unreachable via nav/footer.

RECOMMENDATION: Footer "Contact" link → /contact (route), not mailto:.
  ContactPage.jsx should contain the mailto: link + any support context.
  This preserves the email flow while making /contact a routable, shareable page.

/privacy and /terms

Entry: Footer only

Primary journey:
  Read policy → back (browser) or footer nav to other pages

No CTA required on legal pages. Footer nav provides onward navigation.

Mobile-Specific Notes

Hamburger menu — target item set

Same as desktop. No progressive disclosure. Rationale: 4 items + 1 CTA is not enough to warrant collapsing further. A mobile user clicking "Pricing" expects to go to /pricing, not to a sub-menu.

Mobile hamburger open state:
  [ Product       ]   → /#pillars
  [ Pricing       ]   → /pricing
  [ About         ]   → /about
  [ Join the waitlist ]   → /waitlist  (styled as CTA, moss border)

Hamburger close behavior

Current: onClick={() => setMenuOpen(false)} on each menu item. Correct. Keep.

display: none !important toggle via scoped <style>

Current approach (.nav-hamburger { display: none } overridden at max-width: 639px) is functional. No change required. A cleaner approach would be a CSS module or Tailwind class, but that's a refactor concern for ux-polisher, not this design.

Focus trap on mobile menu

The current mobile menu does not trap focus — pressing Tab from the last item exits the menu without closing it. Recommendation: add a focus trap (Radix FocusTrap or a lightweight hand-rolled version) or at minimum return focus to the hamburger button on close.


Accessibility Notes

Focus order — desktop

Tab order on desktop nav: 1. Wordmark (home link) 2. Product (anchor) 3. Pricing (Link) 4. About (Link) 5. Join the waitlist (Link/button)

This is left-to-right DOM order. No skip link exists. RECOMMENDATION: Add a skip-to-content link as the first focusable element:

<a href="#main-content" class="sr-only focus:not-sr-only">Skip to content</a>

The <main> on each page should have id="main-content".

role="list" on desktop nav div

Current code places role="list" on the desktop nav container div and role="listitem" on each link. This is technically correct for announcing as a list to screen readers, but is unusual. The more idiomatic pattern is <ul> / <li>. Functional either way — flag for ux-polisher.

role="menu" on mobile nav

The mobile menu uses role="menu" with role="menuitem" children. This is semantically incorrect — a navigation menu is not an ARIA menu widget. ARIA menu implies keyboard navigation (arrow keys, Escape closes). The correct role for a navigation flyout is role="navigation" (or no role, since <nav> provides that). RECOMMENDATION: remove role="menu" / role="menuitem" from the mobile dropdown and use the <nav> / <ul> / <li> pattern. aria-expanded on the hamburger button stays.

aria-label on nav

<nav aria-label="Main navigation"> — correct, unchanged.

No active/current page indicator exists in the current nav. For screen readers, the current page link should have aria-current="page". RECOMMENDATION: use useLocation() (already imported) to apply aria-current="page" to the active link. Visual treatment: a subtle underline or color shift on the active item (not bold — too heavy at 14px).


Implications for Feature-Developer

Changes required in LandingNav.jsx

  1. Remove isRoot variable and both productHref/pricingHref conditionals.
  2. Replace <a href={pricingHref}>Pricing</a> with <Link to="/pricing">Pricing</Link>.
  3. Change <a href={productHref}>Product</a> to <a href="/#pillars">Product</a> (static href, no conditional).
  4. Apply same changes to mobile menu section (duplicate of desktop links).
  5. Add aria-current="page" logic using useLocation().
  6. Optional (accessibility): swap role="menu" / role="menuitem" in mobile dropdown to navigation pattern.

Changes required in LandingFooter.jsx

  1. Add primary nav links (Product, Pricing, About, Waitlist) to the footer link set.
  2. Group links: Primary nav | Legal | Support.
  3. Change footer "Contact" from <a href="mailto:..."> to <Link to="/contact">.
  4. Verify ContactPage.jsx contains the mailto: link and appropriate context copy.

No changes required in these components

PricingTeaser naming note

PricingTeaser becomes semantically accurate only when accessed from / (it is a teaser for the full /pricing page). When linked to from the nav, users go directly to /pricing. The teaser remains useful as a homepage section summary. No rename required, but the component's purpose is "landing page pricing overview" not "global pricing component."

Post-conversion flow gaps (design TBD, not blocking this PR)

These are non-blocking for the nav unification. File as follow-on cards.


Open Questions

None blocking. All design decisions above are complete with stated reasoning.

One item for operator awareness (not blocking feature-developer):

Should /contact be a form-based contact page (name + message + submit → FreeScout ticket) or a simple page with the email address? The current ContactPage.jsx exists as a route but its content was not audited for this design. If it's a full form, the footer "Contact" → /contact routing is straightforward. If it's an empty stub, the mailto: footer link is the right interim choice until the page has content.


Mermaid Diagrams

Sitemap

graph TD
    root["getraxx.com"]
    root --> land["/  — Landing"]
    root --> pricing["/pricing  — Full pricing"]
    root --> about["/about  — About"]
    root --> waitlist["/waitlist  — Join waitlist"]
    root --> contact["/contact  — Contact"]
    root --> privacy["/privacy  — Privacy Policy"]
    root --> terms["/terms  — Terms of Service"]

    land --> pillars["#pillars  — Pillars section (scroll)"]
    land --> pricingT["#pricing  — Pricing teaser (scroll)"]
    land --> waitlistS["#waitlist  — Waitlist form (scroll)"]

    pricing --> waitlist
    about --> waitlist
    waitlist -.->|success| waitlist
flowchart LR
    subgraph NAV["LandingNav (all pages)"]
        wm["RAXX wordmark"]
        prod["Product"]
        pric["Pricing"]
        ab["About"]
        cta["Join the waitlist"]
    end

    wm -->|Link to| home["/"]
    prod -->|a href| pillars["/#pillars"]
    pric -->|Link to| pricing["/pricing"]
    ab -->|Link to| about["/about"]
    cta -->|Link to| wl["/waitlist"]

Per-page primary journeys

flowchart TD
    home["/"]
    pricing["/pricing"]
    about["/about"]
    waitlist["/waitlist"]
    contact["/contact"]
    privacy["/privacy"]
    terms["/terms"]

    home -->|Hero: Join the waitlist| waitlist
    home -->|Hero: See pricing scroll #pricing| home
    home -->|Nav: Pricing| pricing
    home -->|Nav: About| about

    pricing -->|Tier CTA: Join the waitlist| waitlist
    pricing -->|Nav: About| about

    about -->|CTA: Join the waitlist| waitlist
    about -->|Nav: Pricing| pricing

    waitlist -->|Success: while you wait| home
    waitlist -->|Nav: Pricing| pricing

    contact -.->|mailto: support@raxx.app| external["email client"]
    privacy -.->|footer nav| home
    terms -.->|footer nav| home

Design doc complete. Feature-developer can implement the LandingNav + LandingFooter changes without further design clarification. qa-agent audit should be cross-referenced on delivery.