Installation

Install the package:

npm install copy-component

Import the module to be handled by your bundler (or not). If you'd prefer to just embed the library in your code you can import it directly from a CDN, shown below.

import "copy-component";
<copy-component>
<p>
Hello<br />
world
</p>
<button slot="button">Copy</button>
</copy-component>

From CDN:

<script type="module" src="https://cdn.skypack.dev/copy-component?min"></script>

Here is an un-styled Codepen.

Basic usage

Any HTML inside of the copy-component tag will be made copy-able. You will likely want to add in a button to trigger the copy, to do this you must set the attribute slot="button" on your button (or a parent of the button). Formatting is preserved on copy.

Of course, all of the "Copy buttons" on this website use copy-component 😊.

<copy-component>
<p>
Hello<br />
world
</p>
<button slot="button">Copy</button>
</copy-component>
copy-component {
position: relative;
border: 2px lightgrey dashed;
}

copy-component > *:not([slot="button"]) {
margin: 1rem;
}
copy-component button {
position: absolute;
text-transform: uppercase;
background: rgba(0, 0, 0, 0.3);
color: white;
top: 0;
right: 0;
border: none;
}

copy-component button:active {
background: rgba(0, 0, 0, 0.8);
}

Limitations

In order to preserve the formatting of copied content you must wrap your content you want to copy in single parent dom node i.e.

<copy-component>
<div>
<p>Stuff to copy</p>
<br />
<p>Copy me too!</p>
</div>
<button slot="button">Copy me!</button>
</copy-component>

NOT

<copy-component>
<p>Stuff to copy</p>
<br />
<p>Copy me too!</p>
<button slot="button">Copy me!</button>
</copy-component>

This is due to how shadow dom works, it is not possible to get the formatted inner text of a single slot without also getting the text of the other slots (e.g. the button slot).

Events

Custom events are fired the following:

copy - when copy succeeds
copy-failed - when copy fails

<copy-component>
<p>
Hello<br />
world
</p>
<button id="copy-button" slot="button">Copy</button>
</copy-component>
document.body.addEventListener("copy", () => {
document.getElementById("copy-button").innerText = "Copied!";
});
copy-component {
position: relative;
display: flex;
border: 2px lightgrey dashed;
}

copy-component > *:not([slot="button"]) {
margin: 1rem;
}
copy-component button {
position: absolute;
text-transform: uppercase;
background: rgba(0, 0, 0, 0.5);
color: white;
top: 0;
right: 0;
border: none;
}

copy-component button:active {
background: rgba(0, 0, 0, 0.8);
}

Check out the codepen.

Markdown

Because Web Components are html you can wrap any markdown text/elements you like in a copy-component

e.g.

<copy-component
oncopy="this.querySelector('[slot=button]').textContent='Copied'"
style="display: block;"
>

```js console.log("hello world!"); ```

<button style="text-transform: uppercase" slot="button">Copy here</button>
</copy-component>

11ty

To automatically add copyable text snippets to your 11ty site from your markdown checkout this 11ty plugin.

```html
<p>Your blog post code</p>
```

plus `.eleventy.config.js`:

```js
eleventyConfig.addPlugin("eleventy-plugin-markdown-copy-button");
```

equals auto-copyable code:

```html
<p>Your blog post code</p>
```

If you're interested in this check out the 11ty plugin: https://github.com/Georgegriff/eleventy-plugin-markdown-copy-button

## Web Component library

Here's an example using the component within lit

