Skip to content
ilinxa/pro-ui

Event Card

alphav0.2.0

Event preview card with six status states and four layouts — capacity-aware badges, overlay links, and soft-failure item handling.

Category: Data DisplayUpdated: 2026-08-11Created: 2026-05-02Author: ilinxa

Context

Use when listing events on a community / association / conference / training site, or when mixing events into a multi-content feed. The grid variant fits magazine grids; the feed variant fits social-style single-column feeds; the list variant fits info-rich sidebars / dashboard listings; the compact variant fits text-only sidebar widgets / 'upcoming events' tickers. Public helpers (`getEventStatus`, `EVENT_STATUS_CONFIG`, `formatEventDate`, `getDaysUntilEvent`) are exported alongside the card so consumers can derive status independently for header counters, calendar coloring, filter logic, or deterministic tests — without rendering a card. Migration origin: kasder kas-social-front-v0 EventCard.tsx + SocialEventCard.tsx.

Installation

Initialize shadcn (once per project)Seeds lib/utils.ts and components.json. Skip if you've already used any shadcn component.
pnpm dlx shadcn@latest init
Register the @ilinxa namespace (once per project)Add to your components.json. Merge with existing config.
"registries": {
  "@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}
Install the component
pnpm dlx shadcn@latest add @ilinxa/event-card

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/event-card-fixtures

Preview

Future of Open Source: Annual SummitRegistration openConference
30
days left

Future of Open Source: Annual Summit

Two days of talks, workshops, and hallway conversations with maintainers from across the open-source ecosystem.

  • July 1, 2026
  • 09:00 - 18:00
  • Istanbul Conference Center
50 / 200150 spots left
Tailwind v4 Deep-Dive WorkshopSoonWorkshop
5
days left

Tailwind v4 Deep-Dive Workshop

Hands-on workshop covering Tailwind v4's new theme system, container queries, and OKLCH color migration.

  • June 6, 2026
  • 14:00 - 17:00
  • Online (Zoom)
60 / 10040 spots left
Designing for Accessibility: Component AuditLast spotsWebinar
14
days left

Designing for Accessibility: Component Audit

Walk through a real WCAG 2.1 AA audit on a production component library and the fixes that followed.

  • June 15, 2026
  • 10:00 - 11:30
  • Online
85 / 10015 spots left
Live Build: Knowledge Graph EditorLive nowWebinar
Happening now

Live Build: Knowledge Graph Editor

Live coding session — building a force-directed knowledge graph editor on top of Sigma.js v3 and React 19.

  • June 1, 2026
  • 16:00 - 18:00
  • Online (Streaming)
312 / 500188 spots left
Founders' Roundtable: Hiring in 2026Sold outPanel
10
days left

Founders' Roundtable: Hiring in 2026

Five founders share what's working and what's not in technical hiring this year. Off the record.

  • June 11, 2026
  • 18:30 - 20:00
  • Galata Hub, Istanbul
50 / 50Sold out
GraphQL Federation in PracticeEndedTraining

GraphQL Federation in Practice

Two-day hands-on training on GraphQL federation v2 — from subgraph composition through production rollout.

  • May 23, 2026
  • 09:00 - 17:00
  • Atlas Plaza, Ankara
28 / 30
Knowledge Day 2026: Annual Member GatheringRegistration openConference
75
days left

Knowledge Day 2026: Annual Member Gathering (Featured event)

Full-day flagship event — keynotes, breakout tracks, networking, and the annual Knowledge Day awards ceremony.

  • August 15, 2026
  • 10:00 - 22:00
  • Istanbul Conference Center + Online
100 / 300200 spots left
Open Office Hours (Drop-In)Registration openWebinar
92
days left

Open Office Hours (Drop-In)

Casual drop-in office hours — no registration needed, no cap. Bring your questions about anything we ship.

  • September 1, 2026
  • 16:00 - 17:00
  • Online (Drop-in link emailed week-of)

Demo source

demo.tsxtsx

Usage

When to use

Reach for EventCard when you need an event preview with a live, time-and-capacity-aware status (registration open, almost full, sold out, happening right now, ended) plus a status-driven CTA. Four layouts: variant="grid" (image-on-top magazine card), variant="feed" (full-bleed image background, white-on-dark content), variant="list" (thumbnail + 4-icon meta + status indicator), and variant="compact" (text-only minimal row for sidebars / widgets).

Minimal example

import { EventCard } from "@/components/event-card";

<EventCard
  event={{
    id: "1",
    title: "Annual Summit",
    type: "Conference",
    date: "2026-07-01",
    time: "09:00 - 18:00",
    location: "Istanbul Conference Center",
    image: "/cover.jpg",
    capacity: 200,
    registered: 50,
  }}
  variant="grid"
  href="/events/1"
/>;

Four variants

  • grid — image-on-top magazine card with full meta lines, capacity progress bar, and decorative status CTA at the foot. Renders well in 1/2/3-column responsive grids.
  • feed — full-bleed image background, content overlaid white-on-dark. Single-column feed item. No capacity progress bar (intentional — spots-left is shown inline in the meta row instead).
  • list— info-rich row with thumbnail: 16/20 px square thumb left, content middle (status badge + type badge + title + 4-icon inline meta row showing date / time / location / spots-left), status-aware right indicator (days-until / pulsing "Live now" / chevron). Featured rows get a left accent border. Use when you want a scannable list with visual context per row.
  • compact— text-only minimal row for sidebars / widgets / "upcoming events" tickers. Title + type pill (top-right) + 3 stacked meta lines (date / time / location). NO thumbnail, NO status badge, NO capacity bar. Tightest variant — maximum density per pixel. Pairs naturally with a "See all →" footer link.

Six statuses (auto-derived)

Status flows from the event data + an optional now reference. The card never needs you to set status — it's derived from the event and clock:

  • open — registrations available, more than 7 days out.
  • upcoming — within 7 days. Amber badge.
  • lastSpots — ≥80% capacity hit. Amber badge + spots-left counter flips to text-destructive when ≤5 left.
  • ongoing — between start and end. Pulsing badge + live indicator.
  • full — capacity reached. Sold-out CTA, button disabled.
  • expired — past end date. Card fades to opacity-60 grayscale-30; CTA shows "View details."

Public helper kernel — use status logic without rendering the card

The status helpers are exported alongside the component so consumers can derive status, format dates, and compute days-until without rendering an actual card — for header counters, calendar day-cells, filter logic, deterministic tests:

import {
  EventCard,
  getEventStatus,
  EVENT_STATUS_CONFIG,
  formatEventDate,
  getDaysUntilEvent,
  type EventStatus,
} from "@/components/event-card";

// Header counter
const liveCount = events.filter(
  (e) => getEventStatus(e) === "ongoing",
).length;

// Calendar day cell — pure-helper composition, no card render
function DayCell({ events, day }: { events: EventCardItem[]; day: Date }) {
  const todays = events.filter((e) => sameDay(e.date, day));
  const hasOngoing = todays.some(
    (e) => getEventStatus(e, day) === "ongoing",
  );
  return (
    <div className={hasOngoing ? "bg-accent/30" : ""}>
      {todays.length > 0 && <span>{todays.length}</span>}
    </div>
  );
}

Polymorphic root + custom href

import NextLink from "next/link";

<EventCard
  event={event}
  variant="grid"
  linkComponent={NextLink}
  getHref={(e) => `/etkinlik/${e.slug}`}
/>;

Localized labels + custom date formatter

<EventCard
  event={event}
  variant="grid"
  href={href}
  formatDate={(d) =>
    new Date(d).toLocaleDateString("tr-TR", {
      day: "numeric",
      month: "long",
      year: "numeric",
    })
  }
  labels={{
    open: "Kayıt Açık",
    upcoming: "Yaklaşıyor",
    ctaRegister: "Kayıt Ol",
    daysUntilSuffix: "gün kaldı",
    capacityAriaPrefix: "Kayıtlı",
    capacityAriaSeparator: "/",
    // ... 17 keys total, all optional
  }}
  typeStyles={{
    Konferans: { className: "bg-primary/10 text-primary border-primary/20" },
  }}
/>;

Actions slot — overlay-link pattern

The whole card is a link via the overlay-link pattern. Drop nested interactive buttons (Save / Share / Calendar) into the actions prop — they render at z-10 above the link overlay. Each action button MUST call e.preventDefault() + e.stopPropagation() in its onClick or the click bubbles to the link and navigates. Tab order: card-link → action1 → action2 → next card.

Deterministic now for testing + live clocks

Pass a now Date to make status derivation deterministic. For demos / SSR / screenshot tests, pin nowto a fixed date so events don't silently drift over time. For minute-accurate live status, drive now from a parent setInterval:

function LiveEvents({ events }: { events: EventCardItem[] }) {
  const [now, setNow] = useState(() => new Date());
  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 60_000);
    return () => clearInterval(id);
  }, []);

  return events.map((e) => (
    <EventCard key={e.id} event={e} variant="grid" href={`/events/${e.id}`} now={now} />
  ));
}

