Standards
Events & News pattern
One module for event and news content, with latest + archive behavior.
Example — listing (News/Events index page)
This is what a News/Events index page renders — the future-events banner is deliberately not shown here; see the separate "Home page banner" example below for why.
Latest entries are shown first in calendar order; older entries move into archive.
Featured update
external article
Community Grant Award Supports Rescue Operations
March 1, 2026
A regional grant will support vaccinations, transport, and placement assistance.
Status: upcoming • Recurring
Monthly Basket Raffle
March 7, 2026 at 7:00 PM • Community Hall, 456 Oak Ave, Example City, ST
First Saturday of every month — dog-themed gift baskets, tickets $5 each or 5 for $20.
local article
Self-Hosted Video Example
March 5, 2026
Example of a news entry using videoSrc (a real, self-hosted mp4) instead of videoEmbed — prefer this whenever the source clip can be downloaded, since a self-hosted file always plays.
local article
Community Partner Spotlight (Local Article)
February 24, 2026
Example of a news entry that links to a local article page hosted inside the site, with the source Facebook video embedded via videoEmbed instead of a static image.
Archive (5)
Status: upcoming
Community Fundraiser
Saturday, February 21, 2026 • Example City, ST
One-day fundraiser supporting ongoing rescue operations.
external article
Field Rescue Team Spotlight
February 10, 2026
How retrieval specialists and fosters coordinate rescue-to-home outcomes.
Status: upcoming • Recurring
Second Weekend of the Month Foster Fair
January 10, 2026 at 6:00 PM • Riverside Park, Example City, ST
Runs on the second weekend each month (Saturday + Sunday).
Status: upcoming • Recurring
Biweekly Supply Drive
January 10, 2026 at 5:00 PM • Warehouse Pickup Hub, Example City, ST
Every 2 weeks donation sorting and supply drop-off.
external article
Volunteer Recruitment Drive
January 10, 2026
New volunteer openings for transport, events, and foster support.
Home page banner (separate from the listing above)
showFutureEventsBanner renders EventBanner — a dismissible announcement banner for upcoming events. It's a home-page-only element, shown here in isolation (no listing, no archive) to make that clear — never enable it on the same instance used for a News/Events index or archive page, as shown above. In practice, a real site computes this same event list from its own entries via EventsNewsSection's internal toBannerEvents() helper (by passing showFutureEventsBanner on the home page's own instance); the array below is constructed directly only so this example can render the banner with nothing else underneath it.
Structured content blocks (cardMode="full")
The richer contentBlocks renderer used on detail-style cards, instead of the summary-only "index" mode shown above.
Status: upcoming
Community Fundraiser
Saturday, February 21, 2026 • Example City, ST
One-day fundraiser supporting ongoing rescue operations.
Join us for a neighborhood fundraiser supporting emergency foster placements and transport costs.
What to expect
- Adoption information booth and volunteer sign-ups
- Pet supply donation drop-off lane
- Local sponsor raffle every hour
Bring donation receipts
Some partner organizations offer matching gifts when donation receipts are submitted within 7 days.
Image layout (imageLayout="alternating")
imageLayout independent of cardMode: entries with an imageSrcrender image-beside-text, flipping sides every other card. An entry without an image still renders as a plain text-only card. imageLayout="side" is also available for image-always-on-the-left, and the default "auto" preserves the existing top-image/text-only behavior shown in the sections above.
external article
Field Rescue Team Spotlight
February 10, 2026
How retrieval specialists and fosters coordinate rescue-to-home outcomes.
Standard
- Use `EventsNewsSection` with a unified `event | news` entry contract.
- Always keep full history in data; render latest entries + archive.
- Keep entries in calendar order (newest first for combined feed).
- Show event-only links (`Open in Maps`, `Add to Google Calendar`, `Share event`) only for events.
- Do not duplicate event actions for news-only items.
- Event-action parity rule: every migrated site news/events implementation must expose these three event actions.
- News entries may link to external URLs or local article routes (for in-site articles).
- For event detail pages, use index + detail routing (for example `/events` plus `/events/my-event-slug`).
- Use lightweight metadata labels for status/type context on cards.
- `showFutureEventsBanner` is home-page-only — enable it only on the `EventsNewsSection` instance mounted on the home page (see the "Home page banner" example above), never on a News/Events index or archive page instance.
- Use `eventDetailsBasePath` when events should auto-link to local event detail routes.
- Recurring event banner cards should resolve to local detail links when `eventDetailsBasePath` is configured.
- For index pages, use `cardMode="index"` so cards stay summary-only (no body/highlights/images).
- Recurring events are supported via `event.recurrence` (weekly, biweekly, monthly by day, monthly nth-week weekday).
- Keep `EnvironmentBanner` and event banner separated (never merged into one banner).
- If both render on a page, order must be `EnvironmentBanner` first, then event banner.
- Render event banner only when there are active future events; if none exist, do not render event banner row.
- Banner placement standard: render both in page flow directly under the header; do not mount globally in shared layout.
- Content seeding standard: do not add fake/sample events in migrated production-like sites; start with empty entries until real events exist.
- Optional featured card and archive controls are built in (`showFeatured`, archive props).
- `socialCta` is a required governance default (not opt-in) for sites on the current generation — populate it from `siteConfig.social.*` so a "follow us" row renders via `SocialFollowCta`; never hand-roll a page-local version.
- Set `entry.videoSrc` (a real, self-hosted mp4 URL) on any entry to render a native `<video>` inline on the detail page (and full-mode card) instead of a static image — takes priority over `videoEmbed` when both are set. Pair it with `entry.imageSrc`, which doubles as the poster frame, and optional `videoAspectRatio` (`"landscape"` default, `"portrait"` for a vertical Reel-shaped clip). Card thumbnails (news index, homepage highlight) still use `imageSrc`/`imageAlt`. Prefer this over `videoEmbed` whenever the source clip can be downloaded (e.g. via `yt-dlp` from a Facebook Reel) — a self-hosted file always plays, where the Facebook iframe embed below can be blocked by Rights Manager depending on the source post's audio track.
- Set `entry.videoEmbed` (provider `"facebook"`, plus `url`, optional `aspectRatio`, optional `title`) on a `local` news entry to embed the source Facebook video/Reel inline instead, when a self-hosted `videoSrc` isn't available. `aspectRatio` defaults to `"portrait"` for `/reel/` URLs, `"landscape"` otherwise. Card thumbnails still use `imageSrc`/`imageAlt` — keep both set so the thumbnail looks right even though the embed wins on the full/detail view. A prior fleet attempt at embedding Facebook video was reverted after hitting a rights-block on a post with non-original audio; always visually verify an embed actually plays before shipping it on a real entry.
- Local article workflow: create a local page route (e.g. `/news/my-article-slug`), add it to `AppRoutes.tsx`, and point the news entry's `href` at it. Build-time route discovery (`tools/app-routes.mjs`) picks it up for prerender + sitemap; for dynamic routes, enumerate concrete URLs via `PRERENDER_EXTRA_ROUTES`. Apply the same rule to event detail routes. Validate with `npm run build` and `npm run qa:smoke:nojs`.
- `imageLayout` (`"auto" | "none" | "top" | "side" | "alternating"`, default `"auto"`) controls image placement on list cards independent of `cardMode` — `"auto"` preserves the original `cardMode`-driven behavior byte-for-byte, so no existing site is affected by leaving it unset.
- For a detail page's Previous/Next navigation, compute both neighbors with `getAdjacentEntries(entries, currentId, eventDetailsBasePath, currentOccurrenceStartAt?)` (exported from `EventsNewsSection.tsx`) and pass the result straight to `EventsNewsDetail`'s `previous`/`next` props — do not reimplement chronological-adjacency logic per site. Pass the 4th param (the resolved entry's own `startAt`) whenever the entry is recurring, or Previous/Next matches whichever occurrence of that series sorts first instead of the one actually being viewed. Either side renders nothing when there's no neighbor (start/end of the timeline).
- For a recurring event's detail page, resolve which occurrence to show via `resolveEventOccurrence(entry, now, requestedDateYmd?)` (exported from `EventsNewsSection.tsx`) — do not reimplement this occurrence-resolution math per site. Read a `?date=YYYY-MM-DD` query param from `useSearchParams()` and pass it through as `requestedDateYmd`; it resolves to that specific occurrence when it's a real generated one, otherwise falls back to the live next occurrence via `getNextOccurrence()`. `expandEventEntry()`/`toEventDetailsHref()` already stamp `?date=<occurrence>` onto every occurrence card's link, so each occurrence in a list/archive is individually addressable without further wiring.
- `EventsNewsDetail`'s event-action row (`Open in Maps`/`Add to Calendar`/share) is hidden once the event has passed — gated on `getEventStatus(entry) !== "past"` (exported from `EventsNewsSection.tsx`); an "ongoing" event (happening right now) still shows it. Those actions aren't relevant for something already over.
- Event detail route examples (for sitemap + prerender): Friday Adoption Meet-and-Greet, Biweekly Supply Drive, Second Weekend Foster Fair, Monthly Volunteer Orientation, and Community Fundraiser.
- Local article route example: News Article Example
- Component: `template/src/components/patterns/EventsNewsSection.tsx`
- Localization lib: `template/src/lib/localized-content.ts`
Localization (bilingual sites)
- `EventsNewsSection`/`EventBanner` themselves stay plain-`string` and single-language — do not add localized fields to the shared components.
- For a bilingual (EN/ES-toggle) site's real event/news content, author each translatable field as `LocalizedText` (from `lib/localized-content.ts`): a plain string, or an en/es record.
- Keep a `resolve*Entry(entry, locale)` mapper in the site's own `data/events.ts`/`data/news-items.ts` that resolves `LocalizedText` fields down to the plain-`string` `EventsNewsEventEntry`/`EventsNewsArticleEntry` shape via `resolveText(value, locale)` — call it from the page component with `toContentLocale(i18n.resolvedLanguage)` before handing `entries` to `EventsNewsSection`.
- `EventBanner` takes an optional `locale` prop (string, default `"en-US"`) for its `Intl.DateTimeFormat` date/time display. On a bilingual site, pass it `toIntlLocaleTag(toContentLocale(i18n.resolvedLanguage))` — otherwise the banner's date/time stays English-formatted even when the rest of the page is in Spanish.
Template