TopNav
ImplementedPublic site top navigation — sticky 64px, Logo + nav links + search + cart + user. Three variants (solid / transparent / solidOnScroll): solidOnScroll is a static translucent backdrop-blur look; TopNav attaches no scroll listener. Mobile: nav links hidden below md — pair with MobileNav for the mobile menu.
components/organisms/TopNav/TopNav.tsxSticky public-site top navigation (64px): logo, desktop link row, search trigger, cart link with badge count, and an optional signed-in user chip.
TopNav
Organism — Navigation family (Family 5)
Sticky site header for public storefront pages. Three background variants (solid / transparent / solidOnScroll — solidOnScroll is a static translucent backdrop-blur look; there is no built-in scroll listener, consumers swap variants on their own scroll state). Below the md breakpoint the nav links are hidden (hidden md:block) — pair with MobileNav for the mobile menu.
This component supports light and dark mode.
Usage
import { TopNav } from '@epasslive/design-system'
<TopNav
logo={<strong>ePassLive</strong>}
links={[
{ label: 'Events', href: '/events', active: true },
{ label: 'Venues', href: '/venues' },
]}
onSearchOpen={() => openSearch()}
cartCount={3}
user={{ name: 'Alex Tremblay', initials: 'AT', href: '/account' }}
/>
// FR consumers: pass the locale-aware label props verbatim
<TopNav ... cartAriaLabel="Panier, 3 billets" accountLabel="Compte : Alex" />Responsive behavior
Below the md breakpoint the nav links are hidden (hidden md:block) — there is no built-in hamburger button. Pair TopNav with MobileNav for the mobile menu (storefront Header composes exactly this pair). The search, cart, and user-chip actions remain visible at every width.
Composition patterns
- AccountShell is the only DS composer — it renders TopNav as the account-page header band and forwards the four i18n label props (
cartAriaLabel,accountLabel,siteHeaderLabel,homeLabel). Don't hand-roll a second top nav inside its children. - The storefront site header intentionally diverges —
SiteHeaderis built on the DSHeaderorganism (DS-CORR-09-13), not on TopNav. The two coexist by design. - Reference consumer: the storefront
/docsheader (packages/storefront/src/components/docs/TopNav.tsx) wraps TopNav with locale-aware labels from thetopnav.*message namespace — copy it when embedding TopNav in a localized app. - FR/EN labels: the English defaults are baked in; pass the label props above to get translated aria strings (
cartAriaLabel/accountLabelare used verbatim).
Accessibility
The root is a <header role="banner"> with an accessible name (siteHeaderLabel, default "Site header"). The primary nav landmark is named via ariaLabel (default "Primary"), the active link carries aria-current="page", and every icon-only control (search, cart, user chip, logo) exposes an accessible name through aria-label per WCAG 4.1.2. The cart, account, header, and home labels can each be overridden verbatim for locale-aware consumers. Interactive icons are decorative (aria-hidden) and focus rings / the active-link underline use the ep-text-teal token, which meets WCAG AA non-text contrast (5.45:1) in the light theme.
Props
logo*React.ReactNodeBrand node rendered inside the home link.
links*TopNavLink[]Primary nav links; the entry with active: true gets aria-current="page".
onSearchOpen*() => voidFires when the search button is clicked — open your SearchOverlay here.
cartCount*numberBadge count on the cart link; 0 hides the badge.
userTopNavUser | nullSigned-in account chip; null/undefined renders the signed-out state.
searchLabelstring'Open search'Accessible name for the search button.
cartLabelstring'Cart'Accessible name prefix for the cart link.
cartAriaLabelstringFull accessible name for the cart link, used verbatim — overrides the default "{cartLabel}, {N} items" composition (locale-aware labels).
accountLabelstringFull accessible name for the user chip, used verbatim (default: "Account: {user.name}").
siteHeaderLabelstring'Site header'Accessible name for the header landmark.
homeLabelstring'Home'Accessible name for the logo home link.
ariaLabelstring'Primary'Accessible name for the primary nav landmark.
variant'solid' | 'transparent' | 'solidOnScroll''solid'Header background style; solidOnScroll is a static translucent backdrop-blur look.
classNamestringAdditional classes merged onto the header element.
idstringauto tn-<useId>Stable id for the header element.
Props
logoReact.ReactNode—Brand node rendered inside the home link.
linksTopNavLink[]—Primary nav links; the entry with active: true gets aria-current='page'.
onSearchOpen() => void—Fires when the search button is clicked — open your SearchOverlay here.
cartCountnumber—Badge count on the cart link; 0 hides the badge.
userTopNavUser | null—Signed-in account chip; null/undefined renders the signed-out state.
searchLabelstring'Open search'Accessible name for the search button.
cartLabelstring'Cart'Accessible name prefix for the cart link.
cartAriaLabelstring—Full accessible name for the cart link, used verbatim — overrides the default '{cartLabel}, {N} items' composition (locale-aware labels).
accountLabelstring—Full accessible name for the user chip, used verbatim (default: 'Account: {user.name}').
siteHeaderLabelstring'Site header'Accessible name for the header landmark.
homeLabelstring'Home'Accessible name for the logo home link.
homeHrefstring'/'Href for the logo home link. Pass a locale-prefixed href (e.g. '/fr') in consumers under a /[locale] segment so the logo click does not drop the locale. Parity with MobileNav.homeHref.
cartHrefstring'/cart'Href for the cart link. Pass a locale-prefixed href (e.g. '/fr/cart') when the cart route is localized.
ariaLabelstring'Primary'Accessible name for the primary nav landmark.
variant"solid" | "transparent" | "solidOnScroll"'solid'Header background style; solidOnScroll is a static translucent backdrop-blur look.
classNamestring—Additional classes merged onto the header element.
idstringauto tn-<useId>Stable id for the header element.
<TopNav />Accessibility
Type
1 ruleARIA
1 ruleUsage
Import
Example 1
Example 2
Anti-patterns
— Do not fetch data directly in organisms
— Avoid tight coupling to specific data sources