---
title: "Render custom overlays on a PDF in React Native | Nutrient SDK"
canonical_url: "https://www.nutrient.io/guides/react-native/user-interface/custom-overlays/"
md_url: "https://www.nutrient.io/guides/react-native/user-interface/custom-overlays.md"
last_updated: "2026-08-27T00:00:00.000Z"
description: "Learn how to anchor your own React Native components to PDF coordinates with NutrientOverlay and NutrientOverlayItem, keeping them in sync with pan and zoom."
---

# Render custom overlays on a PDF in React Native

Nutrient React Native SDK can render your own React Native components on top of a document, anchored to a position on a page rather than to the screen. Because the anchor is a PDF coordinate, the component stays on the same spot on the page as the user pans and zooms.

This is useful for things the annotation system doesn’t cover: a badge next to a clause, a button beside a field, a marker on a floor plan, or any interactive React Native view that has to line up with page content.

Use `NutrientOverlay` for the layer and `NutrientOverlayItem` for each anchored element. Both are available from Nutrient React Native SDK 4.5 onward.

## Add an overlay

`NutrientOverlay` is a transparent layer that tracks a [`NutrientView`](https://www.nutrient.io/api/react-native/NutrientView.html). Render it as a sibling of the view, inside a container the view fills, so the two share the same frame origin. Pass the same ref to both:

```typescript

import React, { useRef } from 'react';
import { Text, View } from 'react-native';
import NutrientView, {
	NutrientOverlay,
	NutrientOverlayItem,
} from '@nutrient-sdk/react-native';

export const CustomOverlays = () => {
	const viewRef = useRef<NutrientView | null>(null);

	return (
		<View style={{ flex: 1 }}>
			<NutrientView
				ref={viewRef}
				document={DOCUMENT}
				style={{ flex: 1 }}
			/>
			<NutrientOverlay viewRef={viewRef}>
				<NutrientOverlayItem
					pageIndex={0}
					position={{ x: 90, y: 690 }}>
					<View style={{ backgroundColor: '#1565c0', padding: 6 }}>

						<Text style={{ color: 'white' }}>Review this</Text>
					</View>
				</NutrientOverlayItem>
			</NutrientOverlay>
		</View>
	);
};

```

`position` is in PDF coordinates on the page given by `pageIndex`, and it positions the item’s top-left corner. PDF coordinates have their origin at the bottom-left of the page and are measured in PDF points, so a larger `y` is further up the page — the opposite of React Native’s layout coordinates. `pageIndex` starts at `0`.

An item renders while the page it’s anchored to is visible. In a continuous scroll layout, more than one page can be visible at a time, and items on each of those pages render. Items anchored to pages that are offscreen aren’t rendered, so you can declare items for the whole document and let the overlay render the ones in view.

## Keep a constant size while zooming

By default, an item scales with the document, so it grows and shrinks with the page content it sits next to — the right behavior for something that annotates the page.

Set `disableAutoZoom` when the element should instead keep a constant size onscreen, which is usually what you want for a control the user taps:

```typescript

<NutrientOverlayItem
	pageIndex={0}
	position={{ x: 300, y: 430 }}
	disableAutoZoom>
	<MyButton />
</NutrientOverlayItem>

```

The item stays anchored to `position` either way; only its rendered size changes.

## React to items becoming visible

`onAppear` and `onDisappear` fire as the anchored page enters and leaves the viewport. Use them to start and stop work that is only worth doing while the element is onscreen, such as fetching the data it displays:

```typescript

<NutrientOverlayItem
	pageIndex={2}
	position={{ x: 100, y: 500 }}
	onAppear={() => startPolling()}
	onDisappear={() => stopPolling()}>
	<LiveStatusBadge />
</NutrientOverlayItem>

```

## Place an item from a tap

To let users place an element themselves, convert the tap location into a PDF coordinate with [`convertPointToPage`](https://www.nutrient.io/api/react-native/NutrientView.html#.convertPointToPage) and store the result. It returns the point on the given page, so pass the page the user is looking at:

```typescript

const onTap = async (event: any) => {
	const { locationX, locationY } = event.nativeEvent;
	const point = await viewRef.current?.convertPointToPage(pageIndex, {
		x: locationX,
		y: locationY,
	});
	if (point!= null) {
		setPins(current => [...current, { pageIndex, position: point }]);
	}
};

```

[`convertPointToScreen`](https://www.nutrient.io/api/react-native/NutrientView.html#.convertPointToScreen) and [`convertRectToScreen`](https://www.nutrient.io/api/react-native/NutrientView.html#.convertRectToScreen) convert the other way — from PDF coordinates to screen coordinates — and [`convertRectToPage`](https://www.nutrient.io/api/react-native/NutrientView.html#.convertRectToPage) converts a screen rect to a PDF rect. Use these when you need to position something yourself rather than through an overlay item.

## Read the current viewport

The overlay positions its items from the [`documentViewportChanged`](https://www.nutrient.io/api/react-native/NotificationCenter.html#.DocumentEvent) Notification Center event, which reports the viewer’s zoom scale, the visible region of the page, the content offset, and the page size. Subscribe to it directly when you need the same information — to show the current zoom, for example, or to lay out the UI outside the overlay:

```typescript

import { NotificationCenter } from '@nutrient-sdk/react-native';

const subscription = viewRef.current?.getNotificationCenter()?.subscribe(
		NotificationCenter.DocumentEvent.VIEWPORT_CHANGED,
		payload => {
			setZoom(payload.zoomScale);
		},
	);

// Later, to stop listening:
subscription?.remove();

```

[`getViewportState`](https://www.nutrient.io/api/react-native/NutrientView.html#.getViewportState) returns the same payload on demand for a one-off read rather than a subscription.

Overlay items are positioned from JavaScript, so they can visibly lag behind the document during a fast fling or pinch. Keep the anchored components light, and prefer `disableAutoZoom` for elements whose size doesn’t need to track the page.

For a complete, runnable version of everything above — including placing pins from a tap and reading the page and zoom from the viewport event — see the [`CustomOverlays.tsx` example](https://github.com/PSPDFKit/react-native/blob/master/samples/Catalog/examples/CustomOverlays.tsx) in the [Catalog example project](https://www.nutrient.io/guides/react-native/prebuilt-solutions/example-projects.md#pspdfkit-catalog).
---

## Related pages

- [Customizing our PDF viewer in React Native](/guides/react-native/user-interface.md)
- [Show the PSPDFKitView close button](/guides/react-native/user-interface/close-button.md)
- [Configuring PSPDFKitView properties](/guides/react-native/user-interface/configuration.md)
- [Localization: Change languages in our React Native PDF viewer](/guides/react-native/user-interface/localization.md)
- [Customizing menus on React Native](/guides/react-native/user-interface/menus.md)
- [NutrientView React Native UI component](/guides/react-native/user-interface/pspdfkitview.md)
- [Show or hide the UI in our React Native viewer](/guides/react-native/user-interface/ui-visibility.md)

