web development

Stolen Buttons: Building a Local “UI Button Collector” Extension

Stolen Buttons: Building a Local “UI Button Collector” Extension

The moment a button turns into data

The first time you notice a great button, it feels almost physical. The rounded corners. The padding that makes the label breathe. The contrast that tells your brain “this is clickable” before your eyes finish reading.

Then the thought hits: how do we turn that vibe into something repeatable? Not by hunting through design systems forever, but by collecting examples automatically as you browse.

That’s the spirit behind “Stolen Buttons”: a Chrome extension idea where the code wanders through web pages, recognizes “button-like” elements, and stashes a cleaned-up copy locally. Not for tracking people, not for changing the page—just for capturing UI inspiration.

This post walks through the technical path from “What even is a button in the browser?” to a working extension architecture: content scripts, DOM parsing, cloning elements, sanitizing styles, and saving results.


Browser extensions: two worlds that have to cooperate

A Chrome extension isn’t one single script. It usually splits responsibilities between:

  • Content script: code that runs inside the page (more precisely: alongside the page’s DOM). It can read elements and styles, but it’s constrained by the extension sandbox.
  • Background script (Manifest V3 service worker): code that runs outside the page context. Think of it as the extension’s “brain” for storage, scheduling, and long-running logic.
  • Offscreen document (optional but common): a hidden, non-visible page used when the background script needs DOM-like capabilities (for example, parsing or transforming HTML) but you still want it decoupled from the main tab.

In the “stolen button” pattern, the content script does the heavy lifting of finding and extracting button candidates, while the background script handles what to do next (store locally, sync, update a UI).


What “button” means in the DOM

The DOM (Document Object Model) is the browser’s in-memory tree of everything on a page: elements, attributes, and text nodes.

In HTML terms, buttons might appear as:

  • <button> elements
  • <a> links styled and behaving like buttons
  • custom components that render as either of the above

A naive approach would be: “collect every <button>.” But real pages contain tiny icons, hidden controls, ads, and accessibility wrappers.

So the collector needs a candidate filter.

A practical candidate filter (size + visibility + text)

Here’s the general idea:

  1. Query the page for likely button nodes.
  2. Reject elements that are too small (e.g., icon-only controls).
  3. Reject elements that are invisible (opacity 0) or offscreen.
  4. Extract a short label (usually innerText) and reject junk labels.

In code, the DOM query is straightforward:

const likelyButtons = [
...document.querySelectorAll('button'),
...document.querySelectorAll('a[role="button"], a')
];

Then filtering by layout characteristics uses layout APIs:

  • offsetWidth / offsetHeight: pixel dimensions of the rendered element.
  • getComputedStyle(element): the browser’s final, computed CSS values after the cascade.

A simplified filter looks like:

function isPlausibleButton(el) {
 const w = el.offsetWidth;
 const h = el.offsetHeight;
 if (w < 10 || h < 25) return false;

 const style = window.getComputedStyle(el);
 if (style.opacity === '0') return false;

 const label = (el.innerText || '').trim();
 if (label.length < 2 || label.length > 32) return false;

 return true;
}

This kind of filtering sounds boring, but it’s the difference between “beautiful examples” and “an extension that collects everything, including the subscribe icons.”

The tricky part: styles can be inherited or delegated

In many UI libraries, the visible button style isn’t on the <button> itself—it might live on a wrapper element or an inner span.

So a robust extractor checks styles of:

  • the element itself
  • sometimes its first meaningful child

Cloning: turn a live element into a portable specimen

Once you’ve picked a candidate element, you need a copy that won’t break when the page navigates away.

There are two common extraction strategies:

  1. Save an identifier (not enough for design inspiration)
  2. Save markup + styling enough to recreate the button look

This is where cloning comes in.

cloneNode(true) and outerHTML

  • cloneNode(true) creates a deep copy of a node, including its children.
  • outerHTML gives a string containing the element’s full HTML markup (including the element itself).

A collector typically does:

