Component Picker
A Chrome and Firefox extension that turns any element on any site into a bundle an AI can rebuild pixel-perfect — HTML, browser-resolved CSS, forced interaction states, theme pairs and responsive diffs, on one click.
Summary
Component Picker is a Manifest V3 extension that hovers, outlines and captures any element on any website, then puts an AI-ready Markdown bundle on the clipboard. One capture carries the HTML, the browser-resolved CSS diffed against a neutral baseline, forced `:hover` / `:focus-visible` / `:active` states, light/dark theme pairs, mobile and tablet responsive diffs, every `var(--…)` design token named and resolved, running WAAPI/CSS animations plus Framer Motion props and GSAP ScrollTrigger geometry, repeated cards collapsed to one component plus a data table, accessibility roles and WCAG contrast, element screenshots, and a ready-to-paste JSX / Vue / Svelte component. The measurement half runs through a single Chrome DevTools Protocol session; everything else is pure DOM. Nothing leaves the browser — no network, no analytics, no account — except an optional localhost-only MCP bridge that lets a local agent request a capture itself.
Target user
Developers and designers who rebuild UI with AI coding agents — anyone who has pasted a screenshot into Claude Code or Cursor, asked for 'this component in our Next.js project', and got back something that looks approximately right and matches on nothing measurable.
- 01
Built a 47-file, ~5k-line extraction engine that diffs `getComputedStyle` against a neutral `<div>` baseline for box properties and against the *parent* for inherited ones — so the CSS in a bundle is what an author would have written, not a 300-property dump of browser defaults.
- 02
Drove one shared `chrome.debugger` CDP session per capture — `CSS.forcePseudoState` for hover/focus/active, `Emulation.setDeviceMetricsOverride` for mobile 390×844 and tablet 768×1024, media emulation for the other colour scheme and for print / reduced-motion / forced-colors, and `Page.captureScreenshot` per viewport — all inside a single attach, because attaching once per section would flash Chrome's debugger bar five times and re-fire the page's resize handlers between each.
- 03
Resolved every `var(--…)` back to its token name alongside the real computed value, so a bundle carries both the portable truth (`var(--gray-a3)`) and the pixel truth (`rgba(176, 199, 217, 0.145)`) — the half that maps onto the target project's own design system, and the half that proves it.
- 04
Shipped selective copy (v1.8): the capture is recorded as named `{ id, title, body }` sections at assembly time, so the drawer can tick individual sections, show a live preview of exactly what a copy will produce, prepend a prompt, and report the KB — replacing a 400 KB wall of Markdown with a targeted paste.
- 05
Wrote a dependency-free ZIP writer (CRC-32 table, store-only) so the side panel can hand back a folder of the component's images, fonts and background assets with the HTML rewritten to local paths.
- 06
Shipped an MCP server (`pick_component`, `last_capture`) over an opt-in localhost WebSocket bridge, so an agent can ask for a capture and wait for the human to click — the only feature in the product that opens a socket, off by default, badged in-page while a pick is in flight.
- 07
Detected running motion three ways at once: WAAPI/CSS animations from the DOM, and — from a MAIN-world probe the page can actually see — Framer Motion props, GSAP tweens with their ScrollTrigger `start`/`end`/`scrub`/`pin` geometry, full Lottie `animationData` JSON, and anime.js instances; a `<canvas>` gets its engine and version named with a plain statement that a GPU-drawn scene has no code to extract.
- 08
Collapsed repeated structure — a grid of twelve cards becomes one card plus a table of what differs — and mapped output onto the user's own component inventory, so a paste produces `<Card>` and `<Button variant={…}>` instead of div soup.
- 09
Shipped fifteen tagged releases in 48 hours against a public roadmap board, closing 78 of 80 issues, each with its own milestone, changelog entry and loadable zip.
The content script sees the DOM, the page's MAIN world sees React fibers and `window.gsap`, and the service worker is the only one that can talk to the debugger — and none of them can call into another. Every payload crossing a boundary is declared once in `src/shared/types.ts`, so a rename on one side is a compile error on the other instead of an `undefined` three worlds away. The MAIN-world probe is serialised by `executeScript`, which means it must be fully self-contained — no imports, no closure over anything in its own file — and elements arrive tagged with a `data-cp-tmp` index because a DOM reference cannot cross the boundary either.
Capturing at click time measures the site's `:hover` styles as if they were the resting state, so every hover diff came back empty and every resting capture was subtly wrong. The fix is the first CDP call of every capture: `Input.dispatchMouseEvent` parks the mouse at (0,0) before the desktop snapshot, and the interaction states are then forced deliberately via `CSS.forcePseudoState` on every element the site actually has a state rule for.
`test/fixture.html` declared no charset, so on a `file://` page Chrome decoded the bundled script with the legacy windows-1252 fallback and every em dash became `â€"` — including the ones the section parsers match on. The fixture's own expected strings were mis-decoded identically, so the assertions stayed green while the parsers matched nothing against real captured text. The fixture now declares UTF-8, and a guard asserts that every section with a visual form produces one from a real capture rather than from a literal typed into the test.
Firefox has no `chrome.debugger`, DevTools being open on the tab steals the debugger session, cross-origin stylesheets cannot be read at all, and fast mode deliberately skips the attach. Each of those removes real sections from a bundle — so the bundle says which ones are missing and why, in place, rather than handing an agent a confidently incomplete capture it will happily rebuild from.
- L01
A capture is only as useful as what it admits it does not have.
An AI given a bundle with a silently missing hover state rebuilds a component with no hover state and no one notices until it ships; a bundle that says `Responsive and states unavailable: DevTools is open on this tab` costs one line and turns a wrong answer into a fixable one.
- L02
Cap by what stays pasteable, not by what fits.
The bundle stops at 300 elements and ~180 KB — but screenshots are appended *after* the cap rather than competing with the CSS for room, because trading structural truth for a picture is exactly the wrong trade for the consumer.
- L03
The feature that made the product discoverable was not a capability.
Through v1.5 everything was behind a keyboard shortcut and a toolbar click; v1.6 put a glass dock on the page and v1.7 put it on every page by default, and nothing about the extraction engine changed. Capability was never the constraint — knowing the thing was there was.
- L04
Make the boundary payloads the contract and the compiler does the integration testing.
Three worlds, one `types.ts`, and every cross-world bug that survived was a semantic one — never a shape mismatch.
- releases shipped
- 15
- extraction engine loc
- 4957
- source files
- 47
- icon signatures
- 1733
- bundle element cap
- 300
- browsers supported
- 2