In my side project using Svelte I needed to copy rich text, including images and formatted text. Turns out this seemingly simple task is rather difficult and is done using different APIs for different browsers. Nothing to worry about, though - there must be an NPM package for it!

This package is ClipboardJS, but if you read the docs they recommend including it as a script in your header either from CDN or from your dist folder. Adding scripts to your header isn’t the way we’re used to working with packages in Svelte or other SPA frameworks and depending on your adapter you may not even have a dist folder. So, what now? For more widely used frameworks and libraries like React, Angular or Vue there are adapters for the ClipboardJS package, but my brief Google search didn’t provide any results for Svelte.

Turns out, even though it’s not in the documentation, you can import it as an ES6 module. Here’s a basic component using ClipboardJS as an ES6 module:

<script>
  import Clipboard from 'clipboard';
  import { onMount } from 'svelte';

  onMount(() => {
    let clipboard = new Clipboard('.btn');
  });
</script>

<div id="container">
  <h1>Look at those awesome pictures!</h1>
  <img src="https://picsum.photos/200/200" alt="Random" />
  <img src="https://picsum.photos/250/200" alt="Random" />
  <img src="https://picsum.photos/150/200" alt="Random" />
</div>

<button class="btn" data-clipboard-target="#container">Copy</button>
<p>{message}</p>

There are two things to keep in mind - firstly, we’re instantiating Clipboard in the onMount function, as it requires access to the document interface - it’s not accessible on the server, and onMount guarantees we only run it once we’re on the client’s side. Secondly, make sure to provide correct selectors both in the constructor of Clipboard, and in the data-clipboard-target attribute. That’s it, you’ve got rich text working in Svelte!

Two things you might want to add to this solution are deselecting the copied text, as in the solution above it stays selected, and providing the user with feedback on whether the copying action worked. Here’s an updated component:

<script>
  import Clipboard from 'clipboard';
  import { onMount } from 'svelte';
  let message = 'Nothing copied yet.';

  onMount(() => {
    let clipboard = new Clipboard('.btn');
    clipboard.on('success', function (e) {
      e.clearSelection();
      message = 'Succesfully copied at ' + new Date().toLocaleTimeString();
    });
    clipboard.on('error', function (e) {
      message = 'Failed to copy at ' + new Date().toLocaleTimeString();
    });
  });
</script>

<div id="container">
  <h1>Look at those awesome pictures!</h1>
  <img src="https://picsum.photos/200/200" alt="Random" />
  <img src="https://picsum.photos/250/200" alt="Random" />
  <img src="https://picsum.photos/150/200" alt="Random" />
</div>

<button class="btn" data-clipboard-target="#container">Copy</button>
<p>{message}</p>

We’ve added a message property and two calls to clipboard.on function - one specifying that on success, we want to clear the selection and set the message, the other specifying that on error we want to also set the message, this time notifying about the failure.