Page header and footer

Headers and footers allow you to display consistent content at the top and bottom of each page. The Pages extension supports static text, HTML markup, dynamic page numbering with placeholders, rich formatting via JSONContent, different headers/footers for first page or odd/even pages, and interactive editing through double-click in the header or footer area.

Note

Headers and footers are always editable via double-click. This behavior cannot be disabled.

Content types

The PagesHeaderFooter type accepts either an HTML string or Tiptap JSONContent for rich formatting:

type PagesHeaderFooter = string | JSONContent

HTML strings

You can use plain text or HTML markup for headers and footers:

Pages.configure({
  header: 'My Document Title',
  footer: '<strong>Confidential</strong> - Internal Use Only',
})

Template placeholders

Use {page} and {total} placeholders for dynamic page numbering. These work in both HTML strings and JSONContent, and are substituted at render time:

Pages.configure({
  header: 'Company Report',
  footer: 'Page {page} of {total}',
})

JSONContent

For rich formatting, use Tiptap's JSONContent structure:

Pages.configure({
  header: {
    type: 'doc',
    content: [
      {
        type: 'paragraph',
        content: [{ type: 'text', text: 'Document Title', marks: [{ type: 'bold' }] }],
      },
    ],
  },
})

Configuration vs commands

Use Pages.configure() when setting up the editor with initial default content. Use editor commands like setHeader() to update content dynamically at runtime.

Both approaches are valid for setting content:

Initial setup with configure:

Pages.configure({
  header: 'My Document Title',
  footer: 'Page {page} of {total}',
})

Runtime updates with commands:

// Update header after editor is initialized
editor.commands.setHeader('Updated Header')

Header configuration

Default header

The header option sets the content displayed in the header area of each page.

Initial configuration:

Pages.configure({
  header: '<strong>Tiptap</strong> – Document Header',
})

Editor command:

editor.commands.setHeader('My New Header')
// or with HTML
editor.commands.setHeader('<em>Updated</em> Header')

Header margin

The headerTopMargin option controls the distance from the page's top edge to where the header content starts (in pixels). If not specified, it defaults to 50% of the top margin.

Initial configuration:

Pages.configure({
  headerTopMargin: 30,
})

Editor command:

editor.commands.setHeaderTopMargin(40)

The footer option sets the content displayed in the footer area of each page.

Initial configuration:

Pages.configure({
  footer: 'Page {page} of {total}',
})

Editor command:

editor.commands.setFooter('Powered by Tiptap')
// or with placeholders
editor.commands.setFooter('{page}/{total}')

The footerBottomMargin option controls the distance from the footer content bottom to the page's bottom edge (in pixels). If not specified, it defaults to 50% of the bottom margin.

Initial configuration:

Pages.configure({
  footerBottomMargin: 30,
})

Editor command:

editor.commands.setFooterBottomMargin(40)

Different first page

Enable a different header and/or footer for the first page, similar to Microsoft Word's "Different First Page" option.

Note

The setDifferentFirstPage() command enables both different first page header and footer together, matching Microsoft Word's behavior.

Configuration

Pages.configure({
  header: 'Default Header',
  footer: 'Page {page}',
  differentFirstPage: true,
  headerFirstPage: 'Title Page',
  footerFirstPage: '', // No footer on first page
})

Commands

// Enable different first page (affects both header and footer)
editor.commands.setDifferentFirstPage(true)

// Set first page header and footer content
editor.commands.setHeaderFirstPage('Welcome to My Document')
editor.commands.setFooterFirstPage('')

Different odd and even pages

Enable different headers and/or footers for odd-numbered pages (1, 3, 5...) and even-numbered pages (2, 4, 6...), similar to Microsoft Word's "Different Odd & Even Pages" option.

Note

The setDifferentOddEven() command enables both different odd/even headers and footers together, matching Microsoft Word's behavior.

Configuration

Pages.configure({
  differentOddEven: true,
  headerOdd: 'Chapter Title – Odd Page',
  headerEven: 'Book Title – Even Page',
  footerOdd: 'Page {page}',
  footerEven: 'Page {page}',
})

Commands

// Enable different odd/even (affects both header and footer)
editor.commands.setDifferentOddEven(true)

// Set odd and even page headers
editor.commands.setHeaderOdd('Odd Page Header')
editor.commands.setHeaderEven('Even Page Header')

// Set odd and even page footers
editor.commands.setFooterOdd('Page {page}')
editor.commands.setFooterEven('Page {page}')

Combining first page and odd/even pages

When both differentFirstPage and differentOddEven are enabled, the first page header/footer takes precedence over odd/even settings for page 1.

