Guides: the quick starts and the examples, without the API reference # AI assistants > Where to point Claude Code, Cursor, Copilot or whatever agent you use, so it builds a book from these docs instead of from memory. Most assistants have seen StPageFlip and react-pageflip, and honestly that’s the problem: this library isn’t either of them. The names are different and there’s no compatibility layer, so an agent working from memory writes against an API that isn’t there. The fix is just pointing it at these docs before it writes anything. ## The docs as plain text [Section titled “The docs as plain text”](#the-docs-as-plain-text) Every page here is also served as Markdown, generated from the same source at every build, so an agent can read it without scraping HTML. * [`https://openpageflip.shittylittleapps.com/llms.txt`](/llms.txt): the index. A short description and links to the other two. If you’re not sure which to give it, give it this one. * [`https://openpageflip.shittylittleapps.com/_llms-txt/guides.txt`](/_llms-txt/guides.txt): the quick starts and the examples, without the API reference. Small enough to read whole, and enough to build a book from. * [`https://openpageflip.shittylittleapps.com/llms-full.txt`](/llms-full.txt): everything, API reference included. Tools that index docs from a URL (Cursor’s docs setting, for one) can be given the site address, [`https://openpageflip.shittylittleapps.com`](https://openpageflip.shittylittleapps.com), and will find the same pages. ## Tell it once [Section titled “Tell it once”](#tell-it-once) Put this in the file your agent reads at the start of every session - `CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md`, a Cursor rule, whichever yours is - and you don’t have to say it again. AGENTS.md ```md ## OpenPageFlip Page-flip effect for HTML content: `@openpageflip/core` (plain JavaScript) and `@openpageflip/react` (React 19). It is not StPageFlip or page-flip: the API is different, so read the docs before writing code. - Guides and examples: https://openpageflip.shittylittleapps.com/_llms-txt/guides.txt - Everything, API reference included: https://openpageflip.shittylittleapps.com/llms-full.txt ``` ## Or ask once [Section titled “Or ask once”](#or-ask-once) For a one-off, a prompt that starts with the docs does the same job. Fill in the bits in angle brackets. prompt ```text Read https://openpageflip.shittylittleapps.com/_llms-txt/guides.txt and then add an OpenPageFlip book to whose pages are . Follow those docs rather than what you know about StPageFlip or page-flip; the API is different. ``` # Plain JavaScript > Install the core package, put your pages in a container, and hand it to createBook. That's most of it. * npm ```sh npm i @openpageflip/core ``` * pnpm ```sh pnpm add @openpageflip/core ``` * yarn ```sh yarn add @openpageflip/core ``` ## Pages are elements [Section titled “Pages are elements”](#pages-are-elements) Put the pages inside one container, in reading order. Any element can be a page - a div, an img, a whole article. Give a page `data-density="hard"` and it turns as one stiff sheet instead of bending, which is what you want for covers. index.html ```html

OpenPageFlip

A page-turn effect for the web

Drag a corner

Pick up any corner and pull. Let go past the middle and the page turns; let go early and it settles back.

Or just click

A click on either half turns the page that way. On a touch screen, a swipe does the same.

Hard and soft

Covers are hard, so they swing as one stiff sheet. Inner pages bend along the fold, the way paper does.

Any HTML

Pages are ordinary elements. Text, images, forms, video - whatever a page holds keeps working.

The end

