Lookbook
A printed sheet of images and videos that unrolls from a spiral. Scroll, drag or arrow-key along it and the roll feeds out, pages passing under a soft directional light with the paper's reverse showing through the curl. Images are stitched into one sheet, so there are no seams and no per-image loading as you travel, and videos play on their own pages.
Included with membership
Members install Lookbook with one command and get the full source, the usage code and the Framer remix, licensed for client work. One membership covers the whole library and the CLI.
When to use it
A body of work shown as a sequence rather than a grid — a lookbook, a case-study reel, a photo essay, a product range, a run of short clips. It suits pages that share a shape, since the roll gives every one the same slot. Give it the full width and most of the height of a screen; it is a page, not a card.
- Define images outside the component, or memoise it. A new array on every render reloads and re-stitches the whole sheet.
- Use pages of the same aspect ratio. The first page sets the shape of every page, and the others are made to fit it.
- Give it real height. It fills its container and the sheet is sized as a fraction of that, so a short parent gets a small roll floating in space.
- Keep the set modest. Images are stitched into a single texture capped at 8192px wide, so past roughly a dozen pages each image gets less resolution. A playing video reads its own texture, so it keeps its resolution.
- Serve images and videos from your own domain or with CORS headers. They are drawn as textures, so a cross-origin file without them will fail to load rather than render.
- Keep video pages short, muted and light, and cut them to loop cleanly. Every video on the sheet loads and plays at once, and a clip whose last frame does not match its first jumps each time it starts over.
Avoid:
- Do not set loop={false} and expect the ends to feel finished on their own — that mode adds rubber-band resistance past the ends, which is the cue that you have run out.
- Do not raise rollTwists far without also lowering rollRadius. More turns in the same radius packs the spiral until it self-intersects.
- Do not use alwaysRender unless you need it. Left off, the component renders only while something is moving and costs nothing at rest.
- Do not put interactive controls inside children without accounting for the drag. The whole surface is a drag target, so a click can be read as the start of a pull.
Props
A printed sheet that unrolls from a spiral. Your images are stitched edge to edge into one long banner, and scrolling, dragging or arrow-keying feeds it out of the roll — paper curling off a cylinder, catching a soft directional light, the reverse of the sheet showing through the curve. Because it is one stitched sheet there are no seams between images and nothing loads as you travel along it. A page can also be a video, which plays in its place and bends with the paper like the printed pages around it.
<Lookbook />
The roll, its background, and the optional brand mark. Fills its container and handles wheel, drag and keyboard itself.
| Prop | Type | Default | Description |
|---|---|---|---|
images* | string[] | — | The pages, in order: image or video URLs. Images are stitched into one sheet, and the first page's aspect ratio sets the slot every page is drawn into. A URL ending in .mp4, .webm, .mov, .m4v or .ogv plays as a muted, looping video. Up to 8 videos play, and any past that show their first frame. |
brand | string | "NORTHDUNE" | Wordmark in the top-left corner. Pass an empty string to drop it. |
accent | string | "#eab308" | Colour of the wordmark. |
background | string | "#D9DFE3" | Colour of the room the roll sits in. |
imageFit | "cover" | "contain" | "cover" | How each image fills its slot. "cover" fills it edge to edge; "contain" fits the whole image and pads the remainder with letterboxColor. |
letterboxColor | string | "#0b0b0b" | Fills the gaps around a contained image. Only visible with imageFit="contain". |
backSideColor | string | "#ffffff" | Tints the reverse of the sheet, seen through the curl of the roll. A warm off-white reads as paper; pure white reads as plastic. |
rollBoundary | number | 0.66 | Where along the sheet the roll begins, from 0 to 1. Lower shows more flat sheet and less spiral; higher does the reverse. |
rollRadius | number | 0.70 | How thick the roll is. |
rollTwists | number | 6.0 | How many turns the spiral makes. Raise it with rollRadius or the coils overlap. |
rollFalloff | number | 1.00 | How quickly the spiral tightens toward its centre. 0 gives a even coil; higher winds it in more sharply. |
planeWidth | number | computed from the images | Width of the sheet in scene units. Left unset it is derived from the combined aspect ratio, which is almost always what you want. |
planeHeight | number | 3.0 | Height of the sheet in scene units. |
sheetHeightFraction | number | 0.50 | How much of the container's height the sheet is scaled to occupy. |
sheetWidthFraction | number | unset | How much of the container's width the lookbook spans, from the left edge of the roll to the end of the open sheet, with equal margins either side. Left unset, the open sheet holds only the pages beside the roll. Set, it runs on to the margin and repeats the pages where it needs more. On a narrow screen the sheet shrinks until a whole page fits beside the roll. |
positionX | number | auto-centred | Horizontal offset of the sheet. Left unset the visible part of the composition is centred for you, which accounts for the roll sitting off to one side. |
scrollRange | number | "auto" | "auto" | How far a full traverse moves the sheet. "auto" matches it to rollBoundary so one pass unrolls the whole thing. |
scrollSensitivity | number | 0.0006 | How far a wheel notch travels. |
dragSensitivity | number | 0.00065 | How far a pixel of drag travels. |
scrollLerp | number | 0.12 | How much of the remaining distance is covered each frame. Lower is heavier and slower to settle. |
inertia | boolean | true | Whether a flick keeps travelling after release. |
friction | number | 3.2 | How quickly that glide decays. Higher stops sooner. |
loop | boolean | true | Whether the sheet wraps around endlessly. Turned off, the ends resist with a rubber band and settle back. |
cameraFov | number | 48 | Field of view in degrees. Lower flattens the perspective on the roll. |
cameraDistance | number | 4.2 | How far back the camera sits. |
alwaysRender | boolean | false | Draws every frame instead of only while something moves. Off is both cheaper and the default; turn it on only if you are compositing something else over the canvas. |
dpr | number | [number, number] | set from the device tier | Pixel-density cap. Left unset it follows the hardware probe — 2 on capable devices, down to 1 on weak ones. |
ariaLabel | string | "Editorial Lookbook. Scroll, drag or use arrow keys to browse." | Label for the region. Worth rewriting to name the actual work being shown. |
children | React.ReactNode | — | Overlay content, rendered above the canvas and below the brand mark. |
className | string | — | Applied to the wrapper. |
mount | "auto" | "eager" | "eager" | When to build the scene. The default builds it on mount. Set "auto" to wait until the lookbook is near the viewport and the page has frame headroom. This is a heavy scene and it draws on demand rather than continuously, so deferring the build costs nothing visible once it is on screen. Neither setting takes it off the shared frame loop or overrides the reduced-motion preference. A scene running its own loop would be invisible to the scheduler, so it could not be set aside under load, and it would slow every other animation on the page rather than only itself. |
disableAdaptiveQuality | boolean | false | Keeps the pixel ratio at whatever the device reports, up to 2. By default the runtime caps it on lower tiers. This is not the same as dpr, which pins a fixed value: this one still follows the device, it just stops the runtime throttling it. |
ssr: false if your route renders on the server.