Precedence order:

  1. First page (if differentFirstPage is enabled) - applies to page 1
  2. Odd/even (if differentOddEven is enabled) - applies to pages 2+
  3. Default header/footer - fallback
Pages.configure({
  header: 'Default Header', // Fallback
  differentFirstPage: true,
  headerFirstPage: 'Title Page', // Page 1
  differentOddEven: true,
  headerOdd: 'Odd Page Header', // Pages 3, 5, 7...
  headerEven: 'Even Page Header', // Pages 2, 4, 6...
})

Editable headers and footers

Users can edit headers and footers directly by double-clicking on them. This opens a fully featured Tiptap editor that allows rich text editing.

Custom extensions

By default, the header/footer editor uses StarterKit. You can provide custom extensions to enable additional features like images, tables, or text alignment:

import { Extension } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
import TextAlign from '@tiptap/extension-text-align'

Pages.configure({
  header: 'My Header',
  headerFooterExtensions: [
    StarterKit,
    Image,
    Table.configure({
      resizable: true,
    }),
    TableRow,
    TableCell,
    TableHeader,
    TextAlign.configure({
      types: ['heading', 'paragraph'],
    }),
  ],
})

Any Tiptap extension can be added to the header/footer editors through the headerFooterExtensions option.

Active editor state

When a user double-clicks a header or footer to edit it, the extension exposes the active editor through storage. This allows you to build custom toolbars that work with the header/footer editor.

Storage properties:

  • activeEditor – The Tiptap Editor instance for the currently open header or footer editor (or null)
  • activeEditorType'header', 'footer', or null
  • activePageNumber – The page number being edited (or null)

Building a custom toolbar

Here's an example of a React component that provides a unified toolbar for both the main editor and header/footer editors:

import { useEditor, useEditorState, EditorContent } from '@tiptap/react'
import { useEffect, useState } from 'react'

function DocumentEditor() {
  const [headerFooterEditor, setHeaderFooterEditor] = useState(null)
  const [activeEditorType, setActiveEditorType] = useState(null)

  const editor = useEditor({
    extensions: [
      StarterKit,
      Pages.configure({
        header: 'Document Header',
        footer: 'Page {page}',
      }),
    ],
  })

  // Listen for changes to active header/footer editor
  useEffect(() => {
    if (!editor) return

    const handleUpdate = () => {
      const { activeEditor, activeEditorType } = editor.storage.pages
      setHeaderFooterEditor(activeEditor)
      setActiveEditorType(activeEditorType)
    }

    editor.on('update', handleUpdate)
    return () => editor.off('update', handleUpdate)
  }, [editor])

  // Use the active editor (header/footer or main)
  const activeEditor = activeEditorType ? headerFooterEditor : editor

  const { isBoldActive } = useEditorState({
    editor: activeEditor,
    selector: ({ editor: e }) => ({
      isBoldActive: e?.isActive('bold') ?? false,
    }),
  })

  return (
    <div>
      <div className="toolbar">
        <span>Editing: {activeEditorType || 'Document'}</span>
        <button
          className={isBoldActive ? 'is-active' : ''}
          onClick={() => activeEditor?.chain().focus().toggleBold().run()}
        >
          Bold
        </button>
      </div>
      <EditorContent editor={editor} />
    </div>
  )
}

After users edit headers or footers through the headers or footers Tiptap editors, the content is stored in the extension's storage. This is useful for saving the document or exporting to formats like DOCX.

HTML content

Access the edited HTML content:

// Default header/footer
const headerHTML = editor.storage.pages.headerHTML
const footerHTML = editor.storage.pages.footerHTML

// First page
const headerFirstPageHTML = editor.storage.pages.headerFirstPageHTML
const footerFirstPageHTML = editor.storage.pages.footerFirstPageHTML

// Odd/even pages
const headerOddHTML = editor.storage.pages.headerOddHTML
const headerEvenHTML = editor.storage.pages.headerEvenHTML
const footerOddHTML = editor.storage.pages.footerOddHTML
const footerEvenHTML = editor.storage.pages.footerEvenHTML

Note

These storage values are null until the user has edited the header/footer through the corresponding header/footer editor. Before editing, the original template from configuration is used.

JSON content for DOCX export

For DOCX export, use the JSON versions which provide structured Tiptap document format:

// Default header/footer
const headerJSON = editor.storage.pages.headerJSON
const footerJSON = editor.storage.pages.footerJSON

// First page
const headerFirstPageJSON = editor.storage.pages.headerFirstPageJSON
const footerFirstPageJSON = editor.storage.pages.footerFirstPageJSON

