Copy Button
Button that copies text to the clipboard — the element it sits in, another element by id, or a literal string.
c2-copy-button puts text on the clipboard. Drop it inside the element you want to copy and that is all the configuration it needs: it copies its parent, and leaves its own label out of the result. Point it at something else with for (the id of the source), or hand it the exact string with value. It reads each source the way that source stores its text — the code of a c2-code-viewer, the value of an input or c2-text-field, the rendered text of anything else — so copying a highlighted snippet gives you the code, not a wall of token spans. By default the button stays out of the way until you hover its source, and pin parks it in a corner where it stays put even while the source scrolls.
Installation
npm install @c2n/copy-buttonUsage
npx -y @c2n/mcp
const copy = document.createElement('c2-copy-button')
>copy.value = 'hello'> Reveal. reveal="hover" is the default: the button is transparent until the pointer enters its source, which keeps a corner button from sitting on top of the content it copies. It is only the opacity that changes, so the button keeps its box and stays reachable — it also appears when focus lands anywhere inside the source (so it is keyboard-operable), on devices with no hover at all (a hover-only button would be unreachable on touch), and for as long as the copied confirmation is showing, so the check mark does not flash invisibly after the pointer leaves. While hidden it takes no pointer events, so there is never an invisible click target. Use reveal="always" when the button should be permanently visible — which is also what the previews and gallery cards on this site do, since nothing hovers them.
Pin. pin absolutely positions the button in a corner of its containing block — pin alone means top right, or name one of top-right, top-left, bottom-right, bottom-left — inset by --c2-copy-button__pin--offset-x / -y. Give the element it sits in position: relative, as with any absolutely positioned child.
The reason pin exists rather than leaving you to write the CSS: an absolutely positioned child of a scrolling box lives in that box’s scrollable overflow, so it scrolls away with the content — exactly when you need it, because long or wide text is why there is a scrollbar in the first place. A pinned button finds the nearest ancestor that can scroll and cancels out its scroll offset, so it holds its corner no matter where the content is scrolled, in either direction. Scroll the second example above to see it.
Sources. Resolution is value, then for, then the light-DOM parent. parentElement is deliberate: a button slotted into another custom element copies the element it was authored inside, not the shadow-DOM node it happens to render in. Whitespace inside the source is preserved (newlines in a <pre> survive) and only the leading and trailing whitespace is trimmed.
Feedback and slots. With no slotted content the button is icon-only and square (--c2-copy-button__container--min-width matches its height); slot text and it grows into an icon-and-label button. The copy-icon and copied-icon slots replace the two default glyphs, and copied-label replaces the text while the copied state is showing. copied is reflected, so c2-copy-button[copied] is styleable from outside, and the __container__copied--* variables cover the state from within. Because an icon swap alone is not announced, the element renders a visually hidden role="status" region carrying copied-label (or Copy failed), and the button’s aria-label follows the same text — set label and copied-label when the defaults (“Copy” / “Copied”) do not fit. A refused clipboard write turns the button red and fires copy-error rather than failing silently, and where navigator.clipboard is unavailable — any non-secure context, which includes a plain-http dev server — it falls back to an off-screen <textarea> and document.execCommand('copy').