Soft-failure

Only id, title, type, date are required. Missing optional fields gracefully omit:

  • No capacity / registered — capacity bar + spots-left counter omitted; status logic skips full / lastSpots states; CTA reverts to plain Register / View Details / Ended.
  • No image — tinted placeholder with calendar icon.
  • No endDate — falls back to date (single-day event spans 00:00 → 23:59:59).
  • No time / location / description — meta line omitted.

Notes

  • The CTA at the foot is decorative — clicking it navigates via the wrapping link overlay. To render a REAL register button (registration dialog, in-place form, etc.), drop one in actions.
  • Status is derived once at render — no internal setInterval. For live updates, drive now from upstream.
  • Helpers (getEventStatus, EVENT_STATUS_CONFIG, formatEventDate, getDaysUntilEvent) are part of the API contract — stable across v0.x.
  • Status differentiated by both color AND icon (color-blind safe).
  • All transforms + pulse animations gated via motion-safe: — reduced-motion users see static cards.

Features

  • 6-state status state machine (open / upcoming / lastSpots / ongoing / full / expired)
  • 4 visual variants — grid (image-on-top), feed (full-bleed background), list (info-rich row with thumbnail), compact (text-only minimal row)
  • Status differentiated by BOTH color AND icon (color-blind safe)
  • Public helper kernel — getEventStatus, EVENT_STATUS_CONFIG, formatEventDate, getDaysUntilEvent
  • Polymorphic root via linkComponent (works with NextLink / RemixLink / etc.)
  • Overlay-link pattern with optional actions slot for nested interactives
  • Soft-failure on optional fields (capacity-less events skip full/lastSpots states + capacity bar)
  • Deterministic status via optional `now` injection (testability + live-clock hosts)
  • 17-key labels object for full i18n
  • typeStyles map for consumer-defined event-type taxonomies
  • Featured treatment — top accent border (grid) / inset ring (feed) + star title prefix
  • WCAG 2.1 AA — aria-labelledby + useId, motion-safe gating, capacity bar aria-label, color-AND-icon status

Tags

event-cardeventsstatuscapacitycard

Dependencies

shadcn primitives: button, progress
npm peer deps: lucide-react@^1.11.0