← All gadgets

Copy Pill

One-click copy for a snippet, command or address

Running live from https://fun.whitneys.co, cross-origin and sandboxed.

Put it on your site

<iframe
  src="https://fun.whitneys.co/g/copy-pill"
  width="420"
  height="90"
  title="Copy Pill"
  sandbox="allow-scripts allow-popups-to-escape-sandbox"
  allow="clipboard-write"
  loading="lazy"
  style="border:0"></iframe>

An iframe, not a script tag -- so this gadget cannot touch the page you paste it into, and neither can we. Your settings above are baked into the URL.

Optional: let it resize itself to fit
<script>
addEventListener('message', function (e) {
  var d = e.data;
  if (!d || d.type !== 'fwf:height') return;
  if (typeof d.height !== 'number' || !isFinite(d.height)) return;
  document.querySelectorAll('iframe').forEach(function (f) {
    // Identity by source window, not by origin -- sandboxed frames report
    // their origin as "null". Do not "fix" this into an origin check.
    if (f.contentWindow !== e.source) return;
    f.style.height = Math.max(40, Math.min(2000, Math.ceil(d.height))) + 'px';
  });
});
</script>

The whole thing

Raw file

Every gadget is one file, and this is it -- all 3.2 kB. Take it, change it, ship it. Just keep the credit.

Show source
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<script type="application/json" id="gadget-manifest">
{
  "name": "Copy Pill",
  "tagline": "One-click copy for a snippet, command or address",
  "category": "tools",
  "tags": ["clipboard", "code", "utility"],
  "author": "rwhitney",
  "background": "dark",
  "size": { "w": 420, "h": 90, "resizable": true },
  "config": {
    "value":  { "type": "text",  "label": "Text to copy", "max": 80, "default": "npm create volt@latest" },
    "accent": { "type": "color", "label": "Accent",       "default": "#66D9EF" },
    "mono":   { "type": "bool",  "label": "Monospace",    "default": true }
  },
  "permissions": ["clipboard"]
}
</script>
<style>
  html, body {
    margin: 0; height: 100%;
    display: grid; place-items: center;
    background: transparent; color: #F8F8F2;
    font: 15px/1.4 ui-sans-serif, system-ui, sans-serif;
    overflow: hidden;
  }
  #pill {
    display: flex; align-items: center; gap: 12px;
    max-width: 92%;
    padding: 12px 12px 12px 18px;
    border: 1px solid rgb(248 248 242 / 0.18);
    border-radius: 999px;
    background: rgb(248 248 242 / 0.04);
  }
  #value {
    flex: 1; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: #F8F8F2;
  }
  #value.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
  #btn {
    flex: none; font: inherit; font-size: 13px; font-weight: 700;
    padding: 7px 16px; border-radius: 999px; cursor: pointer;
    border: 1px solid var(--accent, #66D9EF);
    background: transparent; color: var(--accent, #66D9EF);
    transition: background 120ms ease;
  }
  #btn:hover { background: rgb(255 255 255 / 0.07); }
  #btn:focus-visible { outline: 2px solid var(--accent, #66D9EF); outline-offset: 2px; }
  #btn.done { background: var(--accent, #66D9EF); color: #14150F; }
</style>
</head>
<body>
  <div id="pill">
    <span id="value"></span>
    <button id="btn" type="button">Copy</button>
  </div>
<script>
(function () {
  var cfg = window.gadget ? window.gadget.config : {};
  var text = cfg.value || 'npm create volt@latest';
  var el = document.getElementById('value');
  var btn = document.getElementById('btn');

  el.textContent = text;                       // text, never markup
  if (cfg.mono !== false) el.classList.add('mono');
  document.documentElement.style.setProperty('--accent', cfg.accent || '#66D9EF');

  btn.setAttribute('aria-label', 'Copy to clipboard');

  btn.addEventListener('click', function () {
    // Needs the "clipboard" permission, which this gadget declares. Both the
    // sandbox Permissions-Policy and the embedding page's allow attribute
    // have to grant clipboard-write or this fails -- and failing is fine.
    var done = function () {
      btn.textContent = 'Copied';
      btn.classList.add('done');
      setTimeout(function () {
        btn.textContent = 'Copy';
        btn.classList.remove('done');
      }, 1400);
    };
    var fail = function () {
      btn.textContent = 'Blocked';
      setTimeout(function () { btn.textContent = 'Copy'; }, 1400);
    };
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(text).then(done, fail);
    } else {
      fail();
    }
  });
})();
</script>
</body>
</html>