EventGrid
ImplementedResponsive event grid — 1/2/3/4 columns at sm/md/lg/xl, capped at 48 events per grid. Same shape across all event listing pages. Loading branch renders an accessible skeleton grid (role='list' + aria-busy); empty branch composes the EmptyState molecule instead of hand-rolled markup. Composes the EventCard organism per item (single layout, no variant switch — Wave-3 L8c audit). Locale-aware via the `locale` prop (EN default, FR opt-in); 'fr' also merges the canonical EVENTCARD_LABELS_FR bag under each card's own `labels` (card-level keys win per key).
components/organisms/eventgrid/EventGrid.tsxResponsive multi-column grid of public EventCards. Column counts are configurable per breakpoint (1–4); the loading branch renders an accessible skeleton grid (aria-busy), and the empty branch composes the EmptyState molecule. Locale-aware: `locale="fr"` localizes the grid-authored strings and merges the canonical `EVENTCARD_LABELS_FR` bag under each card.
EventGrid
Organism — Storefront family (Family 7)
Responsive card grid for public event listing pages (homepage, /events). Renders 1/2/3/4 columns at sm/md/lg/xl breakpoints. Each cell is an EventCard; the grid itself owns the list semantics (role="list" + aria-label) and the locale of its own strings.
Default — en
Français — locale="fr"
Loading — skeleton
Empty state
No events found
This component supports light and dark mode.
When to use
EventGrid is the raw responsive grid — it owns column counts and list semantics, but not the surrounding section chrome. Pair it with the sibling organisms depending on the layout you need:
- EventGrid — responsive multi-column grid of EventCards (this component). Use when you need full control over column counts per breakpoint.
- EventList — vertical 1-per-row list with more metadata per item (date, venue, city, price, CTA). Better for narrow viewports and search results.
- EventsRow — horizontal scroll-snap row of 240px cards. Use for "also happening" strips inside a page section.
- FeaturedEvents — section wrapper (heading + "View all" link + responsive padding) that takes an
<EventGrid>aschildren. Use for the homepage / events listing chrome.
Usage
import { EventGrid } from '@epasslive/design-system'
// Default (en) — 1/2/3/4 columns at sm/md/lg/xl
<EventGrid events={events} />
// Custom columns + French locale
<EventGrid
events={events}
columns={{ sm: 1, md: 2, lg: 3 }}
locale="fr"
/>
// Loading skeleton
<EventGrid events={[]} isLoading skeletonCount={8} />Props
events*EventCardPublicConfig[]Event cards to render. Rendered count is capped at 48 (`events.slice(0, 48)`); pass an empty array to render the empty state.
columns{ sm?: 1 | 2 | 3 | 4; md?: 1 | 2 | 3 | 4; lg?: 1 | 2 | 3 | 4; xl?: 1 | 2 | 3 | 4 }{ sm: 1, md: 2, lg: 3, xl: 4 }Column count per breakpoint. Merged over the default — set only the breakpoints you want to override.
emptyStateEmptyStateConfig (deprecated)Custom empty-state copy `{ title, message?, action? }`, rendered via the EmptyState molecule (`title→title`, `message→description`, `action→action`). Deprecated — compose the EmptyState molecule directly where you control the render.
isLoadingbooleanfalseWhen true, renders an accessible skeleton grid (`role="list"` + `aria-busy="true"`) instead of events.
skeletonCountnumber6Number of skeleton placeholders while loading; clamped to 1–12.
locale"en" | "fr"enLocale for the strings EventGrid itself authors: list `aria-label` ("Events" / « Événements ») and the default empty title ("No events found" / « Aucun événement trouvé »). `"fr"` also merges the canonical `EVENTCARD_LABELS_FR` bag under each card's own `labels` (card-level keys win per key). Event data (`title`/`venue`/`priceLabel`) stays call-site-owned. Defaults to `en` — unlike EventList/EventsRow, which default to `fr`.
Props
EventGrid
eventsEventCardPublicConfig[]—Event cards to render. Rendered count is capped at 48 (`events.slice(0, 48)`); pass an empty array to render the empty state.
columns{ sm?: 1 | 2 | 3 | 4; md?: 1 | 2 | 3 | 4; lg?: 1 | 2 | 3 | 4; xl?: 1 | 2 | 3 | 4 }{ sm: 1, md: 2, lg: 3, xl: 4 }Column count per breakpoint. Merged over the default — set only the breakpoints you want to override.
emptyStateEmptyStateConfig—Custom empty-state copy `{ title, message?, action? }`, rendered via the EmptyState molecule (`title→title`, `message→description`, `action→action`). The `EmptyStateConfig` interface is `@deprecated` — compose the EmptyState molecule directly instead; the prop is retained for backwards compatibility with EventGrid.
isLoadingbooleanfalseWhen true, renders an accessible skeleton grid (`role='list'` + `aria-busy='true'`) instead of events.
skeletonCountnumber6Number of skeleton placeholders while loading; clamped to 1–12.
locale"en" | "fr""en"Locale for the strings EventGrid itself authors — list `aria-label` ('Events' / « Événements ») and the default empty title ('No events found' / « Aucun événement trouvé »). 'fr' also merges the canonical `EVENTCARD_LABELS_FR` bag under each card's own `labels` (card-level keys win per key). Event data (`title`/`venue`/`priceLabel`) stays call-site-owned. Defaults to 'en' — unlike EventList/EventsRow which default to 'fr'.
<EventGrid />Accessibility
Type
1 ruleARIA
1 ruleUsage
Import
import { EventGrid } from "@epasslive/design-system"Example 1
<EventGrid events={cards} columns={{ sm: 1, md: 2, lg: 3 }} locale="fr" />Example 2
<EventGrid events={cards} emptyState={{ title: "Aucun événement", message: "Revenez bientôt.", action: <Button>Voir tous les événements</Button> }} locale="fr" />Example 3
<EventGrid events={[]} isLoading skeletonCount={8} />Anti-patterns
— Do not fetch data directly in organisms — adapters own that (EventGridAdapter precedent).
— Do not pass locale expecting event data translation — locale only switches grid-authored strings and merges the FR card-label bag; format/translate event titles, venues and price labels at the call site.
— Do not reach for a `variant` prop — EventGrid ships one layout (Wave-3 L8c audit). Use EventList (vertical) or EventsRow (horizontal rail) for the other layouts.