Artboard 16light, inspiration, solution, idea, innovation,Google Sheets iconSwift icon
Published at
Updated at
Reading time
6min
This post is a note that includes my thoughts about something I found online. Check it out yourself!

I discovered a web development magic trick from Cyrus Roshan today. It took me a good 20 minutes to figure out how the trick works, and I learned some things about JavaScript clipboard handling and MIME types. Sounds intriguing? Read on!

Try the magic tricks yourself; I'll wait. ๐Ÿ˜‰

Screen showing six ascii art playing cards tasking you to click and automatically copy one.

If you didn't try it, here's the flow:

  1. You're tasked to click an ASCII art play card. The card's characters are automatically copied to your clipboard using JavaScript.
  2. Then, a new screen tells you to paste the copied ASCII card into a textarea (which works as expected).
  3. Next, you're told to open a new Google Doc and paste your clipboard content into it (the ASCII art playing card).
  4. The just copied ASCII art now includes a new line telling you to paste the same content into the URL bar, and boom! ๐Ÿช„ You just pasted Cyrus' Twitter profile URL.

Whoot? That's magic! ๐Ÿคฏ

The Clipboard API

After creating fifteen different Google documents wondering if Cyros somehow injects JavaScript into Google Docs (which he doesn't), I figured out how this trick works.

Cyros' page leverages a nifty feature of the JavaScript Clipboard API (navigator.clipboard), and like every magic trick, once you know how it works, it's stupidly simple.

If you've been doing web development long enough, you might remember the document.execCommand('copy') command. This old way to interact with the clipboard is now deprecated and replaced by the Clipboard API. The newer API has the single purpose of interacting with the clipboard and works asynchronously. Yay!

But does the Clipboard API work everywhere today? At first look, navigator.clipboard seems to be cross-browser supported...

MDN Compat Data (source)
Browser support info for Clipboard
chromechrome_androidedgefirefoxfirefox_androidsafarisafari_iossamsunginternet_androidwebview_android
666679636313.113.19.066

... but watch out! Looking deeper into it, you'll find out that just because navigator.clipboard is available, it doesn't mean all functionality is available.

How to place plain text in the clipboard

Putting text into the clipboard is straightforward using the API. Here's an example.

await navigator.clipboard.writeText(
  "That's some cool copied text, isn't it?"
);

Click the button below and paste the new clipboard content into the input fields to confirm it works.

Playground

writeText covers many standard use cases, but it's not what the magic trick uses. Let's dig deeper!

How to write different MIME types to the clipboard

As seen, placing text on the clipboard is quickly done. But how would you handle images or other text formats such as richtext or HTML? Is it possible to put these into the clipboard with JavaScript, too?

There's another method available to put content in the clipboard โ€“ clipboard.write.

await navigator.clipboard.write([
  new ClipboardItem({
    'text/plain': new Blob(["That's some cool plain text, isn't it?"], {
      type: 'text/plain',
    }),
  }),
]);

clipboard.write doesn't accept strings but ClipboardItems. The main difference between the two methods is that if you want to put anything but plain text into the clipboard, you must define the matching MIME type using a ClipboardItem.

It's more code to write but still a decent experience in my opinion. Sweet!

Unfortunately, neither navigator.clipboard.write nor the global ClipboardItem property is defined in Firefox at the time of writing (both are behind the dom.events.asyncClipboard.clipboardItem flag).

MDN Compat Data (source)
Browser support info for ClipboardItem
chromechrome_androidedgefirefoxfirefox_androidsafarisafari_iossamsunginternet_androidwebview_android
66667987*8713.113.19.066

I didn't do the research but if you're looking for a cross-browser solution to place things other than text in the clipboard, I'm sure some libraries have you covered.

And here's annother example to play with. It looks the same as the previous one, but now it uses navigator.clipboard.write.

Fill up your clipboard!

Playground

Can you already imagine how the magic trick works now that you've seen some code?

That's right; the trick is based on different content MIME types. Input fields and textareas handle pasted plain text just fine, but there are obviously other available MIME types.

A clipboard could hold types of image/gif, image/jpeg, text/rtf, the good old text/html, and all sorts of fanciness.

And thanks to the Clipboard API, you're in control of the MIME type and can even store text and images in the same write operation.

And it's not only a single operation; it's even a single clipboard entry.

navigator.clipboard.write([
  new ClipboardItem({
    'text/plain': new Blob(["That's some cool plain text, isn't it?"], {
      type: 'text/plain',
    }),
    'text/html': new Blob(
      [
        '<div style="/* some styles */">Oh yeah - text/html!</div>',
      ],
      {
        type: 'text/html',
      }
    ),
  }),
]);

The example above shows how to put different content as plain text and HTML into your clipboard. ๐Ÿ˜ฒ

Now it's only a matter of where you paste the content to see this magic in action.

A div with an contentEditable attribute can accept and render HTML. ๐Ÿ˜ฒ If you paste content with the MIME type text/html into it, it will render it just fine.

To prove it, hit the button below and see what happens when you paste it into the input fields and the editable div.

Playground
A div with contentEditable

Cyrus' trick uses this functionality.

Initially, the magic trick puts plain text into the clipboard, but later on, it stores a ClipboardItem with multiple MIME types. text/plain holds his Twitter profile URL and text/html includes the ASCII art card. Google Docs then renders the pasted HTML, whereas the URL bar renders the plain text.

How to inspect your clipboard

While I was debugging the magic trick, I discovered that inspecting your clipboard isn't straightforward on MacOS. Even though the Finder provides a way to look at what's in the clipboard (Finder > Edit > Show clipboard), it always shows the plain text entry.

MacOS window showing the content of the clipboard. It's showing plain text.

I built a quick clipboard inspector using the Clipboard API's read methods. And here it became very interesting.

Unfortunately, it's the same story of Firefox not supporting complex clipboard interactions (it's behind another flag โ€“ dom.events.asyncClipboard.read) and even though Safari supports navigator.clipboard.write it has a surprise for us.

