Stop saying "which button?" to Claude.
Click any UI element in your running web app → Claude gets the exact source file, line, component, and surrounding context. Built for dense dashboards, unfamiliar codebases, and multi-component bug reports where "fix this" is ambiguous.
Chrome extension + Svelte / React / Vue / Angular build plugins. Local-only. MIT.
Install · How it works · Who is this for? · Spec · Troubleshooting
You'll get 10x value if:
You probably don't need this if:
src/Header.tsx is faster than the capture workflowThe honest caveat: uiref tells Claude where, never what. You still articulate the change in chat. This is the design: location from the tool, intent from the conversation. Tools that try to infer intent from clicks guess badly.
When you tell an AI coding assistant "fix this button" or "change that header," it has to guess which component you mean. In a codebase with dozens of generic wrappers (Card, EchartsWrapper, Button), the guess is often wrong — you spend your turn explaining location instead of intent.
Sending a screenshot doesn't fix this. Even frontier vision models drift 5–30 pixels on coordinate regression, and a screenshot shows the AI what the element looks like, not which source file rendered it.
uiref turns ambiguous deictic pronouns ("this," "that") into structured references.
┌──────────────────────────────────────┐
│ Your app (React / Vue / Svelte / │
│ Angular) with the uiref build │
│ plugin installed. DOM elements get │
│ data-uiref-file / -line / -component│
│ attributes at compile time. │
└──────────────────┬───────────────────┘
│
⌘⇧C · click an element
│
▼
┌──────────────────────────────────────┐
│ uiref Chrome extension │
│ • hover-to-highlight picker │
│ • reads data-uiref-* attrs │
│ • screenshots the element │
│ • writes uiref/v1 JSON to inbox │
└──────────────────┬───────────────────┘
│
│ ~/uiref-inbox/<timestamp>.uiref.json
▼
┌──────────────────────────────────────┐
│ Claude Code + uiref skill │
│ • detects UI-reference language │
│ • reads the latest uiref │
│ • edits target.file:line directly │
└──────────────────────────────────────┘
The flow:
you: [click SaveButton in browser]
[switch to Claude Code]
"make this use the danger variant"
claude: I see you pointed at <SaveButton> at
src/components/SaveButton.tsx:42. Applying now.
[edits the exact file]
Dense dashboards. You have a water-consumption dashboard with 15 widget types. "Fix this chart" is ambiguous. Click it → Claude gets WaterConsumptionChart.svelte:42 and the parent InsightsPage.svelte:120 that configures it. Zero back-and-forth.
Third-party components. "Why is this popover cropped?" on a bits-ui dropdown you've never opened. uiref resolves to the wrapper + your usage site + computed styles. Claude finds the overflow issue without grep.
Bug reports with receipts. Workflow mode captures N clicks, the events (console errors, failed network requests, SPA navigations) between them, and the URL at each step. "This broke somewhere during the signup flow" becomes a replayable, timestamped JSON Claude can reason about directly.
Cross-role collaboration. PMs and designers can capture and hand off without touching code.
_debugSource or Svelte's internal APIs (which break across versions).uiref/v1 JSON format is the contract; any tool can produce or consume it.npx @uiref/setup
Auto-detects your framework (Svelte / React / Vue / Angular) and package manager, installs the matching @uiref/* plugin, patches your build config, copies the Claude Skill to ~/.claude/skills/uiref/, and creates ~/uiref-inbox/. ~20 seconds.
uiref-extension-v0.2.0.zip from the latest release (~30 KB)~/uiref-extension/)chrome://extensions/ → toggle Developer mode (top-right) → Load unpacked → select the unzipped folder(Chrome Web Store submission is in progress — once approved, this becomes a one-click install.)
⌘⇧C (Mac) / Ctrl+Shift+C (Win/Linux) on any page you're dev-serving → click an element → the extension writes a uiref JSON to ~/uiref-inbox/. Switch to Claude Code, say "fix this" — Claude reads the inbox automatically.
On first capture the browser will ask you to pick an inbox folder once; choose ~/uiref-inbox/.
If you'd rather not use the setup CLI, install the plugin yourself. You'll also need to copy the Claude Skill manually (the CLI does this for you):
mkdir -p ~/.claude/skills/uiref
curl -fsSL https://raw.githubusercontent.com/KokXinTan/uiref/main/skill/SKILL.md \
-o ~/.claude/skills/uiref/SKILL.md
mkdir -p ~/uiref-inbox
Then pick your framework:
npm install --save-dev @uiref/svelte
// svelte.config.js
import uiref from '@uiref/svelte';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
export default {
preprocess: [uiref(), vitePreprocess()],
// ...rest of your config
};
The preprocessor runs dev-only by default. Restart your dev server after adding it.
npm install --save-dev @uiref/babel-plugin-react
For Vite + React:
// vite.config.js
import react from '@vitejs/plugin-react';
export default {
plugins: [
react({
babel: {
plugins: [
['@uiref/babel-plugin-react', { enabled: process.env.NODE_ENV !== 'production' }],
],
},
}),
],
};
For Next.js / Create React App: see plugins/react/README.md.
Also copy the Claude Skill:
mkdir -p ~/.claude/skills/uiref
curl -fsSL https://raw.githubusercontent.com/KokXinTan/uiref/main/skill/SKILL.md -o ~/.claude/skills/uiref/SKILL.md
npm install --save-dev @uiref/vue
// vite.config.js
import vue from '@vitejs/plugin-vue';
import uirefVue from '@uiref/vue';
export default {
plugins: [
uirefVue(), // must come before vue()
vue(),
],
};
npm install --save-dev @uiref/angular
See plugins/angular/README.md for the Vite integration.
The skill teaches Claude Code to automatically check the uiref inbox when you reference a UI element.
git clone https://github.com/KokXinTan/uiref.git /tmp/uiref
mkdir -p ~/.claude/skills/uiref
cp /tmp/uiref/skill/SKILL.md ~/.claude/skills/uiref/SKILL.md
Restart Claude Code. Verify by asking Claude "what skills do you have?" — uiref should be in the list.
mkdir -p ~/uiref-inbox
On first capture, the extension will prompt you to pick this folder. Select ~/uiref-inbox. The extension remembers the choice, so you only do this once.
By default, the extension only starts buffering events (console logs, errors, network) once you activate the picker on a tab. For your own projects, you probably want eager buffering (full pre-click history) and GraphQL operation-name extraction (so repeated calls to /graphql are distinguishable).
Add to your app's bootstrap:
if (import.meta.env.DEV && typeof window !== 'undefined') {
window.__uirefConfig = {
eagerPatch: true, // buffer events from page load
captureGraphQLOperation: true, // extract GraphQL operationName from POST bodies
};
}
Safe in production. import.meta.env.DEV is a compile-time constant in Vite. vite build (any --mode) sets it to false, so Rollup's dead-code elimination strips the entire block from the production bundle. The shipped JS contains zero uiref code — no __uirefConfig, no config object, nothing. Verified by grepping the build output.
SvelteKit users: for cleanest placement, use src/hooks.client.ts (runs before any route). For other frameworks:
src/main.tsx, before createRoot()app/layout.tsx (client component) or in a <Script> in app.htmlsrc/main.ts, before app.mount()src/main.ts, before bootstrapApplication()Why this works even though inject.js runs before your app: the extension re-reads window.__uirefConfig dynamically on every event emission, not just at page load. So setting the config late (after your app has started) still enables capture for subsequent events.
See full config options in the spec.
⌘⇧C (Mac) or Ctrl+Shift+C (Windows/Linux).<SaveButton> → Claude."make this red" — or — "explain what this does" — or — "move this to the top"
Claude reads ~/uiref-inbox/ and knows the exact component. It acknowledges the reference and edits the right file on the first try.
| Shortcut | Action |
|---|---|
⌘⇧C / Ctrl+Shift+C |
Activate picker |
Click |
Capture the hovered element |
Esc |
Cancel the picker |
↑ |
Select the parent element |
↓ |
Select the first child element |
⌘⇧C or click "Start picker"Chrome only injects content scripts into tabs loaded after the extension was installed or reloaded. Refresh the tab (⌘R) and try again. If that still doesn't work, open DevTools on the target page and check the Console for errors.
~/.claude/uiref-inbox/The .claude folder is hidden in macOS Finder. Use ~/uiref-inbox/ (no dot) as the default location — that's what the skill looks in. If you really want the hidden folder, press ⌘⇧. in the Finder dialog to show hidden files.
<svelte:head> cannot have attributes compile errorYou're on an older version of @uiref/svelte (pre-0.1.1). Update: npm install -D @uiref/svelte@latest.
Expected token > / parse errors in my Svelte componentsYou're on an older version of @uiref/svelte that didn't handle {(v) => v} style attribute expressions. Update: npm install -D @uiref/svelte@latest.
pnpm install fails or my dev server can't find @uiref/svelteUse the npm version (npm install --save-dev @uiref/svelte) unless you're actively developing the plugin itself.
Checks:
~/uiref-inbox/? (ls ~/uiref-inbox) If not, the extension didn't write — check that you picked the right folder in the extension's first-run dialog.uiref should be listed.It shouldn't — the extension auto-deletes uiref files older than 1 hour on each new capture, and the skill deletes files after using them. If it's still growing, you probably have uirefs captured in the last hour that the skill hasn't consumed. They'll auto-clean on the next capture cycle.
uiref/
├── SPEC.md # uiref/v1 JSON format specification
├── PRIVACY.md # what data the tool handles and where
├── extension/ # Chrome extension (MV3, vanilla JS)
├── plugins/
│ ├── svelte/ # @uiref/svelte — Svelte preprocessor
│ ├── react/ # @uiref/babel-plugin-react
│ ├── vue/ # @uiref/vue — Vite plugin
│ └── angular/ # @uiref/angular — Vite plugin
├── skill/ # Claude Skill (copy to ~/.claude/skills/uiref/)
├── docs/
│ ├── design.md # Architecture and rationale
│ └── chrome-web-store.md # Submission checklist
└── CHANGELOG.md
@uiref scope:@uiref/svelte@uiref/babel-plugin-react@uiref/vue@uiref/angular@uiref/setup (the npx installer)<svelte:*> elements, self-closing components, arrow-function attributes, tags every HTML element, and skips node_modules/.| Tool | What it does | Why uiref is different |
|---|---|---|
| React DevTools | Inspects React components in the browser | No structured output for AI; React-only |
| LocatorJS, click-to-component | Click → open in editor | Different target: editor vs AI. uiref produces a protocol any AI can consume |
| Claude for Chrome | Agentic browser automation | Doesn't resolve DOM → source. Complementary to uiref |
| Claude Design | Design tool for generating prototypes | Different workflow (design → code handoff), not running-app → source |
| ML annotation tools (LabelImg, CVAT, etc.) | Label images for ML training | Wrong output format, wrong audience |
Issues and feature requests welcome. Priority contributions:
See CONTRIBUTING.md for plugin development guidelines.