```js lit-copy
import { LitElement, html, css } from "https://cdn.skypack.dev/lit";
// un-comment this, commented out because this website uses the component and can only import a web component once
//import "https://cdn.skypack.dev/copy-component";

class CopyToClipboard extends LitElement {
  constructor() {
    super();
    this._copyText = "Copy text";
    this._copiedText = "Copied!";
    this._copyFailed = "Failed! 😞";
    this.copyText = this._copyText;
  }
  static get styles() {
    return css`
      :host {
        display: flex;
        flex-direction: column;
        margin: 0.5rem;
        padding: 0.5rem;
        position: relative;
        border: 2px dashed #82212c;
        max-height: 450px;
        overflow: auto;
      }
      :host([hidden]) {
        display: none;
      }

      button {
        font-family: inherit;
        text-transform: uppercase;
        background: #82212c;
        color: #1a1a1a;
        font-size: 1rem;
        font-weight: 600;
        padding: 0.25rem 1.5rem;
        border-top-left-radius: 0.25rem;
        border-top-right-radius: 0;
        border: none;
        cursor: pointer;
        z-index: 1;
        height: 2.5rem;
        margin-left: auto;
      }

      button:active {
        background: #5a363a;
      }

      .floating-btn {
        position: absolute;
        top: 0;
        right: 0;
      }

      ::slotted(*) {
        margin: 0 !important;
        height: 100%;
        padding-bottom: 2.5rem;
      }
    `;
  }

  _onCopy() {
    this.copyText = this._copiedText;
    this.requestUpdate();
  }

  _onCopyFailed() {
    this.copyText = this._copyFailed;
    this.requestUpdate();
  }

  render() {
    return html`
      <copy-component
        @copy=${this._onCopy.bind(this)}
        @copy-failed=${this._onCopyFailed.bind(this)}
      >
        <slot></slot>
        <button
          slot="button"
          class="floating-btn"
          aria-label="Copy to clipboard"
        >
          ${this.copyText}
        </button>
      </copy-component>
    `;
  }
}

customElements.define("copy-it", CopyToClipboard);
```

```html lit-copy
<copy-it>
  <div>
    <p>Some text to copy</p>
    <br />
    <p>Formatting is preserved!</p>
  </div>
</copy-it>
```

Check out the [codepen](https://codepen.io/georgegriff/pen/jOGYvaY).

Thanks for reading the docs!

<div class="author-data">
    <div class="sponsor">
    <span class="sponsor-title">Did my content help you?</span>
    <div>Share on social media,&nbsp;<a rel="noopener" href="https://twitter.com/intent/follow?screen_name=griffadev">follow me on twitter,</a> or
    </div>
    <div>
        <a  class="kofi" rel="noopener" href='https://ko-fi.com/G2G221OBA' target='_blank'><img height='36' style='border:0px;height:36px;' src='/images/kofi.png' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
    </div>
</div>
    <figure class="author-info">
    <a rel="noopener" target="_blank" href="https://twitter.com/intent/follow?screen_name=griffadev"><img class="author-img" src="/images/griff.jpg" alt="Image of author: George Griffiths".></a>
    <figcaption>
        <span class="author-name">George Griffiths</span>
        <span class="author-twitter"><a target="_blank" rel="noopener" href="https://twitter.com/intent/follow?screen_name=griffadev">@griffadev</a></span>
    </figcaption>
</figure>
</div>


<div class="something-wrong">
    <span>Something not quite right on this page? <a target="_blank" rel="noopener" href="https://github.com/Georgegriff/griffadev/issues/new?title=Content+correction:+.%2Fsrc%2Fdemos%2Fcopy-component%2Findex.md&body=Hello%2C%20I've%20noticed%20an%20issue%20in%3A%0A%20%20%20%20https%3A%2F%2Fgithub.com%2FGeorgegriff%2Fgriffadev%2Fblob%2Fmain%2Fsrc%2Fdemos%2Fcopy-component%2Findex.md%20%0A%0A**Describe%20the%20problem**%0A%0A%20%20A%20clear%20and%20concise%20description%20of%20what%20the%20problem%20is%0A%0A%20%20**Existing%20content**%0A%0A%20%20What%20is%20there%20at%20the%20moment%3F%0A%0A%20%20**Expected%20content**%0A%0A%20%20What%20would%20you%20expect%20instead%3F%20A%20PR%20is%20more%20than%20welcome%20%3Asmiley%3A%0A%0A%20%20**Screenshots**%0A%0A%20%20If%20applicable%2C%20add%20screenshots%20to%20help%20explain%20your%20problem%0A%0A%20%20**Additional%20context**%0A%0A%20%20Add%20any%20other%20context%20about%20the%20problem%20here.%0A%20%20%20%20">please let me know.</a></span>
</div>