const clone = buttonEl.cloneNode(true);
const html = clone.outerHTML;

But if you capture raw outerHTML, you’ll also capture a lot of style noise and context assumptions.


Sanitizing styles: from “page-specific” to “button-specific”

To make a collected button reusable, you want to strip away styles that exist only because the element sat inside a particular layout grid.

The browser gives you computed CSS via getComputedStyle, which returns the final used values.

A powerful (and surprisingly common) technique is:

  1. Remove inline style attributes from the clone.
  2. Compare computed properties between the clone and its reference.
  3. Keep only the properties that actually define the button’s appearance.

The “stolen button” idea tends to keep a whitelist of CSS properties (fonts, colors, cursor, borders, padding, etc.) and discards known defaults.

Even more important: it tries to avoid capturing sizes that would make the recreated element depend on viewport units or tiny default widths.

This yields an output like:

  • cleaned markup (outerHTML)
  • optional normalization of colors (e.g., replacing rgb(0, 0, 0) with #000)
  • the button text label as separate metadata

Why color normalization helps

Browsers output colors in multiple formats: rgb(), rgba(), transparent, or hex depending on how the style was computed.

If you normalize to a consistent format, it makes the stored “specimen” easier to compare and edit later.


Saving results locally: storage as a simple data structure

A collector shouldn’t need a server to be useful. The simplest workflow is:

  • store an array of collected buttons in local extension storage
  • keep a maximum size (a rolling list)
  • store an “upload queue” only if you want sync

In Chrome extensions, this often looks like a ring buffer pattern:

const { buttons = [], maximum = 200 } = await chrome.storage.local.get('buttons');

buttons.unshift(newButton);
while (buttons.length > maximum) buttons.pop();

await chrome.storage.local.set({ buttons });

This is the quiet engineering win: it prevents unbounded growth and keeps performance predictable.


Background work and offscreen parsing

Sometimes extracting a button specimen requires more than what the content script can comfortably do.

That’s where the extension background service worker (the “brain”) can trigger an offscreen document to parse DOM-like content or transform HTML without tying up the visible tab.

The mental model:

  • content script: sees the live page and extracts candidates
  • background: decides whether to store locally, sync, or transform
  • offscreen: performs DOM parsing/transformation tasks in a hidden context

This architecture also helps keep permissions narrow and reduces the chances of accidentally messing with page behavior.


A safety note: UI “stealing” is not clickjacking

The phrase “steals” can sound scary, and for good reason—there are real UI attacks (like clickjacking) that trick users into clicking something different from what they think they’re clicking.

A button collector extension should avoid anything like:

  • overlaying invisible elements on top of real UI
  • rewriting click handlers
  • intercepting user input to change outcomes

Instead, it should be read-only extraction: observe elements, clone them, and store locally. The user interaction should remain on the user’s terms.


What you end up building (and why it’s a good learning project)

By the time this “stolen buttons” extension is working, you’ve touched a lot of real web engineering concepts:

  • DOM querying and element filtering
  • computed CSS and why design systems can be context-dependent
  • cloning and generating portable markup
  • extension architecture: content scripts + service worker + offscreen parsing
  • local persistence with bounded storage

And maybe the best part: you start to see UI as structured data, not just pixels.

So when you open a new site and spot a button you love, your brain doesn’t just admire it anymore. It files it away as something you can reproduce.


Conclusion: turning “inspiration” into engineering patterns

How do you build a browser extension that scans web pages for button elements without breaking layout?

You treat the DOM like a data source, filter candidates with measurable constraints, clone the winner, sanitize the styles into something portable, and store the result with clear limits.

That’s the whole trick. Not magic, not scraping the world—just careful observation and thoughtful extraction.

ahsan

ahsan

Hello! I am Mr Ahsan, the writer of the Website. I am from Netherland. I like to write about technology and the news around it.

Comments (0)

No comments yet. Be the first to respond!

Leave a Comment

Your comment will be visible after review.