Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/johnfactotum/foliate-js/llms.txt

Use this file to discover all available pages before exploring further.

Renderers are the display layer of foliate-js. They are custom HTML elements (web components) that know how to take a book object, load its sections into iframes, paginate or scroll the content, and report back the current reading position. There are two renderers: foliate-paginator for reflowable text (most EPUBs, MOBI, FictionBook), and foliate-fxl for fixed-layout content (pre-paginated EPUBs, CBZ comic books, and PDFs). You rarely need to instantiate them directly — view.js selects and manages the correct renderer automatically based on book.rendition?.layout.

The <foliate-view> element

view.js exports a <foliate-view> custom element that acts as the main entry point for the library. It selects the appropriate renderer, wires up progress tracking and annotation overlays, and re-exports renderer events.
import './foliate-js/view.js'

const view = document.createElement('foliate-view')
document.body.append(view)

view.addEventListener('relocate', e => {
  const { cfi, fraction, tocItem } = e.detail
  console.log('Current CFI:', cfi)
})

await view.open('example.epub')
When view.open() is called with a book whose rendition.layout is "pre-paginated", it imports fixed-layout.js and creates a foliate-fxl element. Otherwise it imports paginator.js and creates a foliate-paginator element.
Both renderer modules register their custom elements as a side effect of being imported. You do not need to call customElements.define() yourself.

The renderer interface

Both renderers implement the same core interface:
open(book)
(book: object) => void
Accepts a book object (see The book interface) and prepares the renderer to display it. In foliate-paginator, this stores the sections array and attaches a CSS transform listener to book.transformTarget for stylesheet processing.
goTo(destination)
(destination: { index: number, anchor?: (doc: Document) => Element | Range }) => Promise<void>
Navigates to a destination object with the same shape returned by book.resolveHref() or book.resolveCFI(). The anchor function, if provided, is called with the loaded Document and returns the element or range to scroll into view.
prev(distance?)
(distance?: number) => Promise<void>
Goes to the previous page or section. In scrolled mode, distance specifies the pixel amount to scroll; in paginated mode it is ignored and the renderer moves back by one page.
next(distance?)
(distance?: number) => Promise<void>
Goes to the next page or section. Behavior for distance mirrors .prev().
getContents()
() => Array<{ doc: Document, index: number, overlayer: object }>
Returns an array of the currently rendered frames. In foliate-paginator this is always at most one item (the current section). In foliate-fxl, spread views can have two items (left and right pages). Each item has:
  • doc — the Document inside the iframe
  • index — the section index
  • overlayer — the Overlayer instance managing annotation highlights for this frame

Renderer events

Both renderers dispatch custom events that bubble up through <foliate-view>:
load
CustomEvent
Fired when a section finishes loading. event.detail contains:
relocate
CustomEvent
Fired whenever the visible location changes — on page turn, scroll, or section load. event.detail contains:When accessed through <foliate-view>, the relocate event detail is enriched with cfi, tocItem, pageItem, and overall book fraction from progress.js.
create-overlayer
CustomEvent
Fired when a new iframe is ready to receive an annotation overlay. event.detail contains:

CSS customization

The filter part

Both renderers expose a CSS part named filter on the iframe element. This lets you apply visual effects — such as color inversion for dark mode — to the book content without affecting overlaid elements like annotation highlights:
foliate-view::part(filter) {
    filter: invert(1) hue-rotate(180deg);
}
Because the filter is applied to the iframe itself rather than the document body, overlayers sit outside the filter and remain unaffected.

The paginator (foliate-paginator)

foliate-paginator handles reflowable content using CSS multi-column layout. It supports both paginated and scrolled display modes and can switch between them without reloading the section.

Layout attributes