MDN Compat Data (source)
Browser support info for clipboard.read
chromechrome_androidedgefirefoxfirefox_androidsafarisafari_iossamsunginternet_androidwebview_android
86867990*9013.113.112.084

MDN explains to use navigator.read as follows:

try {
  const permission = await navigator.permissions.query({ name: 'clipboard-read' });
  if (permission.state === 'denied') {
    throw new Error('Not allowed to read clipboard.');
  }
  const clipboardContents = await navigator.clipboard.read();
  for (const item of clipboardContents) {
    // do things with the clipboard entries
  }
} catch (error) {
  console.error(error.message);
}

It works fine in Chromiums, but it turns out that Safari doesn't support navigator.permissions. ๐Ÿคฆโ€โ™‚๏ธ

MDN Compat Data (source)
Browser support info for Permissions
chromechrome_androidedgefirefoxfirefox_androidsafarisafari_iossamsunginternet_androidwebview_android
434379464616164.0Non

This means you have to check if navigator.permissions is available, too. And if it is, ask for permissions and if not, try to use navigator.clipboard.read anyways.

In this case, Safari shows a little "Paste" mini permission dialog. If you don't click it, navigator.clipboard.read will throw an exception. Ufff...

Big button with a native MacOS "Paste" button on top of it.

Here's a summary on how to use navigator.clipboard.read:

  • For Chromiums you should use the Permissions API.
  • You can't read the clipboard content using Firefox.
  • In Safari you just have to try it and see if it works.

Have fun with it below.

Playground

Side note: not all clipboard content is accessible

Inspecting and accessing text-based clipboard content seemed to work fine in Chromiums. But if I copy an image from the MacOS Finder navigator.clipboard.read doesn't like that either and throws a No valid data on clipboard exception.

JavaScript exception "No valid data on clipboard"

So, if you're planning to use navigator.clipboard.read, you have to feature detect the Permissions API and also make sure to try/catch all your read calls.

Conclusion

This little magic trick became quite a journey. But here's what I learned:

  1. The Clipboard API allows you to write multiple entries in different MIME types to the clipboard.
  2. Using the Clipboard API is still a pain if you're targeting all major browsers.
  3. Not everything in your clipboard is accessible via JavaScript.

If you want to learn more there's a good article on the async Clipboard API on web.dev and Thomas has you covered, too.

And with this, happy pasting! ๐Ÿ‘‹

Related Topics

Related Articles