Zoom Dropdown Menu

Available for free

A controlled zoom control for paginated or canvas-style editors. It pairs zoom-out / zoom-in stepper buttons with a percentage trigger whose popover offers a numeric input, preset levels, and an optional “Fit to page” action.

The component is fully headless and presentational — it holds no zoom state of its own and has no Tiptap editor dependency. You own the zoom value and pass it in through currentZoom / onZoomChange, so it works with any zoom implementation. All of the control logic lives in the useZoomDropdownMenu hook, making the built-in UI just one possible renderer.

Installation

Add the component via the Tiptap CLI:

npx @tiptap/cli@latest add zoom-dropdown-menu

Components

<ZoomDropdownMenu />

A prebuilt React component that renders the stepper buttons, the percentage trigger, and the popover with input, presets, and fit-to-page.

Usage

import { useState } from 'react'
import { ZoomDropdownMenu, type ZoomLevel } from '@/components/tiptap-ui/zoom-dropdown-menu'

export default function MyToolbar() {
  const [zoom, setZoom] = useState<ZoomLevel>(100)

  return (
    <ZoomDropdownMenu currentZoom={zoom} onZoomChange={setZoom} onFitToPage={() => setZoom(100)} />
  )
}

Props

The component also forwards any extra Button props (except type) to the percentage trigger.

NameTypeDefaultDescription
currentZoomnumber100The current zoom level (percentage). Controlled by you.
onZoomChange(zoom: number) => voidundefinedCalled with the next zoom level whenever the user changes it.
onFitToPage() => voidundefinedCalled when “Fit to page” is triggered. Omit to hide that action.
minnumber40Smallest selectable zoom level.
maxnumber200Largest selectable zoom level.
presetsreadonly number[]ZOOM_PRESETSPreset levels offered in the picker and used when stepping up/down.

Hooks

useZoomDropdownMenu()

A headless hook that derives everything a zoom UI needs. The consumer owns the value (currentZoom / onZoomChange); the hook computes the clamped value, min/max flags, the text field state with parse-and-commit, keyboard handling, preset stepping, and the fit-to-page passthrough.

Usage

function MyZoomControl(props) {
  const { zoom, zoomIn, zoomOut, isMinZoom, isMaxZoom } = useZoomDropdownMenu(props)

  return (
    <div>
      <button onClick={zoomOut} disabled={isMinZoom}>
        -
      </button>
      <span>{zoom}%</span>
      <button onClick={zoomIn} disabled={isMaxZoom}>
        +
      </button>
    </div>
  )
}

Props

NameTypeDefaultDescription
currentZoomnumber100The current zoom level (percentage). Controlled by you.
onZoomChange(zoom: number) => voidundefinedCalled with the next zoom level whenever it changes.
onFitToPage() => voidundefinedCalled when fit-to-page is triggered. Omit to hide it.
minnumber40Smallest selectable zoom level.
maxnumber200Largest selectable zoom level.
presetsreadonly number[]ZOOM_PRESETSPreset levels offered and used for stepping.

Return Values

NameTypeDescription
zoomnumberThe clamped current zoom level.
minnumberThe effective minimum zoom.
maxnumberThe effective maximum zoom.
presetsreadonly number[]The preset levels.
isMinZoombooleanWhether the current zoom is at (or below) min.
isMaxZoombooleanWhether the current zoom is at (or above) max.
canFitToPagebooleanWhether a fit-to-page action is available (onFitToPage provided).
zoomIn() => voidStep to the next preset above the current zoom.
zoomOut() => voidStep to the next preset below the current zoom.
setZoom(zoom: number) => voidApply an arbitrary level (clamped to min/max).
inputValuestringControlled value for a percentage text field.
setInputValue(value: string) => voidSetter for the text field value.
commitInput() => voidParse and apply the text field (reverts if invalid). Wire to onBlur.
handleInputKeyDown(e: React.KeyboardEvent<HTMLInputElement>) => voidEnter/Escape handling for the text field. Wire to onKeyDown.
fitToPage() => voidInvoke the consumer’s fit-to-page handler.

Utilities

The component module also exports a few pure helpers and constants for building custom zoom logic.

clampZoom(value, min?, max?)

Clamps a zoom value to the supported range, rounded to the nearest integer.

import { clampZoom } from '@/components/tiptap-ui/zoom-dropdown-menu'

clampZoom(87.6) // 88
clampZoom(999) // 200

parsePercent(input)

Parses a percentage string (e.g. "100%" or "100") to a number, or returns null for invalid input.

import { parsePercent } from '@/components/tiptap-ui/zoom-dropdown-menu'

parsePercent('125%') // 125
parsePercent('abc') // null

getNextPresetZoom(currentZoom, direction, presets?, min?, max?)

Returns the next preset level above or below the current zoom.

import { getNextPresetZoom } from '@/components/tiptap-ui/zoom-dropdown-menu'

getNextPresetZoom(100, 'up') // 125
getNextPresetZoom(100, 'down') // 90

Constants

  • ZOOM_MIN (40), ZOOM_MAX (200), ZOOM_DEFAULT (100)
  • ZOOM_PRESETS[40, 50, 75, 90, 100, 125, 150, 175, 200]
  • ZoomLevel — the exported number type alias used across the API

Requirements

Dependencies

  • react — the control is presentational and does not depend on Tiptap.

Referenced Components

  • button (primitive)
  • button-group (primitive)
  • card (primitive)
  • input (primitive)
  • popover (primitive)
  • separator (primitive)
  • fullscreen-icon (icon)
  • minus-icon (icon)
  • plus-icon (icon)