Passer au contenu principal

StorefrontShell

Implemented

Public storefront page shell — header, footer, skip-link, main landmark. Real organism; the templates stories twin was removed per ADR-035.

Categoryorganisms
Sourcecomponents/organisms/StorefrontShell/StorefrontShell.tsx
AI HintUse for atomic UI elements. Compose into molecules for complex interactions.

Public storefront page shell — an optional announcement bar, a top-nav slot, a focusable main landmark, and a footer slot, with a localized skip link and a theme-aware background.

StorefrontShell

Organism — Storefront family

The outer page-level shell for every public-facing route on the ePassLive storefront (event listings, event detail, marketing pages). Three ReactNode slots (topNav, main, footer) plus an optional announcementBar above the nav, in a single vertical flex layout that pins the footer to the bottom of the viewport on short pages. Renders a localized skip link ("Skip to main content" / "Aller au contenu principal") and a focusable <main id={skipTargetId}> landmark for screen-reader handoff. The background inherits the active surface token (bg-ep-bg-primary) so it adapts to both light and dark themes.

locale: en — the skip link reads “Skip to main content”. Tab into the frame to surface it.

Skip to main content
Free shipping on tickets over $50 — limited time

ePassLive

SiteHeader slot — Events · Venues · Tickets

Storefront route content

Pages inside the (public) group mount here — the main slot renders inside a focusable <main role="main"> landmark.

SiteFooter slot — © 2026 ePassLive

This component supports light and dark mode.

Usage

StorefrontShell is the frame, not the page: fill the topNav and footer slots with the SiteHeader / SiteFooter organisms and render the route inside main. Always pass locale matching the active app language, and skipTargetId="storefront-main" when mounting inside the root layout (see the two-skip-links trap in the docs).

import { StorefrontShell } from '@epasslive/design-system'
import { SiteHeader } from '@/components/layout/SiteHeader'
import { SiteFooter } from '@/components/layout/SiteFooter'

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ locale: string }>
}) {
  const { locale } = await params
  const shellLocale: 'en' | 'fr' = locale as 'en' | 'fr'

  return (
    <StorefrontShell
      locale={shellLocale}
      skipTargetId="storefront-main"
      topNav={<SiteHeader />}
      main={children}
      footer={<SiteFooter />}
    />
  )
}

Composition notes

The slots are plain ReactNode — the shell imposes no markup on them, so headers and footers stay independently swappable. The footer pins to the bottom on short pages (flex-1 on the main region), announcementBar renders above the nav when present, and locale drives only the skip-link label. Don't use this shell for account or admin surfaces — AccountShell and AdminLayout are their respective frames.

Accessibility

Skip-to-content link (WCAG 2.4.1 Bypass Blocks): visually hidden until focused (sr-only focus:not-sr-only), fixed when visible, labelled in the active locale, and targeting the main region's id — the <main role="main"> landmark is focusable via tabIndex={-1} so the focus handoff lands without a visible ring trap (WCAG 2.4.3). The skip link surfaces in the interactive token colors (bg-ep-btn-primary + text-ep-text-inverse — 5.70:1 in both themes). Colors come from tokens, which meet AA contrast in both themes.

Props

Prop
Type
Default
Description
topNav*
React.ReactNode
—

Header component — compose <SiteHeader> (or <MobileNav> at narrow viewports) here.

main*
React.ReactNode
—

Route content rendered inside the <main> landmark.

footer*
React.ReactNode
—

Footer component — compose <SiteFooter> here; pinned to the bottom on short pages.

announcementBar
React.ReactNode
—

Optional banner rendered above the top nav (free-shipping, sale, system alerts).

skipTargetId
string
"main"

Id of the <main> element the skip link targets — namespace it ("storefront-main") when the root layout already owns <main id="main-content">.

locale
"en" | "fr"
"en"

Localizes the skip-link text: "Skip to main content" (en) / "Aller au contenu principal" (fr).

className
string
—

Extra classes appended to the shell container.

id
string
"storefront-shell"

Override the root container id — the <main> id is still skipTargetId.

Props

Preview

StorefrontShell

Prop
Type
Required
Default
Description
topNav
React.ReactNode
Required
—

Synced from source — no interface JSDoc yet; document on `StorefrontShell.tsx`.

main
React.ReactNode
Required
—

Synced from source — no interface JSDoc yet; document on `StorefrontShell.tsx`.

footer
React.ReactNode
Required
—

Synced from source — no interface JSDoc yet; document on `StorefrontShell.tsx`.

announcementBar
React.ReactNode
Optional
—

Synced from source — no interface JSDoc yet; document on `StorefrontShell.tsx`.

skipTargetId
string
Optional
'main'

Synced from source — no interface JSDoc yet; document on `StorefrontShell.tsx`.

skipLabel
string
Optional
'Skip to main content'

Accessible label for the skip link. English-literal default; pass the French label ("Aller au contenu principal") from the consumer's i18n layer.

skipAriaLabel
string
Optional
skipLabel

Accessible name (aria-label) for the skip link; defaults to skipLabel so a localized label also localizes the screen-reader announcement (F-3 / DSD-1). Pass an explicit value only when the visible label and the accessible name must differ (e.g. "/fr" page announcing "Aller au contenu principal").

Quick use:<StorefrontShell />

Usage

Import

tsxSnippet
import { StorefrontShell } from "@epasslive/design-system"

Example 1

tsxSnippet
<StorefrontShell />

Anti-patterns

— Do not compose atoms directly — use molecules instead

— Avoid inline styles on atoms

Design System v4.1.0 | ePassLive