<project title="React Grab" summary="React Grab aims to accelerate developer workflows by enabling quick context extraction from web pages or IDEs. By allowing users to capture HTML, React components, or source code with a single interaction, it reduces the time spent manually copying and pasting content. The tool is especially beneficial for AI-powered coding assistants, potentially improving their performance by up to 55%. The project also supports customization, extensibility, and integration across multiple frameworks (Next.js, Vite, Webpack) and includes a browser extension for seamless in-browser use.">**Remember:**
- React Grab
- Context extraction
- AI development tools
- Cursor, Claude Code, Copilot integration
- Element selection and copying
- Customizable UI themes<api><doc title="Classnames" desc="docs page.">type ClassValue = string | number | boolean | undefined | null;

export const cn = (...classes: ClassValue[]): string => {
  return classes.filter(Boolean).join(" ");
};





</doc><doc title="Is Capitalized" desc="docs page.">export const isCapitalized = (value: string): boolean =>
  value.length > 0 && /^[A-Z]/.test(value);</doc><doc title="Route" desc="API reference.">import packageJson from "react-grab/package.json";

const headers = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "*",
  "Access-Control-Allow-Headers": "*",
  "Cache-Control": "no-store, no-cache, must-revalidate",
};

export function GET() {
  return new Response(packageJson.version, {
    headers,
  });
}

export function OPTIONS() {
  return new Response(null, {
    headers,
  });
}</doc></api><packages><doc title="README" desc="install &amp; quickstart."># <img src="https://github.com/aidenybai/react-grab/blob/main/.github/public/logo.png?raw=true" width="60" align="center" /> React Grab

