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:
- On
/: "Product" →#pillars(anchor scroll), "Pricing" →#pricing(anchor scroll) - On
/pricing,/about, etc.: "Product" →/#pillars(cross-page anchor), "Pricing" →/pricing(route)
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.
Recommended Pattern: Route-Always
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:
- Consistency. A nav link does one thing regardless of where you are. "Pricing" goes to
/pricing. Always. - The anchor-scroll-from-nav pattern was a workaround for when
/pricingdidn't exist as a route. It now exists. - Shareable URLs.
/#pricingis not a stable URL./pricingis. - 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. - 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.
Nav link table
| 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.
Footer Spec — LandingFooter
Current footer links
About | Privacy | Terms | Documentation | Contact | [Cookie preferences]
Target footer link set
Primary nav repeat:
Product | Pricing | About | Join the waitlist
Legal:
Privacy | Terms
Support:
Contact | Documentation
[Cookie preferences] (conditional on FLAG_GETRAXX_COOKIE_BANNER)
Footer layout (two-row)
┌─────────────────────────────────────────────────────────────────────┐
│ 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.
Footer link table
| 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.
Active link state
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
- Remove
isRootvariable and bothproductHref/pricingHrefconditionals. - Replace
<a href={pricingHref}>Pricing</a>with<Link to="/pricing">Pricing</Link>. - Change
<a href={productHref}>Product</a>to<a href="/#pillars">Product</a>(static href, no conditional). - Apply same changes to mobile menu section (duplicate of desktop links).
- Add
aria-current="page"logic usinguseLocation(). - Optional (accessibility): swap
role="menu"/role="menuitem"in mobile dropdown to navigation pattern.
Changes required in LandingFooter.jsx
- Add primary nav links (Product, Pricing, About, Waitlist) to the footer link set.
- Group links: Primary nav | Legal | Support.
- Change footer "Contact" from
<a href="mailto:...">to<Link to="/contact">. - Verify
ContactPage.jsxcontains the mailto: link and appropriate context copy.
No changes required in these components
HeroSection.jsx— hero CTAs (#waitlist,#pricing) are in-page anchors, correct as-is.PricingTeaser.jsx— tier CTAs all point to#waitlist, correct as-is.WaitlistSection.jsx— form behavior unchanged.PricingPage.jsx— tier CTAs point to/waitlist(already a route), correct.- All page-level route files — no nav changes needed.
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)
/waitlistsuccess state: add "while you wait" links (see per-page flows above)./aboutbottom CTA: confirm or add./pricingsecondary link back to product section.
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
/contactbe a form-based contact page (name + message + submit → FreeScout ticket) or a simple page with the email address? The currentContactPage.jsxexists as a route but its content was not audited for this design. If it's a full form, the footer "Contact" →/contactrouting is straightforward. If it's an empty stub, themailto: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
Nav link routing (target state)
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.