
React Grab
- 220 installs
- 40 repo stars
- Updated August 4, 2026
- akillness/oh-my-skills
Extract React component trees, props, and JSX from running pages or bundles to accelerate UI refactors, design parity, and component library migrations.
About
Helps developers and agents pull React component structure, props, and JSX from applications to rebuild screens faster, audit UI implementations, and migrate components into new design systems with less manual inspection.
- Component hierarchy capture from live apps
- Props and state surface inspection
- JSX snippet extraction for reuse
- Design-to-code parity checks
- Migration aid for legacy React codebases
React Grab by the numbers
- 220 all-time installs (skills.sh)
- Ranked #840 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akillness/oh-my-skills --skill react-grabAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 220 |
|---|---|
| repo stars | ★ 40 |
| Last updated | August 4, 2026 |
| Repository | akillness/oh-my-skills ↗ |
What it does
Extract React component trees, props, and JSX from running pages or bundles to accelerate UI refactors, design parity, and component library migrations.
Files
react-grab — Browser element context for AI agents
Keyword:react-grab·grab·element context
>
Point at a live React UI element, capture the exact component/file/HTML context, and feed it
straight into Claude Code, Cursor, Copilot, Codex, Gemini, or an MCP workflow.
When to use this skill
- Install
react-grabin a React project so agents can copy exact component context from the browser - Choose between clipboard flow, MCP flow, and plugin/API flow for React element capture
- Fix
react-grabsetup issues like wrong load point, missing file paths, or empty MCP responses - Add or remove an agent integration (
claude-code,cursor,codex,gemini,mcp, etc.) - Build a custom plugin or use the primitives API for a project-specific workflow
Do not use this skill for:
- Fresh-session browser automation, auth flows, or disposable browser verification →
browser-harness - Running-browser reuse with existing cookies/extensions/logins →
playwriter - Human feedback / annotation packets on rendered UI →
agentation - React performance diagnosis, rerender churn, hydration, bundle, or RSC/client-boundary work →
react-best-practices - General UI-system / token / component-library design work →
design-system
Instructions
Step 1: Classify the request
Choose one primary mode before giving commands:
1. Clipboard install/use — user wants to hover, press Cmd/Ctrl+C, and paste context into an AI agent. 2. MCP workflow — user wants the agent to call a tool like get_element_context after browser selection. 3. Plugin/API workflow — user wants custom actions, lifecycle hooks, formatting, or primitives like freeze(). 4. Troubleshooting/removal — user wants to fix setup problems or remove the integration cleanly.
Step 2: Confirm prerequisites truthfully
Use the upstream project as source of truth:
npx grab@latest initis the default install path.- Upstream
package.jsoncurrently requires Node.js >=22. - The browser overlay is a development-only tool; never present it as a production feature.
If the user’s environment is older than Node 22, say so explicitly before giving the install path.
Step 3: Give the shortest correct path
Mode A — Clipboard install/use
Use this as the default path unless the user explicitly asks for MCP or plugins:
# At the project root
npx -y grab@latest initThen explain the runtime flow: 1. Start the dev server. 2. Open the app in the browser. 3. Hover the target element. 4. Press Cmd+C (Mac) or Ctrl+C (Windows/Linux). 5. Paste the captured context into the AI agent.
Clipboard output should include:
- component name
- source file path
- line/column when available
- HTML snippet
If the user needs framework-specific setup, use references/install-and-routing.md.
Mode B — MCP workflow
When the user wants programmatic access from the agent instead of manual paste:
npx -y grab@latest add mcpExplain the real flow clearly: 1. User selects or hovers the element in the browser overlay. 2. The MCP server exposes the latest selection. 3. The AI agent calls get_element_context. 4. The tool returns file path, component, stack, and HTML context.
If the user asks how this differs from clipboard flow:
- Clipboard is manual paste into chat.
- MCP is tool-based retrieval after selection.
Use references/agent-workflows.md for agent-specific install and routing guidance.
Mode C — Plugin/API workflow
When the user wants custom behavior, automation, or a deeper integration:
- Route installation and workflow choice first.
- Then point to:
references/api.mdfor full API surfacereferences/agent-workflows.mdfor practical plugin/MCP patterns
Common primitives to mention:
getElementContext(element)freeze()/unfreeze()openFile(filePath, lineNumber?)registerPlugin(...)
Do not dump the full API unless the user asked for the deep path.
Mode D — Troubleshooting/removal
If the user wants fixes or teardown:
- Use
references/troubleshooting.mdfor symptom-first triage. - For removal, use upstream CLI removal commands:
npx -y grab@latest remove <agent>Step 4: Keep the boundary sharp
Keep react-grab focused on React element context capture from the browser.
Route out when the user is actually asking for:
- general browser automation →
browser-harnessorplaywriter - UI review / visual annotation →
agentation - performance / profiling / hydration / bundle / RSC issues →
react-best-practices - reusable component-system design →
design-systemorui-component-patterns
Step 5: Prefer reference docs over bloating the front door
After classifying the mode, load only the matching support doc:
references/install-and-routing.mdreferences/agent-workflows.mdreferences/troubleshooting.mdreferences/api.md
Keep the main answer short unless the user explicitly asks for the long path.
Examples
Example 1: install in a Next.js app and use clipboard flow
User: “Set up react-grab in my Next.js app so I can copy a component into Claude Code.”
Response shape: 1. Confirm Node 22+ prerequisite. 2. Use npx -y grab@latest init as the default path. 3. Explain dev-server → browser hover → Cmd/Ctrl+C → paste into Claude Code. 4. Only load framework-specific setup if auto-detect is not viable.
Example 2: MCP setup for agent tool access
User: “Install react-grab MCP so the agent can grab the selected button context.”
Response shape: 1. Confirm Node 22+ prerequisite. 2. Run npx -y grab@latest add mcp. 3. Explain that the browser selection happens first, then the agent calls get_element_context. 4. Route generic browser-control questions to browser-harness / playwriter if needed.
Example 3: custom plugin workflow
User: “Create a react-grab plugin that adds a Jira action to the context menu.”
Response shape: 1. Keep react-grab as the plugin/API lane. 2. Use registerPlugin(...) with an action and optional hooks. 3. Point to references/api.md for the full hook surface. 4. Keep the example focused on the requested action rather than reproducing the whole API.
Example 4: fix setup drift
User: “grab init keeps failing on my machine.”
Response shape: 1. Check Node version first and call out the Node 22+ requirement. 2. Confirm they are running from the project root. 3. Use references/troubleshooting.md for overlay/load/MCP/path failures. 4. Route unrelated framework build issues to the appropriate frontend skill.
Best practices
1. State the Node.js >=22 prerequisite truthfully before recommending install commands. 2. Default to `npx -y grab@latest init` unless the user explicitly needs manual framework setup. 3. Keep it dev-only — never imply this belongs in production builds. 4. Explain clipboard vs MCP clearly so users do not confuse paste-based and tool-based workflows. 5. Route broad browser or UI-review work out early instead of letting react-grab absorb adjacent skills. 6. Use support docs for depth instead of pasting long installation or API manuals into the main response.
References
- react-grab GitHub
- react-grab.com
- Install and routing guide
- Agent workflows
- Troubleshooting
- Full API reference
- Helper scripts —
install.shandadd-agent.sh
{
"skill_name": "react-grab",
"evals": [
{
"id": 1,
"prompt": "Set up react-grab in my Next.js app so I can copy component context into Claude Code.",
"expected_output": "Default to `npx -y grab@latest init`, state the Node.js >=22 prerequisite, and explain the hover + Cmd/Ctrl+C clipboard workflow.",
"assertions": [
"States the Node.js >=22 prerequisite before or alongside install guidance",
"Uses `npx -y grab@latest init` as the default install path",
"Explains dev-server -> hover element -> Cmd/Ctrl+C -> paste into Claude Code"
]
},
{
"id": 2,
"prompt": "Install react-grab MCP so my agent can fetch the selected component context without pasting.",
"expected_output": "Use `npx -y grab@latest add mcp` and explain browser selection first, MCP tool call second.",
"assertions": [
"Shows `npx -y grab@latest add mcp`",
"Explains that the user must select or hover an element in the browser before the MCP tool returns context",
"Distinguishes MCP retrieval from the clipboard workflow"
]
},
{
"id": 3,
"prompt": "Create a react-grab plugin that adds a Jira action to the context menu.",
"expected_output": "Stay in the react-grab plugin/API lane, use `registerPlugin(...)`, and keep the example focused on the requested action.",
"assertions": [
"Uses `registerPlugin` from `react-grab`",
"Includes an action with id, label, shortcut, and `onAction`",
"Preserves the react-grab boundary instead of rerouting the whole request to a generic browser skill"
]
},
{
"id": 4,
"prompt": "`grab init` fails on Node 20 on my machine. What should I check?",
"expected_output": "Call out the Node.js >=22 prerequisite, then route through project-root and dev-mode troubleshooting.",
"assertions": [
"Explicitly says Node 20 is below the supported requirement",
"States that the current upstream requirement is Node.js >=22",
"Checks project-root or dev-mode / load-order troubleshooting next"
]
},
{
"id": 5,
"prompt": "I need to automate an authenticated browser flow and keep my existing login cookies. Should I use react-grab?",
"expected_output": "Say no for the core job, route to `playwriter` or `browser-harness`, and keep react-grab limited to element-context capture.",
"assertions": [
"Routes authenticated running-browser reuse to `playwriter` or fresh-session automation to `browser-harness`",
"Explains that react-grab is for React element-context capture rather than general browser automation",
"Keeps the route-out explicit instead of absorbing the adjacent workflow"
]
}
]
}
react-grab agent workflows
The three practical workflows
1. Clipboard workflow
Best for: quick human-in-the-loop use with Claude Code, Cursor, Copilot Chat, Codex, or Gemini.
npx -y grab@latest initFlow: 1. Start the app in development mode. 2. Hover the UI element in the browser. 3. Press Cmd+C (Mac) or Ctrl+C (Windows/Linux). 4. Paste the captured context into the coding agent.
Expected payload:
- component name
- file path
- line/column when available
- HTML snippet
Use this as the default path unless the user explicitly wants MCP.
2. MCP workflow
Best for: agent tool calls after browser selection.
npx -y grab@latest add mcpFlow: 1. User selects or hovers an element in the browser. 2. The local MCP server stores/exposes the latest selection. 3. The agent calls get_element_context. 4. The tool returns structured context for the selected element.
Clarify this if needed:
- MCP does not replace the browser-side selection step.
- It replaces manual clipboard paste after selection.
3. Plugin/API workflow
Best for: project-specific customization, workflow automation, or custom actions.
Use when the user wants to:
- add a Jira/Figma/GitHub action to the context menu
- transform copied content before it hits the clipboard
- override open-file behavior
- freeze the page for hover-only or transient UI states
- integrate element capture into tests or custom scripts
Minimal plugin example:
import { registerPlugin } from "react-grab";
registerPlugin({
name: "jira-action",
actions: [
{
id: "jira",
label: "Create Jira Ticket",
shortcut: "J",
onAction: async (context) => {
await createJiraTicket({
filePath: context.filePath,
componentName: context.componentName,
html: context.element.outerHTML,
});
context.hideContextMenu();
},
},
],
});Agent-specific commands
npx -y grab@latest add claude-code
npx -y grab@latest add cursor
npx -y grab@latest add copilot
npx -y grab@latest add codex
npx -y grab@latest add gemini
npx -y grab@latest add opencode
npx -y grab@latest add amp
npx -y grab@latest add droid
npx -y grab@latest add mcpRemoval:
npx -y grab@latest remove <agent>Boundary reminders
Use react-grab for exact React element context.
Route out when the user really wants:
- browser navigation, auth, screenshots, or scripted flow control →
browser-harnessorplaywriter - rendered-UI markup/annotation and review packets →
agentation - performance triage on the React app itself →
react-best-practices
Common answer shapes
“I want to feed a selected button to Claude Code”
- confirm Node 22+
- recommend
grab init - explain clipboard flow
“I want the agent to pull the selected component without paste”
- confirm Node 22+
- recommend
grab add mcp - explain browser-selection-first, tool-call-second
“I need a custom context-menu action”
- stay in
react-grab - show
registerPlugin(...) - route full hook/API detail to
references/api.md
react-grab API Reference
Full TypeScript API for react-grab v0.1.x
Source: https://github.com/aidenybai/react-grab
---
ReactGrabAPI
The primary API object — stored as window.__REACT_GRAB__ and returned by init().
interface ReactGrabAPI {
// Activation control
activate(): void; // Show the overlay
deactivate(): void; // Hide the overlay
toggle(): void; // Toggle visibility
comment(): void; // Enter prompt/comment mode
isActive(): boolean; // Is overlay currently shown?
isEnabled(): boolean; // Is react-grab enabled at all?
setEnabled(enabled: boolean): void;
// Toolbar state
getToolbarState(): ToolbarState | null;
setToolbarState(state: Partial<ToolbarState>): void;
onToolbarStateChange(callback: (state: ToolbarState) => void): () => void;
// Element operations
copyElement(elements: Element | Element[]): Promise<boolean>;
getSource(element: Element): Promise<SourceInfo | null>;
getStackContext(element: Element): Promise<string>;
getDisplayName(element: Element): string | null;
// State
getState(): ReactGrabState;
setOptions(options: SettableOptions): void;
// Plugin system
registerPlugin(plugin: Plugin): void;
unregisterPlugin(name: string): void;
getPlugins(): string[];
// Lifecycle
dispose(): void;
}Access the API
import { getGlobalApi } from "react-grab";
const api = getGlobalApi(); // Returns null if not initialized---
init() — Configuration
import { init } from "react-grab";
init(options?: Options);interface Options {
enabled?: boolean; // Enable/disable entirely (default: true)
activationMode?: ActivationMode; // "toggle" | "hold" (default: "toggle")
keyHoldDuration?: number; // Duration in ms for "hold" mode
allowActivationInsideInput?: boolean;
maxContextLines?: number; // Limit React component stack depth
activationKey?: ActivationKey; // string or (event: KeyboardEvent) => boolean
getContent?: (elements: Element[]) => Promise<string> | string;
freezeReactUpdates?: boolean; // Pause React state while active (default: true)
}
type ActivationMode = "toggle" | "hold";
type ActivationKey = string | ((event: KeyboardEvent) => boolean);Examples:
// Custom activation key
init({ activationKey: "g" }); // Cmd+G / Ctrl+G
// Hold mode (show while key is held)
init({ activationMode: "hold", keyHoldDuration: 500 });
// Custom content formatter
init({
getContent: async (elements) => {
const { generateSnippet } = await import("react-grab");
const snippets = await generateSnippet(elements);
return snippets.join("\n---\n");
},
});
// Disable initially, enable later
init({ enabled: false });
getGlobalApi()?.setEnabled(true);---
Primitives API
Standalone utilities available without the default overlay UI.
import { getElementContext, freeze, unfreeze, isFreezeActive, openFile }
from "react-grab/primitives";
// or: from "react-grab/core"getElementContext(element)
getElementContext(element: Element): Promise<ReactGrabElementContext>Returns comprehensive context for any DOM element:
interface ReactGrabElementContext {
element: Element; // The DOM element itself
htmlPreview: string; // Compact HTML snippet
stackString: string; // React component stack as formatted string
stack: StackFrame[]; // Parsed stack frames
componentName: string | null; // Nearest React component display name
fiber: Fiber | null; // React internal fiber
selector: string | null; // CSS selector for the element
styles: string; // Relevant computed CSS properties
}
interface StackFrame {
functionName: string;
fileName: string;
lineNumber: number;
columnNumber: number;
}Example:
const button = document.querySelector('.submit-btn');
const context = await getElementContext(button);
console.log(context.componentName); // "SubmitButton"
console.log(context.selector); // "button.submit-btn"
console.log(context.stackString);
// " in SubmitButton (at Button.tsx:12:5)
// in Form (at Form.tsx:30:3)"
console.log(context.stack[0]);
// { functionName: "SubmitButton", fileName: "Button.tsx", lineNumber: 12, columnNumber: 5 }freeze() / unfreeze()
freeze(elements?: Element[]): void // Halt React updates, pause animations
unfreeze(): void // Restore normal behavior
isFreezeActive(): boolean // Check if freeze is activeFreeze pauses:
- React state updates (prevents re-renders)
- CSS transitions and animations
- Preserves
:hover/:focuspseudo-states
openFile(filePath, lineNumber?)
openFile(filePath: string, lineNumber?: number): Promise<void>Opens the file in your editor. Tries Vite/Next.js dev server endpoints first, falls back to vscode:// protocol URL.
generateSnippet(elements, options?)
generateSnippet(
elements: Element[],
options?: { maxLines?: number }
): Promise<string[]>Takes an array of Elements and returns an array of formatted context strings.
---
Plugin API
Plugin interface
interface Plugin {
name: string;
theme?: DeepPartial<Theme>;
options?: SettableOptions;
actions?: PluginAction[]; // ContextMenuAction[] | ToolbarMenuAction[]
hooks?: PluginHooks;
setup?: (api: ReactGrabAPI, hooks: ActionContextHooks) => PluginConfig | void;
}Register / unregister
import { registerPlugin, unregisterPlugin } from "react-grab";
registerPlugin(plugin: Plugin): void
unregisterPlugin(name: string): voidPluginHooks — all lifecycle hooks
interface PluginHooks {
onActivate?: () => void;
onDeactivate?: () => void;
onElementHover?: (element: Element) => void;
onElementSelect?: (element: Element) => boolean | void | Promise<boolean>;
// return true from onElementSelect to cancel default copy behavior
onDragStart?: (startX: number, startY: number) => void;
onDragEnd?: (elements: Element[], bounds: DragRect) => void;
onBeforeCopy?: (elements: Element[]) => void | Promise<void>;
transformCopyContent?: (content: string, elements: Element[]) => string | Promise<string>;
onAfterCopy?: (elements: Element[], success: boolean) => void;
onCopySuccess?: (elements: Element[], content: string) => void;
onCopyError?: (error: Error) => void;
onStateChange?: (state: ReactGrabState) => void;
onPromptModeChange?: (isPromptMode: boolean, context: PromptModeContext) => void;
// Visual hooks
onSelectionBox?: (visible: boolean, bounds: OverlayBounds | null, element: Element | null) => void;
onDragBox?: (visible: boolean, bounds: OverlayBounds | null) => void;
onGrabbedBox?: (bounds: OverlayBounds, element: Element) => void;
onElementLabel?: (visible: boolean, variant: ElementLabelVariant, context: ElementLabelContext) => void;
onCrosshair?: (visible: boolean, context: CrosshairContext) => void;
onContextMenu?: (element: Element, position: { x: number; y: number }) => void;
// Transform hooks
onOpenFile?: (filePath: string, lineNumber?: number) => boolean | void;
transformHtmlContent?: (html: string, elements: Element[]) => string | Promise<string>;
transformAgentContext?: (context: AgentContext, elements: Element[]) => AgentContext | Promise<AgentContext>;
transformActionContext?: (context: ActionContext) => ActionContext;
transformOpenFileUrl?: (url: string, filePath: string, lineNumber?: number) => string;
transformSnippet?: (snippet: string, element: Element) => string | Promise<string>;
}Context Menu Action
interface ContextMenuAction {
id: string;
label: string;
shortcut?: string; // Keyboard shortcut label shown in UI
target?: "context-menu"; // Omit to place in context menu (default)
enabled?: boolean | ((context: ActionContext) => boolean);
onAction: (context: ContextMenuActionContext) => void | Promise<void>;
agent?: AgentOptions;
}
interface ContextMenuActionContext extends ActionContext {
element: Element;
elements: Element[];
filePath?: string;
lineNumber?: number;
componentName?: string;
tagName?: string;
enterPromptMode?: (agent?: AgentOptions) => void;
hideContextMenu: () => void;
cleanup: () => void;
}Toolbar Action
interface ToolbarMenuAction {
id: string;
label: string;
shortcut?: string;
target: "toolbar"; // Required — places in the floating toolbar
enabled?: boolean | (() => boolean);
isActive?: () => boolean;
onAction: () => void | Promise<void>;
}---
Theme API
interface Theme {
enabled?: boolean; // Toggle entire overlay (default: true)
hue?: number; // Base color 0-360 HSL (default: 0 = red)
selectionBox?: { enabled?: boolean }; // Hover highlight box
dragBox?: { enabled?: boolean }; // Click-drag selection area
grabbedBoxes?: { enabled?: boolean }; // Success flash on copy
elementLabel?: { enabled?: boolean }; // Floating cursor label
crosshair?: { enabled?: boolean }; // Crosshair overlay
toolbar?: { enabled?: boolean }; // Floating activation toolbar
}
// Default values
const DEFAULT_THEME: Required<Theme> = {
enabled: true,
hue: 0,
selectionBox: { enabled: true },
dragBox: { enabled: true },
grabbedBoxes: { enabled: true },
elementLabel: { enabled: true },
crosshair: { enabled: true },
toolbar: { enabled: true },
};---
ReactGrabState
Runtime state of the overlay:
interface ReactGrabState {
isActive: boolean; // Overlay is showing
isDragging: boolean; // User is drag-selecting
isCopying: boolean; // Copy in progress
isPromptMode: boolean; // AI prompt mode active
isCrosshairVisible: boolean;
isSelectionBoxVisible: boolean;
isDragBoxVisible: boolean;
targetElement: Element | null; // Currently hovered element
dragBounds: DragRect | null;
grabbedBoxes: Array<{
id: string;
bounds: OverlayBounds;
createdAt: number;
}>;
labelInstances: Array<{
id: string;
status: SelectionLabelStatus;
tagName: string;
componentName?: string;
createdAt: number;
}>;
selectionFilePath: string | null;
toolbarState: ToolbarState | null;
}---
ToolbarState
interface ToolbarState {
edge: "top" | "bottom" | "left" | "right"; // Which screen edge
ratio: number; // Position along edge (0-1)
collapsed: boolean; // Toolbar minimized
enabled: boolean; // Toolbar visible
}---
SourceInfo
interface SourceInfo {
filePath: string;
lineNumber: number | null;
componentName: string | null;
}---
AgentProvider (custom AI backend)
Connect any AI service to react-grab's prompt mode:
interface AgentProvider<T = unknown> {
send(context: AgentContext<T>, signal: AbortSignal): AsyncIterable<string>;
resume?(sessionId: string, signal: AbortSignal, storage: AgentSessionStorage): AsyncIterable<string>;
abort?(sessionId: string): Promise<void>;
supportsResume?: boolean;
supportsFollowUp?: boolean;
dismissButtonText?: string;
checkConnection?(): Promise<boolean>;
getCompletionMessage?(): string | undefined;
undo?(): Promise<void>;
canUndo?(): boolean;
redo?(): Promise<void>;
canRedo?(): boolean;
}
interface AgentContext<T = unknown> {
content: string[]; // Array of context strings (HTML + component stacks)
prompt: string; // User instruction
options?: T;
sessionId?: string;
}Custom provider example:
const myProvider: AgentProvider = {
send: async function*(context, signal) {
const response = await fetch("/api/ai", {
method: "POST",
body: JSON.stringify({ context: context.content, prompt: context.prompt }),
signal,
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
yield decoder.decode(value);
}
},
supportsFollowUp: true,
};
registerPlugin({
name: "my-ai",
actions: [{
id: "ask-ai",
label: "Ask My AI",
onAction: (ctx) => {
ctx.enterPromptMode?.({ provider: myProvider });
},
}],
});---
TypeScript Types — Full Export List
export type {
Options,
SettableOptions,
ReactGrabAPI,
SourceInfo,
Theme,
ReactGrabState,
ToolbarState,
OverlayBounds,
GrabbedBox,
DragRect,
Rect,
DeepPartial,
ElementLabelVariant,
PromptModeContext,
CrosshairContext,
ElementLabelContext,
AgentContext,
AgentSession,
AgentProvider,
AgentSessionStorage,
AgentOptions,
AgentCompleteResult,
ActivationMode,
ContextMenuAction,
ContextMenuActionContext,
ToolbarMenuAction,
PluginAction,
ActionContext,
ActionContextHooks,
Plugin,
PluginConfig,
PluginHooks,
}---
Package Structure
| Import path | Contents |
|---|---|
react-grab | Main entry — init, getGlobalApi, registerPlugin, unregisterPlugin, generateSnippet, built-in plugins |
react-grab/core | Core primitives + API — same as react-grab/primitives |
react-grab/primitives | Standalone: getElementContext, freeze, unfreeze, isFreezeActive, openFile |
react-grab/styles.css | Stylesheet (if needed) |
---
Built-in Plugins
import { commentPlugin, openPlugin } from "react-grab";
// commentPlugin — adds "Comment" action to context menu
// openPlugin — adds "Open in editor" action to context menu
registerPlugin(commentPlugin);
registerPlugin(openPlugin);---
MCP Server (@react-grab/mcp)
The MCP server exposes one tool:
`get_element_context`
- Returns the most recently selected element's context from the browser overlay
- Context is consumed once then cleared (one-shot)
- Returns:
{ componentName, filePath, lineNumber, htmlPreview, stackString, selector }
Setup: npx -y grab@latest add mcp
Transport: Supports both stdio (for editor MCP clients) and HTTP (Streamable HTTP, default port configurable).
---
Source: https://github.com/aidenybai/react-grab · License: MIT
react-grab install and routing guide
Default installation path
Use the upstream CLI from the project root:
npx -y grab@latest initWhy this is the default:
- upstream README positions
initas the primary install path - it auto-detects framework/project shape
- it keeps the main skill from teaching too many manual snippets by default
Prerequisites
Before recommending install steps, confirm:
- Node.js >=22 (upstream
package.jsonrequirement) - project root context (where
next.config.*,vite.config.*, or equivalent lives) - development-only usage; this should not ship to production
When to stay on the default path
Stay with grab init when the user says things like:
- “set up react-grab”
- “install it in my app”
- “I want to copy element context into Claude/Cursor/Codex/Gemini”
- “make the component picker work”
When to use framework-specific setup
Only drop to manual instructions when:
grab initcannot detect the app correctly- the user needs to patch a specific entry point
- the user is working inside a custom setup and asks for the exact insertion point
Next.js App Router
Add to app/layout.tsx:
import Script from "next/script";
export default function RootLayout({ children }) {
return (
<html>
<head>
{process.env.NODE_ENV === "development" && (
<Script
src="//unpkg.com/react-grab/dist/index.global.js"
crossOrigin="anonymous"
strategy="beforeInteractive"
/>
)}
</head>
<body>{children}</body>
</html>
);
}Next.js Pages Router
Add to pages/_document.tsx:
import Script from "next/script";
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head>
{process.env.NODE_ENV === "development" && (
<Script
src="//unpkg.com/react-grab/dist/index.global.js"
crossOrigin="anonymous"
strategy="beforeInteractive"
/>
)}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}Vite
Add to the entry path or top-level module loaded in development:
if (import.meta.env.DEV) {
import("react-grab");
}Webpack
Install the package if needed, then gate it to development:
npm install --save-dev react-grabif (process.env.NODE_ENV === "development") {
import("react-grab");
}Routing boundaries
Use react-grab when the real need is React element context capture.
Route elsewhere when the real need is:
- fresh disposable browser automation →
browser-harness - running-browser reuse / existing auth state →
playwriter - rendered UI review / annotation packets →
agentation - React performance / hydration / RSC / bundle issues →
react-best-practices - component-system / token / UI-architecture work →
design-systemorui-component-patterns
Quick decision table
| Situation | Best move |
|---|---|
| User just wants react-grab installed | npx -y grab@latest init |
| Auto-detect fails or user needs exact insertion point | Use manual framework snippet |
| User wants tool-based element retrieval | grab add mcp |
| User wants a custom action or hook | Plugin/API path |
| User is really asking for browser automation or visual review | Route out to adjacent skills |
react-grab troubleshooting
First checks
Before deeper debugging, verify:
- Node.js is >=22
- install happened from the project root
- the app is running in development mode
- the overlay is loaded before the relevant React tree renders
Symptom → likely cause → fix
| Symptom | Likely cause | Fix |
|---|---|---|
| Overlay never appears | not loaded in development path, or disabled globally | confirm dev-only insertion point and check window.__REACT_GRAB_DISABLED__ |
Component name is null | overlay loaded too late or React/source-map context unavailable | load earlier (beforeInteractive in Next.js) and stay in dev mode |
| File path missing | source maps or development metadata unavailable | verify dev build / source maps / unminified environment |
| Clipboard copy works poorly on transient hover UI | target disappears before capture | use freeze() / unfreeze() primitives workflow |
| MCP tool returns empty | no recent browser-side selection | select/hover element first, then call get_element_context |
grab init fails immediately | old Node version or broken package-manager environment | upgrade to Node 22+ and retry from repo root |
| Agent-specific install feels wrong for browser automation | user actually wants browser control, not element capture | route to browser-harness or playwriter |
Quick commands
# Check Node version
node --version
# Retry default install from project root
npx -y grab@latest init
# Add MCP workflow
npx -y grab@latest add mcp
# Remove an integration
npx -y grab@latest remove <agent>When to escalate out of react-grab
Escalate when the issue is really about:
- full browser automation, auth state, navigation, screenshots, or replay →
browser-harness/playwriter - visual review packets, layout comments, rendered annotations →
agentation - React performance, hydration, bundle size, or rerender churn →
react-best-practices
Truthfulness rule
Do not keep repeating the local helper-script story if upstream has moved. For prerequisite disputes, prefer the current upstream raw sources:
README.mdfor install shapepackage.jsonfor runtime requirements
#!/usr/bin/env bash
# react-grab add-agent script
# Connects react-grab to a specific AI coding agent
#
# Usage:
# bash scripts/add-agent.sh <agent>
# bash scripts/add-agent.sh mcp
# bash scripts/add-agent.sh claude-code
# bash scripts/add-agent.sh cursor
#
# Supported agents:
# claude-code, cursor, copilot, codex, gemini, opencode, amp, droid, mcp
set -euo pipefail
SUPPORTED_AGENTS=(
"claude-code"
"cursor"
"copilot"
"codex"
"gemini"
"opencode"
"amp"
"droid"
"mcp"
)
show_help() {
echo "Usage: bash scripts/add-agent.sh <agent>"
echo ""
echo "Supported agents:"
for agent in "${SUPPORTED_AGENTS[@]}"; do
echo " $agent"
done
echo ""
echo "Examples:"
echo " bash scripts/add-agent.sh claude-code"
echo " bash scripts/add-agent.sh cursor"
echo " bash scripts/add-agent.sh mcp"
echo ""
echo "Remove an integration:"
echo " npx -y grab@latest remove <agent>"
}
if [[ $# -eq 0 ]] || [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
show_help
exit 0
fi
AGENT="$1"
# Validate agent name
VALID=false
for a in "${SUPPORTED_AGENTS[@]}"; do
if [[ "$AGENT" == "$a" ]]; then
VALID=true
break
fi
done
if [[ "$VALID" == "false" ]]; then
echo "❌ Unknown agent: $AGENT"
echo ""
show_help
exit 1
fi
# Check Node.js
if ! command -v node &>/dev/null; then
echo "❌ Node.js is required. Install from https://nodejs.org/ (>=22 required)"
exit 1
fi
NODE_VERSION=$(node --version | sed 's/v//' | cut -d. -f1)
if [[ "$NODE_VERSION" -lt 22 ]]; then
echo "❌ Node.js >=22 is required (current: $(node --version))"
exit 1
fi
# Check npx
if ! command -v npx &>/dev/null; then
echo "❌ npx not found. Run: npm install -g npx"
exit 1
fi
echo "🔗 Connecting react-grab to: $AGENT"
echo ""
# Run grab add
npx -y grab@latest add "$AGENT"
echo ""
echo "✅ react-grab connected to $AGENT!"
echo ""
case "$AGENT" in
claude-code)
echo "📋 Usage with Claude Code:"
echo " 1. Start your dev server"
echo " 2. Hover over an element and press Cmd+C"
echo " 3. Paste the clipboard content into your Claude Code prompt"
echo " 4. Claude now has exact file path + component + HTML context"
;;
mcp)
echo "📋 Usage with MCP:"
echo " 1. The react-grab MCP server runs locally"
echo " 2. Your AI agent can call get_element_context tool"
echo " 3. Select an element in browser first, then call the tool"
;;
cursor)
echo "📋 Usage with Cursor:"
echo " 1. Select element in browser with react-grab"
echo " 2. Paste clipboard into Cursor chat"
;;
*)
echo "📋 Select an element in the browser, then paste the clipboard into $AGENT."
;;
esac
echo ""
echo "To remove this integration: npx -y grab@latest remove $AGENT"
#!/usr/bin/env bash
# react-grab install script
# Installs react-grab in the current project via the grab CLI
#
# Usage:
# bash scripts/install.sh # auto-detect framework
# bash scripts/install.sh --framework next-app # Next.js App Router
# bash scripts/install.sh --framework next-pages # Next.js Pages Router
# bash scripts/install.sh --framework vite # Vite
# bash scripts/install.sh --framework webpack # Webpack
#
# Requirements: Node.js >=22, npm/npx
set -euo pipefail
FRAMEWORK=""
YES_FLAG=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--framework|-f)
FRAMEWORK="$2"
shift 2
;;
--yes|-y)
YES_FLAG="--yes"
shift
;;
--help|-h)
echo "Usage: bash scripts/install.sh [--framework <next-app|next-pages|vite|webpack>] [--yes]"
exit 0
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Check Node.js
if ! command -v node &>/dev/null; then
echo "❌ Node.js is required. Install from https://nodejs.org/ (>=22 required)"
exit 1
fi
NODE_VERSION=$(node --version | sed 's/v//' | cut -d. -f1)
if [[ "$NODE_VERSION" -lt 22 ]]; then
echo "❌ Node.js >=22 is required (current: $(node --version))"
exit 1
fi
# Check npx
if ! command -v npx &>/dev/null; then
echo "❌ npx not found. Run: npm install -g npx"
exit 1
fi
echo "📦 Installing react-grab..."
echo ""
if [[ -n "$FRAMEWORK" ]]; then
# Manual framework mode: install package, then add script tag
echo "🔧 Framework: $FRAMEWORK"
# Detect package manager
PM="npm"
if [[ -f "bun.lockb" ]] || command -v bun &>/dev/null && [[ -f "package.json" ]]; then
PM="bun"
elif [[ -f "pnpm-lock.yaml" ]]; then
PM="pnpm"
elif [[ -f "yarn.lock" ]]; then
PM="yarn"
fi
echo "📦 Package manager: $PM"
# Install react-grab package
case "$PM" in
bun) bun add -d react-grab ;;
pnpm) pnpm add -D react-grab ;;
yarn) yarn add --dev react-grab ;;
*) npm install --save-dev react-grab ;;
esac
echo ""
echo "✅ react-grab installed as dev dependency"
echo ""
echo "📋 Add the following to your project entry point:"
echo ""
case "$FRAMEWORK" in
next-app)
cat << 'SNIPPET'
// In app/layout.tsx:
import Script from 'next/script'
// Inside your root layout component:
{process.env.NODE_ENV === "development" && (
<Script
src="//unpkg.com/react-grab/dist/index.global.js"
crossOrigin="anonymous"
strategy="beforeInteractive"
/>
)}
SNIPPET
;;
next-pages)
cat << 'SNIPPET'
// In pages/_document.tsx:
import Script from 'next/script'
// Inside _document Body:
{process.env.NODE_ENV === "development" && (
<Script
src="//unpkg.com/react-grab/dist/index.global.js"
crossOrigin="anonymous"
strategy="beforeInteractive"
/>
)}
SNIPPET
;;
vite)
cat << 'SNIPPET'
<!-- In index.html: -->
<script type="module">
if (import.meta.env.DEV) {
await import('//unpkg.com/react-grab/dist/index.global.js');
}
</script>
SNIPPET
;;
webpack)
cat << 'SNIPPET'
// In your webpack entry file:
if (process.env.NODE_ENV === 'development') {
import('react-grab');
}
SNIPPET
;;
*)
echo "Unknown framework: $FRAMEWORK"
echo "Supported: next-app, next-pages, vite, webpack"
;;
esac
else
# Auto-detect mode (recommended)
echo "🔍 Auto-detecting framework..."
npx -y grab@latest init $YES_FLAG
fi
echo ""
echo "✅ react-grab installation complete!"
echo ""
echo "Next steps:"
echo " 1. Start your dev server (npm run dev / bun dev)"
echo " 2. Open your app in the browser"
echo " 3. Hover over any element and press Cmd+C (Mac) or Ctrl+C (Win/Linux)"
echo " 4. Paste the context into your AI coding agent!"
echo ""
echo "Add to an AI agent: bash scripts/add-agent.sh <agent>"
echo " Supported: claude-code, cursor, copilot, codex, gemini, opencode, amp, mcp"
N:react-grab
D:[react-grab] Capture exact React element context from a live browser UI and hand it to an AI coding agent as component name, source file path, line number, and HTML. Use for react-grab install/use, clipboard-to-agent copy, MCP-backed element selection, or plugin/API customization. Not for generic browser automation (browser-harness/playwriter), rendered-UI review (agentation), React performance audits (react-best-practices), or general design-system work.
G:react-grab grab element-context ai-agent clipboard component-inspector dev-tools react browser mcp component-source browser-component-picker plugin freeze
U[5]:
Install react-grab in a React project so agents can copy exact component context from the browser
Choose between clipboard flow, MCP flow, and plugin/API flow for React element capture
Fix react-grab setup issues like wrong load point, missing file paths, or empty MCP responses
Add or remove a specific agent integration (claude-code, cursor, codex, gemini, mcp, etc.)
Build a custom plugin or use primitives like freeze() and getElementContext()
S[4]{n,action,details}:
1,Classify,choose clipboard install/use vs MCP vs plugin/API vs troubleshooting/removal
2,Prereqs,confirm upstream truth: Node.js >=22 and development-only browser overlay
3,Default path,use npx -y grab@latest init unless the user explicitly needs MCP or manual framework setup
4,Route,send generic browser automation to browser-harness/playwriter and UI review to agentation
R[5]:
Use npx -y grab@latest init as the default install path from project root
For tool-based retrieval after browser selection use npx -y grab@latest add mcp
Keep react-grab dev-only; never position it as a production feature
Use references/install-and-routing.md, references/agent-workflows.md, references/troubleshooting.md, and references/api.md for depth
State the Node.js >=22 prerequisite truthfully before recommending install commands
E[4]{type,command}:
Default install,npx -y grab@latest init
MCP workflow,npx -y grab@latest add mcp
Agent integration,npx -y grab@latest add claude-code
Removal,npx -y grab@latest remove cursor