[![size](https://img.shields.io/bundlephobia/minzip/react-grab?label=gzip&style=flat&colorA=000000&colorB=000000)](https://bundlephobia.com/package/react-grab)
[![version](https://img.shields.io/npm/v/react-grab?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/react-grab)
[![downloads](https://img.shields.io/npm/dt/react-grab.svg?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/react-grab)

React Grab allows you to select an element and copy its context (like HTML, React component, and file source)

It makes tools like Cursor, Claude Code, Copilot run up to [**55% faster**](https://react-grab.com/blog/intro)

### [Try out a demo! →](https://react-grab.com)

![Demo](https://react-grab.com/demo.gif)

## Install

> [**Install using Cursor**](https://cursor.com/link/prompt?text=1.+Run+curl+-s+https%3A%2F%2Freact-grab.com%2Fllms.txt+%0A2.+Understand+the+content+and+follow+the+instructions+to+install+React+Grab.%0A3.+Tell+the+user+to+refresh+their+local+app+and+explain+how+to+use+React+Grab)

Get started in 1 minute by adding this script tag to your app:

```html
<script
  src="//unpkg.com/react-grab/dist/index.global.js"
  crossorigin="anonymous"
></script>
```

If you're using a React framework or build tool, view instructions below:

#### Next.js (App router)

Add this inside of your `app/layout.tsx`:

```jsx
import Script from "next/script";

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        {/* put this in the <head> */}
        {process.env.NODE_ENV === "development" && (
          <Script
            src="//unpkg.com/react-grab/dist/index.global.js"
            crossOrigin="anonymous"
            strategy="beforeInteractive"
          />
        )}
        {/* rest of your scripts go under */}
      </head>
      <body>{children}</body>
    </html>
  );
}
```

#### Next.js (Pages router)

Add this into your `pages/_document.tsx`:

```jsx
import { Html, Head, Main, NextScript } from "next/document";

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        {/* put this in the <Head> */}
        {process.env.NODE_ENV === "development" && (
          <Script
            src="//unpkg.com/react-grab/dist/index.global.js"
            crossOrigin="anonymous"
            strategy="beforeInteractive"
          />
        )}
        {/* rest of your scripts go under */}
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}
```

#### Vite

Your `index.html` could look like this:

```html
<!doctype html>
<html lang="en">
  <head>
    <script type="module">
      // first npm i react-grab
      // then in head:
      if (import.meta.env.DEV) {
        import("react-grab");
      }
    </script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
```

#### Webpack

First, install React Grab:

```bash
npm install react-grab
```

Then add this at the top of your main entry file (e.g., `src/index.tsx` or `src/main.tsx`):

```tsx
if (process.env.NODE_ENV === "development") {
  import("react-grab");
}
```

## Extending React Grab

React Grab provides an public customization API. Check out the [type definitions](https://github.com/aidenybai/react-grab/blob/main/packages/react-grab/src/types.ts) to see all available options for extending React Grab.

```typescript
import { init } from "react-grab/core";

const api = init({
  theme: {
    enabled: true, // disable all UI by setting to false
    hue: 180, // shift colors by 180 degrees (pink → cyan/turquoise)
    crosshair: {
      enabled: false, // disable crosshair
    },
    elementLabel: {
      // when hovering over an element
      backgroundColor: "#000000",
      textColor: "#ffffff",
    },
  },

  onElementSelect: (element) => {
    console.log("Selected:", element);
  },
  onCopySuccess: (elements, content) => {
    console.log("Copied to clipboard:", content);
  },
  onStateChange: (state) => {
    console.log("Active:", state.isActive);
  },
});

api.activate();
api.copyElement(document.querySelector(".my-element"));
console.log(api.getState());
```

## Resources & Contributing Back

Want to try it out? Check the [our demo](https://react-grab.com).

Looking to contribute back? Check the [Contributing Guide](https://github.com/aidenybai/react-grab/blob/main/CONTRIBUTING.md) out.

Want to talk to the community? Hop in our [Discord](https://discord.com/invite/G7zxfUzkm7) and share your ideas and what you've build with React Grab.

Find a bug? Head over to our [issue tracker](https://github.com/aidenybai/react-grab/issues) and we'll do our best to help. We love pull requests, too!

We expect all contributors to abide by the terms of our [Code of Conduct](https://github.com/aidenybai/react-grab/blob/main/.github/CODE_OF_CONDUCT.md).

[**→ Start contributing on GitHub**](https://github.com/aidenybai/react-grab/blob/main/CONTRIBUTING.md)

### License

React Grab is MIT-licensed open-source software.</doc><doc title="Vite Playground" desc="docs page."><!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
    <script type="module">
      if (import.meta.env.DEV) {
        import("react-grab");
      }
    </script>

    <title>Kitchen Sink - Vite</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html></doc><doc title="Eslint.Config" desc="docs page.">import tseslint from "typescript-eslint";

export default tseslint.config(
  {
    ignores: [
      "node_modules/**",
      "dist/**",
      "eslint.config.mjs",
      "bundled_*.mjs",
      "*.mjs",
      "*.cjs",
      "*.js",
      "*.json",
      "*.md",
    ],
  },
  ...tseslint.configs.recommended,
  ...tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
    rules: {
      "import/order": "off",
    },
  },
);</doc><doc title="Instrumentation Client" desc="docs page.">import "react-grab";</doc><doc title="Instrumentation Client" desc="docs page.">import { init } from "react-grab";

if (typeof window !== "undefined") {
  init({
    onActivate: () => {
      window.dispatchEvent(new CustomEvent("react-grab:activated"));
    },
    onDeactivate: () => {
      window.dispatchEvent(new CustomEvent("react-grab:deactivated"));
    },
  });
}</doc><doc title="Next.Config" desc="docs page.">import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  /* config options here */
};

export default nextConfig;</doc><doc title="Next.Config" desc="docs page.">import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  serverExternalPackages: ["react-grab"],
  // this causes sources to be mangled
  reactCompiler: false,
  productionBrowserSourceMaps: true,
  turbopack: {},
  webpack: (config, { dev, isServer }) => {
    if (!isServer && !dev) {
      config.devtool = "source-map";
    }
    return config;
  },
  redirects: async () => {
    return [
      {
        source: "/benchmarks",
        destination: "/blog/intro",
        permanent: true,
      },
    ];
  },
  rewrites: async () => {
    return {
      beforeFiles: [
        {
          source: "/",
          destination: "/llms.txt",
          has: [
            {
              type: "header",
              key: "accept",
              value: "(.*)text/markdown(.*)",
            },
          ],
        },
        {
          source: "/llm.txt",
          destination: "/llms.txt",
        },
      ],
    };
  },
};

export default nextConfig;</doc><doc title="Tsup.Config" desc="docs page.">import fs from "node:fs";
import { defineConfig, type Options } from "tsup";
// @ts-expect-error -- esbuild-plugin-babel is not typed
import babel from "esbuild-plugin-babel";

const banner = `/**
 * @license MIT
 *
 * Copyright (c) 2025 Aiden Bai
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */`;

const DEFAULT_OPTIONS: Options = {
  banner: {
    js: banner,
  },
  clean: ["**/*", "!styles.css"],
  dts: true,
  entry: [],
  env: {
    NODE_ENV: process.env.NODE_ENV ?? "development",
    VERSION:
      process.env.VERSION ??
      (JSON.parse(fs.readFileSync("package.json", "utf8")) as { version: string })
        .version,
  },
  external: [],
  format: [],
  loader: {
    ".css": "text",
  },
  minify: false,
  noExternal: ["clsx", "tailwind-merge", "solid-js", "bippy"],
  onSuccess: process.env.COPY ? "pbcopy < ./dist/index.global.js" : undefined,
  outDir: "./dist",
  platform: "browser",
  sourcemap: false,
  splitting: false,
  target: "esnext",
  treeshake: true,
};

export default defineConfig([
  {
    ...DEFAULT_OPTIONS,
    entry: ["./src/index.ts"],
    format: ["iife"],
    globalName: "ReactGrab",
    loader: {
      ".css": "text",
    },
    minify: process.env.NODE_ENV === "production",
    outDir: "./dist",
    esbuildPlugins: [
      // eslint-disable-next-line @typescript-eslint/no-unsafe-call -- babel is not typed
      babel({
        filter: /\.(tsx|jsx)$/,
        config: {
          presets: [
            ["@babel/preset-typescript", { onlyRemoveTypeImports: true }],
            "babel-preset-solid",
          ],
        },
      }),
    ],
  },
  {
    ...DEFAULT_OPTIONS,
    clean: false,
    entry: ["./src/index.ts", "./src/core.tsx"],
    format: ["cjs", "esm"],
    loader: {
      ".css": "text",
    },
    outDir: "./dist",
    splitting: true,
    esbuildPlugins: [
      // eslint-disable-next-line @typescript-eslint/no-unsafe-call -- babel is not typed
      babel({
        filter: /\.(tsx|jsx)$/,
        config: {
          presets: [
            ["@babel/preset-typescript", { onlyRemoveTypeImports: true }],
            "babel-preset-solid",
          ],
        },
      }),
    ],
  },
]);</doc><doc title="Vite.Config" desc="docs page.">import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      '@': '/src',
    },
  },
});</doc><doc title="Vite.Config" desc="docs page.">import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import webExtension from 'vite-plugin-web-extension';

export default defineConfig({
  plugins: [
    react(),
    webExtension({
      manifest: './src/manifest.json',
      watchFilePaths: ['src/**/*'],
      browser: 'chrome',
    }),
  ],
  resolve: {
    alias: {
      '@': '/src',
    },
  },
  publicDir: 'public',
});</doc></packages></project>
