Skip to main content

EventCard

Implemented

EPASS card organism — a square image or branded no-image placeholder (title monogram + optional localized category caption), with date and price chips, sold-out scrim, and a solid-background text block. The whole card is one link; no CTA button. PRICE CONTRACT (ADR-067 §2): `priceLabel` is used verbatim; without it, `priceFrom === 0` means unknown and is not announced as free. Category is visible only in the image-less placeholder and included in the card's accessible name.

Categoryorganisms
Sourcecomponents/organisms/EventCard/EventCard.tsx
AI HintUse for public-facing event cards (homepage, /events grid). i18n house pattern: EN defaults; spread EVENTCARD_LABELS_FR for French or pass priceLabel for a fully locale-formatted price. EventGrid composes the card and merges the FR bag automatically at locale='fr'; the storefront homepage composes EventCard directly with its own label bag. Provide an already-localized category for image-less cards; it is shown in the placeholder and announced in the accessible name.

Public-facing event card with image on top, text in a solid section below (WCAG-readable). Locale-aware price label. Branded teal-to-blue gradient placeholder for events without an image. D-HOME-P4a 2026-08-11.

EventCard

Storefront organism (Family 7 — Tier 3)

A public-facing event card for listing pages (homepage, /events). Renders an aspect-square (1:1) image (or branded gradient placeholder) with date badge and price chip overlaid, followed by a solid-bg text section with title and venue. The slot is 1:1 because the card is fed profile_image_url, authored at 400×400 (EVENT-UPLOAD-01 2026-08-14). The entire card is a single <a> with a descriptive aria-label.

This component supports light and dark mode.

Usage

import { EventCard } from '@epasslive/design-system'

// With image
<EventCard
  id="evt_1"
  title="Summer Jazz Night"
  image={{ url: "/banners/jazz.jpg", alt: "Saxophone at sunset" }}
  date={{ day: "12", month: "AUG", year: "2026", iso: "2026-08-12T00:00:00Z" }}
  venue={{ name: "Club Soda", city: "Montréal" }}
  priceFrom={25}
  currency="CAD"
  priceLabel="From $25.00"  // or "À partir de 25,00 $" for FR
  href="/events/evt_1"
/>

// Without image (branded gradient placeholder)
<EventCard
  id="evt_2"
  title="Comedy Night"
  image={{ url: "", alt: "Comedy show" }}
  date={{ day: "15", month: "AUG", year: "2026", iso: "2026-08-15T00:00:00Z" }}
  venue={{ name: "Théâtre Saint-Denis", city: "Montréal" }}
  priceFrom={0}
  currency="CAD"
  href="/events/evt_2"
/>

// Sold out
<EventCard {...props} soldOut />

// With badges
<EventCard {...props} badges={[{ label: "Hot", variant: "hot" }]} />

// French — i18n labels bag (chip "À partir de {price}", label "Billets épuisés",
// screen-reader fragments "complet" / "gratuit" / "image à venir")
import { EVENTCARD_LABELS_FR } from '@epasslive/design-system'
<EventCard {...props} labels={{ ...EVENTCARD_LABELS_FR }} />

Visual structure (D-HOME-P4a 2026-08-11)

┌─────────────────────────┐
│  [IMAGE]                 │   aspect-square (1:1), object-cover
│  [Date]    [Price]       │   badges top-left/top-right (solid bg + shadow-lg)
│  [Sold out scrim]        │   only when soldOut=true
└─────────────────────────┘
┌─────────────────────────┐
│  Title                   │   solid bg-ep-surface-card, WCAG AA guaranteed
│  Venue · City            │
└─────────────────────────┘

No-image cards: teal→blue gradient replaces [IMAGE] section.
Image slot is 1:1 (profile_image_url authored 400×400) — EVENT-UPLOAD-01 2026-08-14.

Accessibility

The entire card is a single <a> element with a descriptive aria-label:"Event title, AUG 12 2026, Venue City, from $25.00". The label adapts to state: ", sold out" or ", free" when applicable. Decorative elements (date badge, price chip, badges) have aria-hidden="true".

Text is always rendered on a solid bg-ep-surface-card background (D-HOME-P4a 2026-08-11), guaranteeing WCAG AA contrast regardless of image content. Previously, text floated on the image via an undefined ep-glass-card class, causing readability issues.

i18n

The priceLabel prop allows locale-aware price formatting. The adapter (eventGridAdapter.ts) pre-formats this:

  • EN: From $25.00 (via Intl.NumberFormat("en-CA", ...))
  • FR: À partir de 25,00 $ (via Intl.NumberFormat("fr-CA", ...))

Price-chip precedence (highest first): priceLabel → labels.fromPriceTemplate → English default "From {price}". When none is provided, the component falls back to English "From {price}". For French, spread the FR bag — labels={{ ...EVENTCARD_LABELS_FR }} — which also localizes the sold-out label (Billets épuisés) and the screen-reader fragments (complet / gratuit / image à venir).

