Standards
Dog Spotlight pattern
Highlight a curated set of dogs — e.g. longest-waiting residents — in a 4-across grid, each linking out to their listing.
Example
dogs accepts LocalAdoptableManifestItem[] — the same source-agnostic shape used by the local-manifest adoptable-pets loader, so hand-curated data and a future automated pull (GetBuddy, Petfinder, local manifest) share one type.
Same dogs, a different presentation
The exact same four dogs from the example above, passed through the same component — only the optional `title`/`description`/`labels.ctaPrefix` props change, to show the data itself is presentation-agnostic.
Ready for their forever home
These four have been waiting the longest — give one of them a second look.
Without a detailsUrl
A dog without a detailsUrl still renders its card (photo, name, breed, age • gender) but with no CTA button — there is nothing to link to yet.
Juniper
Labrador Retriever, Mixed Breed
2 years old • Female
Standard
- dogs is LocalAdoptableManifestItem[] (from adoptable-local-manifest.ts) — the same manifest shape used elsewhere in the ecosystem, so this data can start as a hand-curated array and later be swapped for an automated GetBuddy/Petfinder/local-manifest pull without changing the component.
- Selection of which dogs to feature is entirely a call-site concern — pass in exactly the curated list, in the order you want it shown. There is no built-in sort/filter.
- Grid is fixed at up to 4 across (`grid-cols-1 sm:grid-cols-2 lg:grid-cols-4`) — this component's whole purpose is the 4-card spotlight row, not a general-purpose gallery, so there is deliberately no `columns` prop.
- Age • gender line is computed via `resolvePetAgeLabel()` from `lib/pet-age.ts` (never hand-rolled) using each item's `age`/`birthDate`.
- The CTA button (default "Meet {Name}", via `labels.ctaPrefix`) renders only when `detailsUrl` is present — a dog without one still renders its card, just without a button. `ctaPrefix` is a plain word/phrase (e.g. "Meet", "Adopt"), never a {{token}}-style template string, so it's safe to pass straight through i18next's `t()` without colliding with its own interpolation syntax.
- If `dogs` is empty, the component renders nothing — no empty-state placeholder.
- Distinct from `AdoptablePetsSection` (the full adoptable-pets listing shell with Petfinder/Adopt-a-Pet/GetBuddy/local-manifest provider modes) — this is a small, separate curated highlight, not a listing provider.
- Shared type: `LocalAdoptableManifestItem` in `adoptable-local-manifest.ts`
- Component: `template/src/components/patterns/DogSpotlightSection.tsx`
Template