Skip to content
ilinxa/pro-ui

Thumbnail List

alphav0.2.0

Linked thumbnail list — small image, title, and meta line per row, each row one link target.

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

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

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/thumbnail-list

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/thumbnail-list-fixtures

Preview

Demo source

demo.tsxtsx

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). Pass headerIcon={null} to hide it entirely.
  • Items without href render 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 via imageClassName for square / portrait / other shapes.
  • The component is exported as React.memo. Pass stable refs for linkComponent / headerIcon / renderMeta for 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)

Tags

thumbnail-listlistthumbnailsrelatedsidebardata

Dependencies

npm peer deps: lucide-react@^1.11.0