Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

Build a "Quote This" Feature with the Selection API

C
Carlo Fontanos
· 2 min read

Users already highlight text while reading - it's practically a nervous tic. Sites like Medium turned that into a feature: select a sentence, get a little "share" button. The machinery is one global object and one event, and it's been in browsers forever.

Reading what's selected

document.addEventListener('selectionchange', () => {
    const selection = document.getSelection();
    const text = selection.toString().trim();

    if (text.length < 10 || selection.isCollapsed) {
        hideQuoteButton();
        return;
    }
    showQuoteButton(text, selection);
});

selectionchange fires on every adjustment (it's chatty - debounce it in real code), and isCollapsed distinguishes an actual selection from a plain cursor click. The length check avoids popping UI over accidental double-clicks.

Positioning the button over the selection

The selection's Range object knows its own geometry:

function showQuoteButton(text, selection) {
    const rect = selection.getRangeAt(0).getBoundingClientRect();

    btn.style.left = `${rect.left + rect.width / 2 + scrollX}px`;
    btn.style.top  = `${rect.top + scrollY - 44}px`;   // float above
    btn.hidden = false;

    btn.onclick = () => {
        const quote = `"${text}" - ${document.title} ${location.href}`;
        navigator.clipboard.writeText(quote);
        toast('Quote copied');
    };
}

getBoundingClientRect on the range gives viewport coordinates; adding scrollX/scrollY converts to page coordinates for an absolutely positioned button. Multi-line selections return the union box, which is usually what you want for centering.

Details that separate polished from janky

  • Scope it: check that the selection lives inside your article element (article.contains(selection.anchorNode)) so the button doesn't appear when someone selects nav text.
  • mouseup vs selectionchange: showing UI on selectionchange means it flickers during the drag. The classy pattern: track validity on selectionchange, but reveal the button on mouseup/touchend, when the gesture is finished.
  • Don't steal the selection: if your button takes focus on mousedown, the selection collapses before the click handler reads it. btn.addEventListener('mousedown', e => e.preventDefault()) keeps the highlight alive.
  • Share targets: on mobile, navigator.share({ text: quote }) hands off to the native share sheet - feature-detect and prefer it over clipboard.

Same API powers the other direction too: prefilling a feedback form with the selected text ("report a typo" features), or checking selection.toString() in a copy event listener to append attribution. Ethical note on that last one: readers hate sites that mangle their clipboard - append, never replace, and keep it short.

Support: universal, including the touch-selection handles on mobile. One of those APIs that's been quietly complete for a decade, waiting for product ideas.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Message sent!

Your details are only used to reply to you.

Keep reading