Thumbnail List
alphav0.2.0Linked thumbnail list — small image, title, and meta line per row, each row one link target.
Context
Built for sidebars and dropdowns: related posts, popular articles, search-suggestion results, 'up next' media queues, file-picker recents. Short lists only (3–10 items typically; cap ~20). Same family rhythm as author-card / newsletter-signup — composes cleanly in the same sidebar. Polymorphic link component, custom meta render slot, frame toggle, empty state.
Installation
pnpm dlx shadcn@latest init"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}pnpm dlx shadcn@latest add @ilinxa/thumbnail-listAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/thumbnail-list-fixturesPreview
Related
Default chrome — framed card, default header icon, native anchors. Drop into any sidebar slot.
Demo source
Usage
When to use
A linked thumbnail-list block — small image + title + meta line per row, each row a single link target. Built for sidebars and dropdowns: related posts, popular articles, search-suggestion results, "up next" media queues, file-picker recents.
Use it for short lists (3–10 items typically; cap at ~20). For longer result sets reach for data-table or magazine-layout.
Basic example
import { ThumbnailList } from "@/components/thumbnail-list"
const items = [
{ id: "1", title: "Sustainable cities", imageSrc: "/img/1.jpg", meta: "5 min read", href: "/news/1" },
{ id: "2", title: "Public transit on the rebound", imageSrc: "/img/2.jpg", meta: "3 min read", href: "/news/2" },
]
export function Example() {
return <ThumbnailList items={items} labels={{ heading: "Related" }} />
}No frame (inline)
Pass framed={false} to drop the card chrome. Useful in a search-suggestions dropdown, a modal, or any other surface that already provides its own framing.
<ThumbnailList items={searchHits} framed={false} />Custom meta rendering
The default meta render shows item.meta as a plain string. Pass renderMeta(item) to render dates, badges, scores, or anything else.
<ThumbnailList
items={posts}
renderMeta={(item) => (
<time className="text-xs text-muted-foreground mt-1 block" dateTime={item.publishedAt}>
{formatRelative(item.publishedAt)}
</time>
)}
/>Polymorphic link
Per-item href drives navigation. By default the row link renders as a native <a>. Pass linkComponent for router-aware links (e.g. next/link or RemixLink).
import Link from "next/link"
<ThumbnailList items={items} linkComponent={Link} />Empty state
When items is empty, the default empty fallback renders with labels.emptyText. For a richer custom UI, pass emptyState as a ReactNode — it replaces the default message.
<ThumbnailList
items={[]}
labels={{
heading: "Recently viewed",
emptyText: "Nothing here yet — articles you read will show up.",
}}
/>Notes
- Items render as
<li>inside a<ul>. The link wraps the row content; keyboard focus and hover both shift the title color (group-hover:text-primary/group-focus-visible:text-primary). - The header icon is decorative (
aria-hidden). PassheaderIcon={null}to hide it entirely. - Items without
hrefrender as plain rows (no link affordance). Useful when you want the visual but not the navigation. - Thumbnails default to
w-20 h-16(5:4 landscape). Override viaimageClassNamefor square / portrait / other shapes. - The component is exported as
React.memo. Pass stable refs forlinkComponent/headerIcon/renderMetafor memoization to hold. - Don't reach for this for paginated or virtualized lists — it renders all items eagerly. Cap input at ~20.
Features
- Fixed-shape items (id / title / imageSrc / imageAlt? / meta? / href?)
- Polymorphic link via linkComponent slot (default native anchor)
- renderMeta slot for dates / badges / scores / custom meta UI
- Frame toggle (framed: true card-style, false borderless inline)
- Custom or default header icon (Lucide); pass null to hide
- Empty state: emptyState ReactNode OR labels.emptyText fallback
- Configurable heading level (h2 / h3 / h4)
- i18n via labels prop with English defaults
- Memoized; SSR-safe; lazy-loaded thumbnails
- Keyboard parity (focus-visible:text-primary mirrors hover)