Used by

  • EventGrid — renders a grid of EventCards
  • FeaturedEvents — wraps EventGrid for the homepage
  • Homepage (/) and Events listing (/events)

Props

Prop
Type
Default
Description
id*
string
—

Unique event identifier. Used for data-event-id and React key.

title*
string
—

Event name. Rendered as <h3> with line-clamp-2.

image*
{ url: string | null; alt: string }
—

Profile image (1:1 slot). When url is empty or null, renders a branded teal-to-blue gradient placeholder instead of an <img>.

date*
{ day: string; month: string; year: string; iso: string }
—

Date parts for the badge (top-left). day and month are displayed; iso is used in aria-label.

venue*
{ name: string; city: string }
—

Venue name and city. Rendered as "Venue · City" or just "Venue" if city is empty.

priceFrom*
number
—

Minimum ticket price. When > 0 and not sold out, shows a price chip top-right. When 0, the chip is hidden (unless priceLabel is provided) and aria-label says "free".

currency*
string
—

ISO currency code (e.g. "CAD"). Used for Intl.NumberFormat fallback when priceLabel is not provided.

priceLabel
string
—

Pre-formatted, locale-aware price label (e.g. "À partir de 25,00 $"). Highest precedence — overrides labels.fromPriceTemplate and the English "From {price}" default. D-HOME-P4c 2026-08-11.

labels
EventCardLabels
—

i18n labels bag — EN defaults when omitted. Spread the FR bundle: labels={{ ...EVENTCARD_LABELS_FR }}. Keys: fromPriceTemplate ("From {price}"), soldOutLabel ("Sold out"), soldOutSummary ("sold out"), freeSummary ("free"), noImageSummary ("no image available").

href*
string
—

Link to the event detail page. The entire card is wrapped in <a href>.

soldOut
boolean
false

When true: shows a "Sold out" scrim over the image, hides the price chip, and aria-label includes "sold out".

badges
{ label: string; variant: "new" | "hot" | "last-chance" }[]
—

Optional promotional badges stacked below the price chip.

Props

Preview

EventCard

Prop
Type
Required
Default
Description
id
string
Required
—

Unique event id (rendered as data-event-id on the link).

title
string
Required
—

Event name (line-clamp-2).

image
{ url: string | null; alt: string }
Required
—

1:1 profile image; null/empty url renders the gradient placeholder (aspect-square slot — not the wide banner).

date
{ day: string; month: string; year: string; iso: string }
Required
—

Pre-formatted date parts for the top-left chip; iso is carried for data/SEO tooling.

venue
{ name: string; city: string }
Required
—

Venue line — 'name · city' (city hidden when empty).

priceFrom
number
Required
—

Minimum price; 0 is ambiguous (free OR unknown) — with no priceLabel the card announces NO price and shows no price chip. Pass priceLabel to state the price.

currency
string
Required
—

ISO currency code for the Intl.NumberFormat('en') fallback.

priceLabel
string
Optional
—

Pre-formatted locale-aware price (e.g. 'À partir de 25,00 $') — highest precedence, overrides labels.fromPriceTemplate.

href
string
Required
—

Event detail link — the card's single <a> target.

soldOut
boolean
Optional
false

Top-right sold-out pill with labels.soldOutLabel; hides the price chip.

badges
EventCardBadge[]
Optional
[]

{ label, variant: 'new' | 'hot' | 'last-chance' } promotional chips (aria-hidden).

labels
EventCardLabels
Optional
EN defaults

i18n bag — fromPriceTemplate, soldOutLabel, soldOutSummary, freeSummary, noImageSummary. FR: spread EVENTCARD_LABELS_FR. ADR-067 §2: freeSummary stays in the bag for API compatibility but is no longer emitted — announce a free event with priceLabel.

category
string | null
Optional
null

Already-localized event category. Shown as a readable caption on the no-image placeholder and included in the link's accessible name; ignored when an image is present.

Quick use:<EventCard />

Accessibility

ARIA rolelink

ARIA

1 rule

WCAG 1.3.1 / 4.1.2

Single <a> wraps the whole card (one focus target, focus-visible ring); decorative chips are aria-hidden with their info duplicated in the link's aria-label summary

a11y

2 rules

WCAG 1.4.3

Title/venue sit on a solid bg below the image — AA contrast regardless of image content

WCAG 1.3.1 / 4.1.2

The localized category caption appears and is announced only when the no-image placeholder is visible

Usage

Import

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

Example 1

tsxSnippet
<EventCard />

Example 2

tsxSnippet
<EventCard {...event} labels={{ ...EVENTCARD_LABELS_FR }} />

Example 3

tsxSnippet
<EventCard title={event.name} category={event.category_name_fr} image={{ url: null, alt: '' }} />

Anti-patterns

— Do not fetch data directly in organisms

— Avoid tight coupling to specific data sources

— Do not expect a CTA button — the entire card is the single link target

— Do not pass the wide banner image — the slot is aspect-square (profile image)

Design System v4.1.0 | ePassLive