@kagadmodyaa/sketchbook
v0.1.0
Published
A page-turning sketchbook component for React, built from DOM + CSS 3D transforms, no WebGL, no dependencies beyond react/react-dom.
Maintainers
Readme
@kagadmodyaa/sketchbook
A page-turning sketchbook component for React — a book you drag to turn, built from DOM and CSS 3D
transforms. No WebGL, no dependencies beyond react/react-dom. It renders a full book: cover,
binding, paper texture, page-turn physics, an optional magnifying loupe, and a larger "lightbox"
view, all themeable through CSS custom properties.
Live playground: https://kagadmodyaa.vercel.app/components/sketchbook
Install
a) npm
npm i @kagadmodyaa/sketchbookimport '@kagadmodyaa/sketchbook/style.css'b) shadcn CLI — copies the component source straight into your project, so you can edit it:
npx shadcn@latest add https://kagadmodyaa.vercel.app/r/sketchbook.jsonc) "Copy for AI" — from the component page on the site, for pasting into an AI coding assistant.
Usage
import { Sketchbook } from '@kagadmodyaa/sketchbook'
import '@kagadmodyaa/sketchbook/style.css'
<Sketchbook
pages={[
{ spread: '/pages/01-title.webp', alt: 'Title page' },
{ left: '/pages/02a.webp', right: '/pages/02b.webp', alt: 'Floor plan and section' },
]}
book={{ binding: 'spiral', cover: { title: 'Portfolio' } }}
/>Next.js: this is a client component (it renders "use client", both in the built bundle and in the
source you get from the shadcn CLI), so it works as-is inside the App Router — no extra directive
needed in the file that imports it.
Page model
Each entry in pages is one opening of the book (what you see when it lies open):
spread— one image laid across both pages, split at the fold. For the defaultsquarepage shape this should be a 2:1 image (e.g. 2400×1200 for a landscape portfolio page).left/right— a separate image for each side; either may be left out.
Props
| Prop | Type | Description |
|---|---|---|
| pages | SketchbookPage[] | The openings of the book. Each is { alt, caption?, project? } plus either spread or left/right (see Page model above). alt is required. |
| book | BookOptions | Look and behaviour of the book — see table below. |
| projects | SketchbookProject[] | { slug, title, color? }[]. Lets pages link to a project (for tabs and the lightbox's "View project" link). |
| className | string | Extra class on the root <section>. |
| label | string | Accessible label for the book (aria-label). Default "Sketchbook". |
| onEvent | (event: SketchbookEvent) => void | Fires on page turns ({ type: 'turn', opening, total }) and opens ({ type: 'open', opening, side }). |
| onPageClick | (opening: number, side: 'left' \| 'right') => boolean \| void | Called when a page is clicked or tapped. Return true to stop the built-in lightbox from opening (e.g. to navigate yourself). |
| projectHref | (slug: string) => string | Href for a project, used by the lightbox's "View project" link. |
book options
| Option | Type | Description |
|---|---|---|
| pageShape | 'portrait' \| 'square' \| 'landscape' | Shape of one page. 'square' (default) opens to a 2:1 spread. |
| binding | 'spiral' \| 'wire-o' \| 'stitched' \| 'coptic' \| 'glued' | spiral: one continuous coil · wire-o: twin wire loops · stitched: sewn signatures (thread in the fold) · coptic: exposed chain stitch across the spine · glued: perfect-bound, a deep fold, nothing visible. |
| bindingColor | string | Wire colour (spiral, wire-o) or thread colour (stitched, coptic). |
| paper | string | Paper colour, any CSS colour. Page images are printed onto it (dark papers switch that off). |
| paperTexture | 'cold-press' \| 'hot-press' \| 'smooth' \| 'kraft' \| 'dotted' \| 'grid' \| 'none' | cold-press: toothy watercolour paper · hot-press: fine and smooth · smooth: plain · kraft: fibrous · dotted: dot-grid sketchbook · grid: squared sketchbook · none: flat colour. |
| printOnPaper | boolean | When true, whites in your images take the paper colour (looks printed). |
| imageFit | 'contain' \| 'cover' | 'contain' (default) shows every image whole, with paper around it if its shape differs from the page. 'cover' fills the page and trims the rest. |
| cover | CoverOptions \| false | The book starts closed on this cover. false starts open. See below. |
| ribbon | string \| false | Ribbon bookmark colour, or false for none. |
| intro | boolean | Open the cover and riffle through to openAt when the page loads. |
| openAt | number | Which opening (1 = first page in pages) to settle on after the intro. |
| loupe | boolean | Draggable magnifying glass (desktop only). |
| captions | boolean | Show page captions under the book. |
| tabs | boolean | Coloured index tabs on the page edge, one per project. |
| maxHeight | string | Largest height the book may take, as a CSS length. |
cover (CoverOptions): title, subtitle?, color? (board colour), ink? (title lettering
colour), material? ('cloth' | 'kraft' | 'leather' | 'card'), image? (artwork filling the front
board), band? (elastic band colour, or false), align? ('left' | 'right', where the title
sits).
Imperative handle
const book = useRef<SketchbookHandle>(null)
<Sketchbook ref={book} … />| Method | Description |
|---|---|
| goTo(opening) | Go to an opening (1-based, like openAt). 0 closes the book. |
| goToProject(slug) | Go to the first page of a project. |
| next() / prev() | Turn one page. |
| current() | Current opening, 0 when closed. |
| replayIntro() | Close the book and play the opening animation again. |
How it works
| File | Role |
|---|---|
| engine/model.ts | Positions: 0 = closed cover, n = n-th opening. L(n)/R(n) faces. A forward turn lifts R(n) and its back is L(n+1). |
| engine/leaf.ts | The turning sheet: a chain of nested strips, each rotated a little relative to its parent, so the sheet bows. Bending is weighted towards the spine and fades to flat at both ends of the turn. Each strip is shaded at both edges from its angle. The cover is one rigid strip. |
| engine/paint.ts | Paints a face or a vertical slice of one as CSS backgrounds, fitted to its frame (one page, or the whole spread for images crossing the fold): whole by default, or filling the page. |
| engine/engine.ts | State, DOM building, turn control (drag, tap, keys, springs, riffle), tilt/zoom, captions, deep links (#page-n). React renders the skeleton once; the engine writes styles and CSS variables per frame. |
| engine/loupe.ts | Magnifier: a masked, scaled copy of the resting spread under a draggable glass. |
| engine/chrome.ts | Paper surfaces (tooth, dot and line grids), cover materials, and the bindings (spiral, Wire-O, stitched, Coptic, glued), generated as inline SVG. |
| engine/motion.ts | Spring and tween. |
| Sketchbook.tsx, Lightbox.tsx | React wrapper and the larger-view dialog. |
| sketchbook.css | All styles, themed through --skb-* custom properties. |
Interaction: a drag turns the page. A tap (under 7 px of movement) opens the larger view. Arrow keys
turn pages; +/-/0 zoom when the book has focus. prefers-reduced-motion swaps turns for a fade.
MIT © Kagadmodyaa