``` ## Make the book [Section titled “Make the book”](#make-the-book) Import the stylesheet once (it only handles the geometry, so your pages look however you style them), then hand the container to `createBook`. The book below runs this exact file, and so does the test suite. quickstart.ts ```ts import "@openpageflip/core/styles.css"; import { type Book, createBook } from "@openpageflip/core"; /** The container's children are the pages, in reading order. */ export function mount(container: HTMLElement): Book { return createBook(container, { width: 400, // base page size; with size "stretch" only the ratio matters height: 560, size: "stretch", // scale to the container, portrait when it gets narrow cover: true, // first and last pages stand alone }); } ``` ## Then [Section titled “Then”](#then) What comes back is a [`Book`](/api/openpageflip/core/type-aliases/book/): * `flipNext()`, `flipPrev()` and `flipTo(page)` animate a turn and give you a promise that resolves when the page lands. `turnTo(page)` just jumps there. * `book.on("flip", ({ page }) => ...)` fires whenever a different spread is showing. `on` hands back the unsubscribe function. * A press that starts on a link, button or form field never starts a flip, so pages can hold real controls. * `book.destroy()` stops everything and puts your DOM back the way it was. Every option and event is in the [reference](/api/openpageflip/core/), generated from the source on every build. ## Without a bundler [Section titled “Without a bundler”](#without-a-bundler) There’s also a single-file build for a script tag. It puts `createBook` on `window.OpenPageFlip` and otherwise works the same. index.html ```html ``` # Questions > Short answers to the things people ask first - how this relates to StPageFlip, what each package needs, and what isn't done yet. ## Is this StPageFlip? It's a rebuild of it. StPageFlip and react-pageflip haven't had a commit since April 2021, so we kept the part that makes the fold look right (the geometry maths, still MIT, still Oleg Litovski's) and rewrote everything around it in strict TypeScript. The look is held to the original by a test that runs the published page-flip 2.0.7 next to this library and compares screenshots, and the handful of places where we chose to differ are written down. [The differences, in the spec](https://github.com/benhonda/openpageflip/blob/main/SPEC.md) ## Can I drop it in where page-flip or react-pageflip was? Not without changes. The options and methods have new names and there's no compatibility layer for the old ones, so it's a small migration rather than a version bump. The migration guide is a table of every old name and where it went. [The migration guide](/start/migrate/) ## Does it work without React? Yes. The core package is plain TypeScript with nothing framework-shaped in it, so it works from Vue, Svelte, or a page with no framework at all. There's also a single-file build for a script tag that puts createBook on window\.OpenPageFlip, for when there's no bundler. [Using it without a bundler](/start/core/#without-a-bundler) ## What does the React package need? React 19, and the core package installed beside it. Every direct child of FlipBook is a page and every core option is a prop. Your page components don't need refs or forwardRef; the wrapper owns the page elements. [The React quick start](/start/react/) ## Does it render on the server (Next.js, Astro)? Yes. Nothing in either package touches window when it's imported, and FlipBook ships with "use client" and renders to HTML on the server. The React demo on this site is server-rendered by Astro and hydrated after, and the test suite renders it with renderToString in Node. ## Does it work on phones? Yes. Input is pointer events, so mouse, touch and pen all drag a corner, and a quick horizontal swipe turns the page. Leave layout on auto and the book shows a spread when the container has room for two pages and a single page when it doesn't. [Every option, in the playground](/examples/playground/) ## Can pages hold links, buttons and forms? Yes. A press that starts on a link, button or form field never starts a flip, so the control just works. That's the ignoreDragOn option. Its default selector is in the reference, and it includes a data attribute you can put on anything else you want left alone. [ignoreDragOn in the reference](/api/openpageflip/core/type-aliases/bookoptions/#ignoredragon) ## How do I get hard covers? Set cover to true so the first and last pages stand on their own, and give a page data-density="hard" (or density="hard" on Page in React) to make it stiff. Hard and soft pages mix fine in one book. [The plain JavaScript quick start](/start/core/) ## Can a page be an image? A page is any element, so an img works, and so does anything else you can put in the DOM. Everything is drawn with the DOM; there's no canvas renderer. ## Does it respect reduced motion? Yes. With prefers-reduced-motion on, a flip lands instantly instead of animating. Keyboard navigation and ARIA labelling aren't done yet - they're on the list before 1.0. ## What does an idle book cost? Nothing. Frames are drawn only when something changes, never on a timer, and destroy() stops everything and puts your DOM back the way it was. ## Is it ready for production? It's pre-1.0. The rendering is pixel-matched to the original, but the version stays at 0.x until the backlog of issues from the original projects is closed. Everything is verified in Chromium so far; Firefox and WebKit runs are still to come. [What has changed, release by release](/changelog/core/) ## What's the licence? MIT, same as the original. The fold geometry derives from StPageFlip, and the licence file credits it. [The licence file](https://github.com/benhonda/openpageflip/blob/main/LICENSE) # Migrating from StPageFlip > What changed between page-flip / react-pageflip and OpenPageFlip, as a table of every old option, method, event and prop and where it went. The names changed and there’s no compatibility layer for the old ones, so this is a small migration rather than a version bump. The tables below list every option, method, event and React prop the old packages had and where each one went. They’re typed against both APIs, so if a row is here, it’s current. Swap the dependency first. `page-flip` becomes the core package and `react-pageflip` becomes the React one (which needs the core package beside it, and React 19): * npm ```sh npm i @openpageflip/core ``` * pnpm ```sh pnpm add @openpageflip/core ``` * yarn ```sh yarn add @openpageflip/core ``` - npm ```sh npm i @openpageflip/core @openpageflip/react ``` - pnpm ```sh pnpm add @openpageflip/core @openpageflip/react ``` - yarn ```sh yarn add @openpageflip/core @openpageflip/react ``` Then the shape of things, which the tables don’t quite say on their own: * `new PageFlip(el, opts)` followed by `loadFromHTML(pages)` is one call now, `createBook(el, opts)`. The container’s children are the pages, and the stylesheet is a separate import. The [quick start](/start/core/) is the full version. * The animated turns return a promise that resolves when the page lands, and the getters became plain properties. Everything the old class exposed for its own internals is gone. * Event names are the same. The handler gets a typed event with the fields named (`e.page`, `e.orientation`, `e.state`) instead of `e.data`, and `on` hands back the unsubscribe function. * In React, the ref is the book itself, so `ref.current.pageFlip().flipNext()` is just `ref.current.flipNext()`. Pages don’t need refs any more. ## Options | page-flip | OpenPageFlip | Notes | | --------------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `width` | [`width`](/api/openpageflip/core/type-aliases/BookOptions/#width) | | | `height` | [`height`](/api/openpageflip/core/type-aliases/BookOptions/#height) | | | `size` | [`size`](/api/openpageflip/core/type-aliases/BookOptions/#size) | | | `minWidth` | [`minWidth`](/api/openpageflip/core/type-aliases/BookOptions/#minwidth) | | | `maxWidth` | [`maxWidth`](/api/openpageflip/core/type-aliases/BookOptions/#maxwidth) | | | `minHeight` | *removed* | Height follows width by the width:height ratio, so the width bounds are enough. | | `maxHeight` | *removed* | Same as minHeight. | | `startPage` | [`startPage`](/api/openpageflip/core/type-aliases/BookOptions/#startpage) | | | `flippingTime` | [`flipDuration`](/api/openpageflip/core/type-aliases/BookOptions/#flipduration) | | | `drawShadow` | [`shadows`](/api/openpageflip/core/type-aliases/BookOptions/#shadows) | | | `maxShadowOpacity` | [`shadowOpacity`](/api/openpageflip/core/type-aliases/BookOptions/#shadowopacity) | | | `showCover` | [`cover`](/api/openpageflip/core/type-aliases/BookOptions/#cover) | | | `usePortrait` | [`layout`](/api/openpageflip/core/type-aliases/BookOptions/#layout) | usePortrait: false is layout: "spread". The default "auto" picks by container width, and "single" is new. | | `autoSize` | [`autoSize`](/api/openpageflip/core/type-aliases/BookOptions/#autosize) | | | `startZIndex` | *removed* | Pages are stacked inside the container, so the book no longer needs a z-index base. | | `mobileScrollSupport` | *removed* | The book sets touch-action on itself, so the page scrolls and the book flips without a flag. | | `clickEventForward` | [`ignoreDragOn`](/api/openpageflip/core/type-aliases/BookOptions/#ignoredragon) | Clicks on links, buttons and form fields always reach them. ignoreDragOn is the selector that decides which elements never start a flip. | | `useMouseEvents` | [`drag`](/api/openpageflip/core/type-aliases/BookOptions/#drag) | Split three ways: drag, swipe and click each have their own option. | | `swipeDistance` | [`swipeDistance`](/api/openpageflip/core/type-aliases/BookOptions/#swipedistance) | | | `showPageCorners` | [`hoverCorners`](/api/openpageflip/core/type-aliases/BookOptions/#hovercorners) | | | `disableFlipByClick` | [`click`](/api/openpageflip/core/type-aliases/BookOptions/#click) | disableFlipByClick: true is click: "corners". "off" turns clicks off entirely. | ## Methods | PageFlip | OpenPageFlip | Notes | | ----------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `on()` | [`book.on()`](/api/openpageflip/core/type-aliases/Book/#on) | Returns the unsubscribe function, and takes an AbortSignal in its options. | | `off()` | [`book.off()`](/api/openpageflip/core/type-aliases/Book/#off) | Takes the listener as well as the event name; the old off() dropped every listener of that event. | | `destroy()` | [`book.destroy()`](/api/openpageflip/core/type-aliases/Book/#destroy) | | | `update()` | [`book.update()`](/api/openpageflip/core/type-aliases/Book/#update) | Resizes are watched for you; call this after other layout changes. | | `loadFromHTML()` | *removed* | The container's children are the pages when createBook runs, or pass the pages option. | | `updateFromHtml()` | [`book.setPages()`](/api/openpageflip/core/type-aliases/Book/#setpages) | | | `loadFromImages()` | *removed* | There is no image mode. Put an img element on each page and it renders like any other content. | | `updateFromImages()` | *removed* | Same as loadFromImages; use setPages with img pages. | | `clear()` | *removed* | destroy() restores the DOM; a new book starts from the pages you give it. | | `turnToPrevPage()` | [`book.turnPrev()`](/api/openpageflip/core/type-aliases/Book/#turnprev) | | | `turnToNextPage()` | [`book.turnNext()`](/api/openpageflip/core/type-aliases/Book/#turnnext) | | | `turnToPage()` | [`book.turnTo()`](/api/openpageflip/core/type-aliases/Book/#turnto) | | | `flipNext()` | [`book.flipNext()`](/api/openpageflip/core/type-aliases/Book/#flipnext) | Resolves when the turn lands, with false when there was nothing to turn to. | | `flipPrev()` | [`book.flipPrev()`](/api/openpageflip/core/type-aliases/Book/#flipprev) | Same promise as flipNext. | | `flip()` | [`book.flipTo()`](/api/openpageflip/core/type-aliases/Book/#flipto) | Same promise as flipNext. | | `getPageCount()` | [`book.pageCount`](/api/openpageflip/core/type-aliases/Book/#pagecount) | | | `getCurrentPageIndex()` | [`book.page`](/api/openpageflip/core/type-aliases/Book/#page) | | | `getOrientation()` | [`book.orientation`](/api/openpageflip/core/type-aliases/Book/#orientation) | | | `getState()` | [`book.state`](/api/openpageflip/core/type-aliases/Book/#state) | | | `getBoundsRect()` | [`book.rect`](/api/openpageflip/core/type-aliases/Book/#rect) | | | `getSettings()` | *removed* | Keep your own options object; the book does not hand it back. | | `getPage()` | *removed* | Internal in the old library. The page elements are the ones you passed in. | | `getRender()` | *removed* | Internal in the old library. | | `getFlipController()` | *removed* | Internal in the old library. | | `getUI()` | *removed* | Internal in the old library. | | `getPageCollection()` | *removed* | Internal in the old library. | | `updateState()` | *removed* | Internal in the old library; state is read-only from outside. | | `updatePageIndex()` | *removed* | Internal in the old library; use turnTo or flipTo. | | `updateOrientation()` | *removed* | Internal in the old library; the layout option decides orientation. | | `startUserTouch()` | *removed* | Internal in the old library; pointer input is handled by the book. | | `userMove()` | *removed* | Same as startUserTouch. | | `userStop()` | *removed* | Same as startUserTouch. | ## Events | page-flip | OpenPageFlip | Notes | | ------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------- | | `init` | [`init`](/api/openpageflip/core/type-aliases/BookEvents/#init) | e.data.page and e.data.mode are now e.page and e.orientation. | | `update` | [`update`](/api/openpageflip/core/type-aliases/BookEvents/#update) | Same shape as init. | | `flip` | [`flip`](/api/openpageflip/core/type-aliases/BookEvents/#flip) | e.data is now e.page. | | `changeState` | [`changeState`](/api/openpageflip/core/type-aliases/BookEvents/#changestate) | e.data is now e.state. | | `changeOrientation` | [`changeOrientation`](/api/openpageflip/core/type-aliases/BookEvents/#changeorientation) | e.data is now e.orientation. | ## React props | HTMLFlipBook | OpenPageFlip | Notes | | ---------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------- | | `onInit` | `onInit` | Handlers get the core event, see the events table. | | `onUpdate` | `onUpdate` | | | `onFlip` | `onFlip` | | | `onChangeState` | `onChangeState` | | | `onChangeOrientation` | `onChangeOrientation` | | | `className` | `className` | | | `style` | `style` | | | `children` | `children` | Children are the pages, as before, but they no longer need a ref or forwardRef. Wrap one in Page to make it hard. | | `ref` | `ref` | The ref is the Book itself, so ref.current.flipNext() replaces ref.current.pageFlip().flipNext(). | | `renderOnlyPageLengthChange` | *removed* | Pages added or removed by React are picked up after every render, and a changed option rebuilds the book on its own. | # React > Install the React package beside the core one and render a FlipBook. Its children are the pages. The React package needs React 19, and the core package installed next to it. * npm ```sh npm i @openpageflip/core @openpageflip/react ``` * pnpm ```sh pnpm add @openpageflip/core @openpageflip/react ``` * yarn ```sh yarn add @openpageflip/core @openpageflip/react ``` ## A book is a component [Section titled “A book is a component”](#a-book-is-a-component) Every direct child of `FlipBook` is a page. Wrap one in `Page` when it needs to be hard, or when you want a class or style on the page element itself. Nothing needs a ref or `forwardRef`. Here’s the component running, and the file that renders it: Quickstart.tsx ```tsx import "@openpageflip/core/styles.css"; import { type Book, FlipBook, Page } from "@openpageflip/react"; import { useRef } from "react"; export default function Quickstart() { const book = useRef(null); return ( <>

OpenPageFlip

A page-turn effect for React

Every child is a page

Wrap one in Page to make it hard, or to put a class or style on the page element itself.

No refs on pages

The book owns the page elements. Your components render inside them.

Props are options

Change one and the book is rebuilt on the same page. Children can change whenever.

Renders on the server

This page was server-rendered by the docs site and hydrated after.

The end

); } ``` ## What the props do [Section titled “What the props do”](#what-the-props-do) * Every core option is a prop. Change one and the book is rebuilt on the same page it was showing. Children can change whenever. * `ref` gives you the core `Book`, so `flipNext`, `flipTo`, `page`, `on` and the rest are right there. * `page` makes the book controlled, and `onFlip` tells you the first page of the spread that landed. The [controlled page example](/examples/controlled-page/) shows the two working together. * Each core event is a prop: `onInit`, `onUpdate`, `onFlip`, `onChangeState`, `onChangeOrientation`. * The component ships with `"use client"` and renders on the server. The demo above was server-rendered by this site and hydrated after. Every prop is in the [reference](/api/openpageflip/react/), generated from the source. # Controlled page > Drive a React FlipBook from state, and let onFlip keep that state honest when the reader turns pages by hand. When `page` changes, the book flips there. When the reader turns a page by hand, `onFlip` reports the first page of the spread that landed, so the state follows the book as much as the book follows the state. Try picking a chapter that sits on the right-hand side of a spread - the number settles on the spread’s first page, not the one you picked. Controlled.tsx ```tsx import "@openpageflip/core/styles.css"; import { FlipBook, Page } from "@openpageflip/react"; import { useState } from "react"; const chapters = [ "Cover", "Chapter one", "Chapter two", "Chapter three", "Chapter four", "The end", ]; export default function Controlled() { const [page, setPage] = useState(0); return ( <> {/* `page` drives the book; `onFlip` reports where it landed, so the two never disagree. */} setPage(e.page)} > {chapters.map((title, i) => { const isCover = i === 0 || i === chapters.length - 1; return (

{title}

); })}

Showing page {page + 1} of {chapters.length}

); } ``` # Playground > Every option as a control, the book's API as buttons, and the code that gives you exactly what you're looking at. Change anything and the book is rebuilt with the new options, on the page it was showing. The code at the bottom is generated from the controls, so it always matches the book above it - copy it into your app and you get exactly this book. # One page at a time > A book that stays one page wide on any screen and only turns from its corners, so the page itself is free for reading. Some books shouldn’t open into a spread - a phone-sized reader, say, where one page is the whole point. This one stays single however wide the container gets, and only the corners turn it, so clicking, selecting and following links in the middle of the page just work. On a mouse, hovering a corner lifts it so you can see where to grab. single-page.ts ```ts import "@openpageflip/core/styles.css"; import { type Book, createBook } from "@openpageflip/core"; export function mount(container: HTMLElement): Book { return createBook(container, { width: 360, // the page's ratio, and with maxWidth below, its largest size height: 480, size: "stretch", // shrinks to fit a phone maxWidth: 360, // but never grows past one phone-sized page layout: "single", // one page, however wide the container is click: "corners", // the middle of the page is left to whatever it holds }); } ```