One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Clipboard

Copy and paste text, HTML, images, and URLs, and hear when the clipboard changes

One.Clipboard reads and writes the system clipboard: plain text, HTML, images, and on iOS URLs. It is UIPasteboard on iOS, ClipboardManager on Android, and the async Clipboard API in the browser.

Every method returns a promise, and the same calls work on all three platforms. What a platform cannot do resolves an empty value instead of throwing, so shared code needs no platform checks.

Usage

import { Pressable, Text } from 'react-native'
import { One } from 'one'
function CopyLink({ url }: { url: string }) {
return (
<Pressable onPress={() => One.Clipboard.setString(url)}>
<Text>Copy link</Text>
</Pressable>
)
}

setString resolves true once the text is on the clipboard, or false on web when the browser refuses.

How it works

Native calls. Each method crosses to native code through a Nitro module. iOS reads and writes the general pasteboard on the main thread. Android uses the primary clip; reading it as text also turns a copied link or rich text into its text.

Text and HTML. setString(text, { inputFormat: 'html' }) writes one item with an HTML version and a plain version, so apps that paste rich text get the formatting and the rest get the text. getString({ preferredFormat: 'html' }) returns the HTML when there is some, and the plain text otherwise.

Images. setImage takes base64 image data and decodes it before writing, so bad data rejects. Android writes the image to a PNG file in the app’s cache and puts a content URI on the clipboard, which other apps read through One’s file provider. getImage encodes whatever image is on the clipboard as PNG or JPEG and returns it as a data URI with its pixel size, ready for an image source.

URLs. iOS keeps URLs as their own type, so setUrl, getUrl, and hasUrl work there. Android and web have no separate URL type: the three calls resolve nothing, null, and false.

Change events. The first addListener starts one native observer: UIPasteboard.changedNotification on iOS and a primary clip listener on Android. Each change calls every subscriber with the content types now on the clipboard. Removing the last subscription stops the observer.

API reference

MethodResolves
getString(options?)The text, or an empty string
setString(text, options?)Whether it was written
hasString()Whether there is text
getImage(options)The image, or null
setImage(base64)Once written
hasImage()Whether there is an image
getUrl()The URL, or null
setUrl(url)Once written
hasUrl()Whether there is a URL
addListener(listener)A subscription, at once
ClipboardGetImageOptionsTypeDescription
format (required)"png" | "jpeg"The encoding of the returned data.
"png" | "jpeg"The encoding of the returned data.
jpegQualitynumber = 1From 0 to 1. JPEG only.
number = 1From 0 to 1. JPEG only.
ClipboardImageTypeDescription
datastringA data URI, with its data:image prefix.
stringA data URI, with its data:image prefix.
size{ width: number; height: number }The size in pixels.
{ width: number; height: number }The size in pixels.
ClipboardEventTypeDescription
contentTypesClipboardContentType[]Any of plain-text, html, image, and url.
ClipboardContentType[]Any of plain-text, html, image, and url.

getString takes { preferredFormat } and setString takes { inputFormat }, each 'plainText' by default or 'html'.

Platform differences

iOSAndroidWeb
Text and HTMLYesYesYes
ImagesYesAs a cached PNG fileWhere allowed
URLsYesEmptyEmpty
Change eventsYesYesNever fire

Limits and gotchas

Examples

Offer paste only when there is text

import { useEffect, useState } from 'react'
import { Button } from 'react-native'
import { One } from 'one'
function PasteButton({ onPaste }: { onPaste: (text: string) => void }) {
const [hasText, setHasText] = useState(false)
useEffect(() => {
One.Clipboard.hasString().then(setHasText)
const subscription = One.Clipboard.addListener(({ contentTypes }) => {
setHasText(contentTypes.includes('plain-text'))
})
return () => subscription.remove()
}, [])
return (
<Button
title="Paste"
disabled={!hasText}
onPress={async () => onPaste(await One.Clipboard.getString())}
/>
)
}

The button follows the clipboard without reading it, so iOS shows its paste notice only when someone taps Paste.

Copy formatted text

import { One } from 'one'
async function copyQuote(author: string, quote: string) {
await One.Clipboard.setString(`<blockquote>${quote}</blockquote><p>${author}</p>`, { inputFormat: 'html' })
}

Mail and notes apps paste the block quote with its formatting. Apps that take only plain text get the same string as text.

Paste an image into a preview

import { useState } from 'react'
import { Button, Image, View } from 'react-native'
import { One } from 'one'
function PasteImage() {
const [uri, setUri] = useState<string | null>(null)
return (
<View>
<Button
title="Paste image"
onPress={async () => {
const image = await One.Clipboard.getImage({ format: 'jpeg', jpegQuality: 0.8 })
setUri(image?.data ?? null)
}}
/>
{uri && <Image source={{ uri }} style={{ width: 200, height: 200 }} />}
</View>
)
}

The data URI goes straight into source. JPEG at 0.8 keeps the string much smaller than PNG for photos.

Edit this page on GitHub.