// Odd/even pages
const headerOddJSON = editor.storage.pages.headerOddJSON
const headerEvenJSON = editor.storage.pages.headerEvenJSON
const footerOddJSON = editor.storage.pages.footerOddJSON
const footerEvenJSON = editor.storage.pages.footerEvenJSON

Note

These storage values are null until the user has edited the header/footer through the corresponding header/footer editor. Before editing, the original template from configuration is used.

Persisting header and footer content is your responsibility. Retrieve the content from storage when saving your document and restore it when loading.

Saving all header and footer content:

function getHeaderFooterContent(editor) {
  const { pages } = editor.storage

  return {
    // Default header/footer
    headerHTML: pages.headerHTML,
    headerJSON: pages.headerJSON,
    footerHTML: pages.footerHTML,
    footerJSON: pages.footerJSON,

    // First page
    headerFirstPageHTML: pages.headerFirstPageHTML,
    headerFirstPageJSON: pages.headerFirstPageJSON,
    footerFirstPageHTML: pages.footerFirstPageHTML,
    footerFirstPageJSON: pages.footerFirstPageJSON,

    // Odd/even pages
    headerOddHTML: pages.headerOddHTML,
    headerOddJSON: pages.headerOddJSON,
    headerEvenHTML: pages.headerEvenHTML,
    headerEvenJSON: pages.headerEvenJSON,
    footerOddHTML: pages.footerOddHTML,
    footerOddJSON: pages.footerOddJSON,
    footerEvenHTML: pages.footerEvenHTML,
    footerEvenJSON: pages.footerEvenJSON,
  }
}

// Save to your backend
const headerFooterData = getHeaderFooterContent(editor)
await saveDocument({ content: editor.getJSON(), headerFooter: headerFooterData })

Programmatically opening and closing editors

You can open and close header and footer editors programmatically. This is useful when integrating with custom UI elements, building navigation interfaces, or responding to user actions.

Opening editors

Use the openHeaderEditor and openFooterEditor commands to programmatically open the editor for a specific page:

// Open header editor for page 1
editor.commands.openHeaderEditor({ pageNumber: 1 })

// Open footer editor for page 3
editor.commands.openFooterEditor({ pageNumber: 3 })

These commands return true if the editor was opened successfully, or false if the specified page doesn't exist.

Note

Opening an editor automatically closes any previously open header or footer editor. The main document editor becomes non-editable while the header or footer editor is open.

Use cases:

  • Building a page navigation UI that lets users jump directly to editing a specific page's header or footer
  • Creating a "Go to header/footer" button in your toolbar
  • Implementing keyboard shortcuts to open headers or footers
  • Programmatically opening editors in response to external events

Example: Page navigation component

function PageNavigation({ editor }) {
  const pageCount = editor.storage.pages.getPageCount?.() ?? 1

  return (
    <div>
      {Array.from({ length: pageCount }, (_, i) => (
        <div key={i + 1}>
          <span>Page {i + 1}</span>
          <button onClick={() => editor.commands.openHeaderEditor({ pageNumber: i + 1 })}>
            Edit Header
          </button>
          <button onClick={() => editor.commands.openFooterEditor({ pageNumber: i + 1 })}>
            Edit Footer
          </button>
        </div>
      ))}
    </div>
  )
}

Closing editors

Use the close commands to dismiss the header or footer editor:

// Close whichever editor is currently open
editor.commands.closeHeaderFooterEditors()

// Close specific editors
editor.commands.closeHeaderEditor()
editor.commands.closeFooterEditor()

Preventing editor close on double-click

By default, double-clicking outside an open header or footer editor closes it. You can prevent this behavior using callback options, which is useful when you have toolbar buttons or other UI elements that users might double-click while editing.

Configuration

Three callback options are available:

  • onDblClickHeaderFooterPreventClose - Applies to both headers and footers (fallback)
  • onDblClickHeaderPreventClose - Applies only to headers (takes priority over the fallback)
  • onDblClickFooterPreventClose - Applies only to footers (takes priority over the fallback)

Each callback receives the MouseEvent and returns true to prevent closing or false to allow closing.

Example: Prevent closing when clicking a toolbar

Pages.configure({
  header: 'My Header',
  footer: 'Page {page}',
  onDblClickHeaderFooterPreventClose: (event) => {
    // Keep editor open if clicking inside the toolbar
    const toolbar = document.querySelector('.my-toolbar')
    return toolbar?.contains(event.target)
  },
})

Example: Different behavior for headers and footers

Pages.configure({
  header: 'My Header',
  footer: 'Page {page}',
  // Never close header editor on double-click outside
  onDblClickHeaderPreventClose: () => true,
  // Use default behavior for footer (close on double-click outside)
  onDblClickFooterPreventClose: () => false,
})

