> Markdown version of [Clipboard](https://vaadin.com/docs/next/flow/browser-apis/clipboard-api). Section index: [llms.txt](https://vaadin.com/docs/next/flow/llms.txt)

# Clipboard (since V25.2)

The `Clipboard` API lets server-side Java code copy text, HTML, and images to the user’s clipboard through the [browser Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API). Actions are bound to a clickable component and run during the DOM event that fires the underlying click, so the browser sees a fresh user gesture and allows the write. The same click binding can also [read the clipboard](#reading-the-clipboard), and separate listeners react to browser paste events — including [pasted files](#handling-pasted-files) — without any click binding.

> **Note:** The Clipboard API requires a secure context (HTTPS), except on `localhost` during development. Browsers also require the `write` call to happen inside a short-lived user gesture (click, key press, …) — writes triggered outside of a gesture, for example from a background thread or a timer, will fail with a `NotAllowedError`.

## <a id="how-the-binding-works"></a>How the Click Binding Works

`Clipboard.onClick(component)` does not register a click listener. It *arms* a clipboard action that the browser runs synchronously inside the click event, while the user gesture is active — a browser requirement for clipboard writes. The action must therefore be assigned during the same server request that creates the binding.

Chain the action method directly onto `onClick(…​)`. The `write*` and `read*` methods return `void`, so there is nothing to keep:

```java
Button copy = new Button("Copy");
Clipboard.onClick(copy).writeText("Hello, world!");
```

Assigning the action later — for example storing the `ClipboardBinding` and calling `writeText()` from a separate `addClickListener` callback — arms the binding one request too late, after the gesture window has closed. A binding left with no action does nothing on the client, so the framework throws an `IllegalStateException` when the response is built.

The same arming model applies to the [Fullscreen](https://vaadin.com/docs/next/flow/browser-apis/fullscreen-api.md) and [Web Share](https://vaadin.com/docs/next/flow/browser-apis/web-share-api.md) APIs, which share the user-gesture requirement.

## <a id="copying-text"></a>Copying Text

`Clipboard.onClick(component)` returns a `ClipboardBinding` — a fluent surface that attaches a clipboard action to the component’s `click` event. The typical "Copy to clipboard" button is one line:

```java
Button copy = new Button("Copy");
Clipboard.onClick(copy).writeText("Hello, world!");
```

Every subsequent click of the button copies the literal string to the clipboard. The call returns immediately and does not subscribe to the result — see [Observing the Outcome](#observing-the-outcome) if you need to know whether the write succeeded.

> **Important:** A literal string is captured **when the action is armed**, not when the click fires. Building it from a value that changes — for example concatenating `LocalTime.now()` into the text — therefore copies a stale, build-time snapshot on every click, frozen at the moment the action was armed.
>
> To copy a value that changes, drive it through a component and pass the component (not a string) to `writeText()` — the value is then read on the client at click time, as shown next. Pre-arming has no equivalent for content that must be computed at the exact instant of the click; route such values through a field your server keeps up to date.

## <a id="copying-a-field-value"></a>Copying a Field Value

To copy the current value of an input field, pass the component to `writeText()`. The value is read on the client at the moment the click fires, so user edits are reflected without a server round-trip:

```java
TextField token = new TextField("API token");
Button copy = new Button("Copy");
Clipboard.onClick(copy).writeText(token);
```

Any component implementing `HasValue<?, String>` works — `TextField`, `TextArea`, `PasswordField`, `EmailField`, and so on.

## <a id="other-click-sources"></a>Other Click Sources

`Clipboard.onClick()` accepts any component implementing `ClickNotifier`, not just `Button`. A `ContextMenu` item is a common alternative — the user right-clicks a target and picks "Copy" from the menu:

```java
ContextMenu menu = new ContextMenu(target);
MenuItem copyItem = menu.addItem("Copy value");
Clipboard.onClick(copyItem).writeText(value,
        written -> Notification.show("Token copied"),
        error -> Notification.show("Copy failed: " + error.message()));
```

The menu item’s click counts as the user gesture, so the write is allowed. The same pattern works for icons, list items, or any custom component that fires a click event.

## <a id="copying-html"></a>Copying HTML

Use `writeHtml()` to put a `text/html` payload on the clipboard. This is what rich-text targets (word processors, mail composers) paste as formatted content:

```java
Button copy = new Button("Copy formatted");
Clipboard.onClick(copy).writeHtml("<b>Hello</b>, <i>world</i>!");
```

Most targets fall back to a plain-text representation when only `text/html` is present, but the fallback is browser-dependent. To control both representations explicitly, use the multi-format form below.

## <a id="copying-images"></a>Copying Images

Use `writeImage()` to copy an image to the clipboard:

```java
Image preview = new Image("images/chart.png", "Chart preview");
Clipboard.onClick(copy).writeImage(preview);
```

The image slot in the clipboard only accepts a component whose root element is an `<img>`. The source can be a same-origin image, a `data:` URL, or any cross-origin image served with matching CORS headers and `crossorigin="anonymous"` on the `<img>`.

When the image is rendered server-side — for example a dynamic chart that the user wants to paste into an email or slide deck — pass a `DownloadHandler` instead:

```java
Button copyChart = new Button("Copy chart");
Clipboard.onClick(copyChart).writeImage(DownloadHandler.fromInputStream(event -> {
    // Produce the PNG bytes for the chart. How you render the image is
    // application-specific -- here a service turns the report data into a PNG.
    byte[] png = chartRenderer.renderPng(report.currentData());
    return new DownloadResponse(new ByteArrayInputStream(png),
            "chart.png", "image/png", png.length);
}));
```

To include an image together with text or HTML, use `ClipboardContent.image()` in the multi-format form.

## <a id="multi-format-content"></a>Multi-Format Content

`ClipboardContent` packs several MIME types into a single clipboard item, so the paste target can pick the representation it understands. Set the `text/plain`, `text/html`, and `image/png` slots independently; at least one must be set.

```java
Image preview = new Image("images/chart.png", "Chart preview");
Button copy = new Button("Copy");
Clipboard.onClick(copy).write(ClipboardContent.create()
        .text("Hello, world!")
        .html("<b>Hello</b>, <i>world</i>!")
        .image(preview));
```

The `text()` setter also accepts a component, with the same client-side read semantics as `writeText(Component)`:

```java
TextField message = new TextField();
Button copy = new Button("Copy");
Clipboard.onClick(copy).write(ClipboardContent.create()
        .text(message)
        .html("<blockquote>" + message.getValue() + "</blockquote>"));
```

The HTML slot only accepts a literal string — there is no component overload. If the HTML depends on a field value, build it on the server before binding, or use the plain-text component overload alongside a static HTML literal.

## <a id="reading-the-clipboard"></a>Reading the Clipboard

The same `ClipboardBinding` that writes can also read. The `read*` methods read the clipboard via `navigator.clipboard.read()` when the bound click fires and deliver the result to a server-side callback. `readText()` delivers the `text/plain` content:

```java
TextField address = new TextField("Address");
Button paste = new Button("Paste");
Clipboard.onClick(paste).readText(
        text -> address.setValue(text != null ? text : ""),
        error -> Notification.show("Couldn't read: " + error.message()));
```

Both consumers are required. The payload consumer receives `null` when the clipboard is empty or has no representation of the requested type. `readHtml()` works the same way for the `text/html` representation.

To receive both textual representations at once, use `read()`. It delivers a `ClipboardPayload` record whose `text()` and `html()` accessors are each `null` when the corresponding MIME type is not present on the clipboard item:

```java
Clipboard.onClick(paste).read(
        payload -> {
            if (payload == null) {
                Notification.show("Clipboard is empty");
            } else if (payload.html() != null) {
                renderHtml(payload.html());
            } else {
                renderText(payload.text());
            }
        },
        error -> Notification.show("Couldn't read: " + error.message()));
```

> **Note:** Reading is more restricted than writing: in addition to the user gesture, the browser requires the user to grant the `clipboard-read` permission, typically through a prompt on the first read. If the permission is denied, the read is rejected with a `NotAllowedError`, which is delivered to the `onError` consumer — see [Handling Errors](#handling-errors).

## <a id="observing-the-outcome"></a>Observing the Outcome

All `write*` methods have an overload that takes `onCopied` and `onError` consumers. The write becomes observed: after the browser resolves or rejects the underlying promise, the framework invokes one of the consumers on the UI thread.

```java
Clipboard.onClick(copy).writeText(token,
        copied -> Notification.show("Copied " + copied),
        err -> Notification.show("Couldn't copy: " + err.message()));
```

`onCopied` receives the string that was copied — the `text/plain` value if present, otherwise the `text/html` value. This is useful when the input was a component value, since the exact string is only known on the client.

Both consumers are required in the observed form. To opt out of one side, pass an empty lambda:

```java
Clipboard.onClick(copy).writeText(token,
        copied -> { /* ignore */ },
        err -> log.warn("clipboard write failed: {}", err.message()));
```

## <a id="handling-errors"></a>Handling Errors

The `onError` consumer receives a `PromiseAction.Error` record with the browser’s rejection details:

- `name()`

  The DOM exception class name, typically `NotAllowedError` when the user has denied clipboard access or the write happened outside a user gesture. Switch on this when you need typed handling.

- `message()`

  The free-form description from the browser. Useful for logging and diagnostics; not meant for end users.

```java
Clipboard.onClick(copy).writeText(token,
        copied -> Notification.show("Copied"),
        err -> {
            String userMessage = switch (err.name()) {
                case "NotAllowedError" ->
                    "Clipboard access is blocked. Please allow it in your browser settings.";
                default ->
                    "Couldn't copy to clipboard.";
            };
            Notification.show(userMessage);
            log.warn("clipboard write failed: {} -- {}",
                    err.name(), err.message());
        });
```

The Clipboard API does not expose a permission state the same way other browser APIs do, so there is no separate availability signal — handle errors at the point of the write.

## <a id="user-gesture-requirement"></a>User Gesture Requirement

The Clipboard API requires every write to happen inside a fresh user gesture. A few consequences:

- Writes cannot be initiated from a background thread, a scheduled task, or a server push. The gesture must originate in the browser.

- Each click is one gesture. Chaining multiple `write*` calls on the same binding attaches multiple actions to the same click, but each one needs to succeed against the browser’s gesture budget.

- If the application performs a long server round-trip before the write call, the browser may consider the gesture stale and reject the write.

Reads bound to a click are subject to the same requirement — the click satisfies the gesture, but the `clipboard-read` permission is still checked separately.

## <a id="handling-paste-events"></a>Handling Paste Events

`Clipboard.onPaste(component, listener)` registers a server-side listener for the browser’s native `paste` event — the one the browser fires when the user presses `Ctrl+V` (`Cmd+V` on macOS) or picks *Paste* from a context menu. Unlike the write and read APIs, it needs no click binding — the listener is attached directly to the component’s element and is invoked on the UI thread once per paste gesture targeting the component, or any of its descendants, since `paste` bubbles. The listener receives a `PasteEvent` carrying the `text/plain` and `text/html` representations of the pasted content:

```java
Div pasteTarget = new Div();
pasteTarget.getElement().setAttribute("tabindex", "0");
add(pasteTarget);

Clipboard.onPaste(pasteTarget, event -> {
    if (event.hasHtml()) {
        renderHtml(event.getHtml());
    } else if (event.hasText()) {
        renderText(event.getText());
    }
});
```

The call returns a `Registration`; call `remove()` on it to detach the listener. The component does not need to be attached at registration time — the DOM listener is applied when the element is attached to a UI.

`PasteEvent` exposes the paste through these methods:

- `getText()`, `getHtml()`

  The `text/plain` and `text/html` content, or `null` when the paste did not include that representation. The browser returns an empty string both for an absent MIME type and for a pasted empty string; `PasteEvent` collapses both into `null`, so `hasText()` and `hasHtml()` are sufficient checks.

- `getTargetElement()`

  The closest Flow-tracked `Element` ancestor of the paste’s DOM target, resolved against the live DOM in the browser. For a paste inside a Vaadin web component this is typically the component’s host element. Use `Element.getComponent()` to look up the enclosing component. Returns `null` in the rare case that no Flow-tracked element encloses the target.

- `getSource()`

  The component the listener was registered on.

A few browser caveats apply:

- The browser only fires `paste` when the target element is focused at the moment the user invokes paste. Non-editable elements such as a plain `Div` must be made focusable, typically via `tabindex="0"`.

- On editable targets (`<input>`, `<textarea>`, `contenteditable` elements), the browser still performs its native paste insertion. The listener observes the paste; it does not replace it.

- On Safari, some plain-text pastes do not include a `text/html` representation even when Chromium would synthesize one, so `getHtml()` may be `null` on Safari for a paste that yields HTML on Chrome.

Files and binary clipboard items — pasted screenshots, files copied from a file manager — are not delivered by `PasteEvent`. Register for those with `Clipboard.onFilePaste()` instead; the same paste gesture fires both listeners when both are registered.

### <a id="page-wide-listeners-and-paste-options"></a>Page-Wide Listeners and Paste Options

To observe pastes anywhere on the page, pass the `UI` as the component — its root element is `<body>`, so every bubbling paste event reaches it. A page-wide listener usually should not react to pastes the user intends for a form field. The `PasteOptions` overload controls this:

```java
Clipboard.onPaste(UI.getCurrent(), PasteOptions.defaults(), event -> {
    importFromClipboard(event);
});
```

- `PasteOptions.defaults()`

  Skips pastes whose target is an input, textarea, or `contenteditable` element. The check sees through open shadow DOMs, so a focused field inside a Vaadin web component is also skipped.

- `PasteOptions.includingInputFields()`

  Forwards every paste regardless of focus. This is also the behavior of the two-argument `onPaste()` overload, which takes no options.

## <a id="handling-pasted-files"></a>Handling Pasted Files

`Clipboard.onFilePaste(component, handler)` registers a listener for pastes that deliver files — a screenshot from the OS clipboard, files copied from a file manager, anything that arrives on `event.clipboardData.files`. Each pasted file is uploaded to the server individually and handed to the given `UploadHandler`, exactly as for a regular upload; pastes without files are ignored. The simplest form uses a plain in-memory handler:

```java
Clipboard.onFilePaste(this, UploadHandler.inMemory(
        (metadata, bytes) -> store(metadata.fileName(), bytes)));
```

UI changes made in the handler are flushed to the browser automatically once the paste’s uploads have finished — no server push is required.

For paste-aware handling, `PasteFileHandler` builds upload handlers that correlate the parallel uploads of one paste gesture. `PasteFileHandler.perFile()` delivers each file as a `PasteFile` record on the UI thread:

```java
Clipboard.onFilePaste(this, PasteFileHandler.perFile(file -> {
    if (file.newPaste()) {
        attachments.removeAll();
    }
    attachments.add(createPreview(file.fileName(), file.bytes()));
}));
```

`PasteFile` carries the file’s bytes plus metadata and paste correlation:

- `fileName()`, `contentType()`, `size()`, `bytes()`

  The file name and MIME type as reported by the browser (`contentType()` may be `null`), the size in bytes, and the content itself.

- `pasteId()`

  A sequence number shared by all files of the same paste gesture; subsequent pastes in the same tab carry strictly larger values.

- `newPaste()`

  `true` on the first file of each paste to reach the listener, `false` for the rest.

- `totalFiles()`

  The total number of files the originating paste contained.

When the application needs to know when a whole paste has finished, use the three-step batch form. `onStart` fires once per paste before its first file, `onFile` fires per file, and `onComplete` fires once the paste’s declared file count has been delivered; any step may be omitted:

```java
Clipboard.onFilePaste(this, PasteFileHandler.batch()
        .onStart(start -> progress.setVisible(true))
        .onFile(file -> addAttachment(file.fileName(), file.bytes()))
        .onComplete(complete -> progress.setVisible(false))
        .build());
```

`onStart` receives a `PasteStart` record with `pasteId()` and `totalFiles()`; `onComplete` receives a `PasteComplete` with `pasteId()` and `receivedFiles()`. Each paste runs as its own batch and completes on its own timeline, even when the uploads of two pastes interleave in transit. An upload that fails in transit never reaches the server and does not count, so a paste with a lost upload never fires `onComplete`.

`PasteFileHandler.perFile()` and the `onFile` step read each upload fully into memory as a `byte[]` before invoking the callback, so a very large paste is held entirely in heap. Cap it with the size limits of a custom `UploadHandler`, or read the stream yourself, when that matters. Also treat `fileName()` as untrusted browser input — never use it directly as a filesystem path without sanitizing it first.

Unlike `onPaste()`, the file variant has no option to skip pastes into form fields: browsers never paste files into an `<input>` or `<textarea>`, so there is no native paste behavior to compete with. The API is independent of `onPaste()` — a paste that carries both files and text fires both listeners when both are registered.