Configure the paginator by calling .setAttribute() on the element. There is no JavaScript property API for these settings.
Always use .setAttribute() to set layout options. Direct property assignment (e.g., paginator.flow = 'scrolled') does not trigger the internal attributeChangedCallback.
AttributeTypeDefaultDescription
flow"paginated" | "scrolled""paginated"Display mode. "scrolled" enables continuous vertical (or horizontal for vertical writing) scrolling.
gapCSS <percentage>7%Column gap size relative to the page width.
marginCSS px length48pxHeight reserved for the header and footer regions.
max-inline-sizeCSS px length720pxMaximum column width in paginated mode, or maximum text width in scrolled mode.
max-block-sizeCSS px length1440pxMaximum height of the content area.
max-column-countinteger2Maximum number of columns. Has no effect in scrolled mode or when the element is in portrait orientation.
animatedboolean attributeabsentWhen present, enables a sliding transition effect when turning pages.
const view = document.querySelector('foliate-view')

// Switch to scrolled mode
view.renderer.setAttribute('flow', 'scrolled')

// Configure layout
view.renderer.setAttribute('margin', '64px')
view.renderer.setAttribute('gap', '5%')
view.renderer.setAttribute('max-inline-size', '680px')
view.renderer.setAttribute('max-column-count', '1')

// Enable animated page turns
view.renderer.setAttribute('animated', '')

Running heads and footers

In paginated (non-scrolled) mode, the paginator creates header and footer regions for each column. These are accessible via .heads and .feet on the renderer instance:
view.addEventListener('relocate', e => {
  const { tocItem } = e.detail

  // heads and feet are arrays — one element per column
  if (view.renderer.heads) {
    for (const el of view.renderer.heads) {
      el.textContent = tocItem?.label ?? ''
    }
  }
  if (view.renderer.feet) {
    for (const el of view.renderer.feet) {
      el.textContent = e.detail.location
        ? `${e.detail.location.current} / ${e.detail.location.total}`
        : ''
    }
  }
})
.heads and .feet are null when the paginator is in scrolled mode. Check before accessing them.
Style these regions using the ::part(head) and ::part(foot) pseudo-elements:
foliate-view::part(head) {
    padding-bottom: 4px;
    border-bottom: 1px solid graytext;
    font-size: 0.75em;
    opacity: 0.6;
}

foliate-view::part(foot) {
    padding-top: 4px;
    border-top: 1px solid graytext;
    font-size: 0.75em;
    opacity: 0.6;
}

The fixed-layout renderer (foliate-fxl)

foliate-fxl handles pre-paginated content where each page has fixed dimensions. It respects pageSpread properties on sections to assemble left/right spread pairs and scales pages to fit the available space.

Zoom

The fixed-layout renderer observes a zoom attribute:
// Fit the page to available width
view.renderer.setAttribute('zoom', 'fit-width')

// Fit the whole page (default)
view.renderer.setAttribute('zoom', 'fit-page')

// Explicit scale factor (1.0 = 100%)
view.renderer.setAttribute('zoom', '1.5')

Spread behavior

The renderer reads book.rendition.spread to determine how to pair pages into spreads:
  • "none" — each section is displayed centered, never paired
  • "both" — always show a two-page spread
  • "portrait" — show spreads in landscape orientation, single pages in portrait
When the element’s height exceeds its width (portrait), individual pages from a spread are shown one at a time and .prev() / .next() steps between the left and right pages before moving to the next spread.

Example: basic setup

import './foliate-js/view.js'

const view = document.createElement('foliate-view')
Object.assign(view.style, { width: '100%', height: '100vh' })
document.body.append(view)

// Configure before or after opening — attributes are applied on render
view.addEventListener('load', () => {
  if (view.renderer.tagName === 'FOLIATE-PAGINATOR') {
    view.renderer.setAttribute('margin', '48px')
    view.renderer.setAttribute('gap', '6%')
    view.renderer.setAttribute('max-inline-size', '720px')
    view.renderer.setAttribute('animated', '')
  }
})

view.addEventListener('relocate', e => {
  document.title = e.detail.tocItem?.label ?? ''
})

await view.open('book.epub')
await view.goTo(savedCFI)

Build docs developers (and LLMs) love