DocsNewsletter Bookshelf

Newsletter Bookshelf

A tactile WebGL archive with cloth-bound book textures, horizontal camera navigation, hover lift, centered cover inspection, and automatic drag-adjustable orbiting.

Installation

pnpm dlx shadcn@latest add @componentry/newsletter-bookshelf

Usage

API Reference

PropertyTypeDefault
items

Archive entries with title, date, optional href, and optional cover colors. Activate a focused book again to open its href.

NewsletterBookshelfItem[]—
height

Height of the WebGL stage.

number | string620
brand

Wordmark printed on every generated cover.

string"The Brief"
onSelect

Called when a book enters the focused view.

(item, index) => void—
className

Additional classes for the outer shelf stage.

string—

View source

Click the icon in the preview panel to browse source for each variant.

Keep in mind

This component is inspired by various open-source projects and patterns. Please verify licenses and implementation details before using in production.

Need a custom component?

I build bespoke UI components & websites tailored to your brand.

DM me on X
import { NewsletterBookshelf } from "@/components/ui/newsletter-bookshelf"
"use client"

import {
  NewsletterBookshelf,
  type NewsletterBookshelfItem,
} from "@/components/ui/newsletter-bookshelf"

const editions: NewsletterBookshelfItem[] = [
  {
    id: "edition-04",
    title: "The useful systems issue",
    date: "JUL 24, 2026",
    href: "/posts/useful-systems",
  },
  {
    id: "edition-03",
    title: "A better creative workflow",
    date: "JUL 17, 2026",
    href: "/posts/creative-workflow",
  },
  {
    id: "edition-02",
    title: "Notes on building in public",
    date: "JUL 10, 2026",
    href: "/posts/building-in-public",
    color: "#efe8d4",
  },
  {
    id: "edition-01",
    title: "The small team advantage",
    date: "JUL 3, 2026",
    href: "/posts/small-team-advantage",
    color: "#16277a",
  },
]

export default function Archive() {
  return (
    <NewsletterBookshelf
      items={editions}
      brand="Studio Notes"
      onSelect={(edition) => console.log("Selected", edition.id)}
    />
  )
}