Accent colors

Customize the visual appearance of the header and footer editor overlays using accent colors. The accent color affects three elements:

  • Caret color - The text cursor in the editor
  • Toolbar border - The top border (header) or bottom border (footer) of the editing toolbar
  • Label background - The "Header" or "Footer" label background

Configuration

Set accent colors when configuring the extension:

Pages.configure({
  header: 'My Header',
  footer: 'Page {page}',
  // Set the same color for both header and footer overlays
  accentColor: '#3b82f6',
})

You can also set different colors for header and footer overlays:

Pages.configure({
  header: 'My Header',
  footer: 'Page {page}',
  headerAccentColor: '#3b82f6', // Blue for headers
  footerAccentColor: '#10b981', // Green for footers
})

Runtime updates

Change accent colors dynamically using commands:

// Update both header and footer overlay colors
editor.commands.setAccentColor('#8b5cf6')

// Update header overlay color only
editor.commands.setHeaderAccentColor('#3b82f6')

// Update footer overlay color only
editor.commands.setFooterAccentColor('#ef4444')

CSS color values

Accent color options accept any valid CSS color value including hex (#3b82f6), rgb (rgb(59, 130, 246)), hsl (hsl(217, 91%, 60%)), oklch, or CSS variables (var(--primary-color)).

Limitations

Editing cannot be disabled

The double-click editing behavior is built into the extension and cannot be disabled. All users with access to the editor can edit headers and footers.

The header/footer editor uses built-in styles. However, you can customize the accent color using the accentColor, headerAccentColor, and footerAccentColor options.

Collaboration

Collaborative editing is not supported for headers and footers. The header and footer editors are separate Tiptap instances that are independent from the main document's collaboration session.

Complete options reference

OptionTypeDefaultDescription
headerstring | JSONContent''Default header content
footerstring | JSONContent''Default footer content
headerTopMarginnumber50% of top marginDistance from page top to header content
footerBottomMarginnumber50% of bottom marginDistance from footer content to page bottom
differentFirstPagebooleanfalseEnable different first page header and footer
headerFirstPagePagesHeaderFooter''First page header content
footerFirstPagePagesHeaderFooter''First page footer content
differentOddEvenbooleanfalseEnable different odd/even headers and footers
headerOddPagesHeaderFooter''Odd pages header content
headerEvenPagesHeaderFooter''Even pages header content
footerOddPagesHeaderFooter''Odd pages footer content
footerEvenPagesHeaderFooter''Even pages footer content
headerFooterExtensionsExtension[]StarterKitExtensions for header/footer editor
onDblClickHeaderFooterPreventClose(event: MouseEvent) => booleanundefinedCallback to prevent closing header/footer editors on double-click outside
onDblClickHeaderPreventClose(event: MouseEvent) => booleanundefinedCallback to prevent closing header editor on double-click outside
onDblClickFooterPreventClose(event: MouseEvent) => booleanundefinedCallback to prevent closing footer editor on double-click outside
accentColorstring'#6366f1'Accent color for header/footer editor overlays
headerAccentColorstringaccentColor valueAccent color for header editor overlay
footerAccentColorstringaccentColor valueAccent color for footer editor overlay

Commands reference

CommandParametersDescription
setHeaderheader: PagesHeaderFooterSet default header content
setFooterfooter: PagesHeaderFooterSet default footer content
setHeaderTopMarginmargin: numberSet header top margin (pixels)
setFooterBottomMarginmargin: numberSet footer bottom margin (pixels)
setDifferentFirstPageenabled: booleanToggle different first page (header + footer)
setHeaderFirstPageheader: PagesHeaderFooterSet first page header content
setFooterFirstPagefooter: PagesHeaderFooterSet first page footer content
setDifferentOddEvenenabled: booleanToggle different odd/even pages (header + footer)
setHeaderOddheader: PagesHeaderFooterSet odd pages header content
setHeaderEvenheader: PagesHeaderFooterSet even pages header content
setFooterOddfooter: PagesHeaderFooterSet odd pages footer content
setFooterEvenfooter: PagesHeaderFooterSet even pages footer content
openHeaderEditor{ pageNumber: number }Open header editor for a specific page (1-indexed)
openFooterEditor{ pageNumber: number }Open footer editor for a specific page (1-indexed)
closeHeaderFooterEditorsnoneClose whichever header/footer editor is open
closeHeaderEditornoneClose the header editor if open
closeFooterEditornoneClose the footer editor if open
setAccentColorcolor: stringSet accent color for both editor overlays
setHeaderAccentColorcolor: stringSet accent color for header editor overlay
setFooterAccentColorcolor: stringSet accent color for footer editor overlay