diff --git a/apps/public/content/docs/sdks/react.mdx b/apps/public/content/docs/sdks/react.mdx
index 81dbe49e7..a3f442f58 100644
--- a/apps/public/content/docs/sdks/react.mdx
+++ b/apps/public/content/docs/sdks/react.mdx
@@ -1,5 +1,325 @@
----
-title: React
----
-Use [script tag](/docs/sdks/script) or [Web SDK](/docs/sdks/web) for now. We'll add a dedicated react sdk soon.
+## About
+
+The React SDK provides a clean, idiomatic way to use OpenPanel in React applications. Built on `@openpanel/sdk`, it follows the same architecture as the React Native SDK and includes:
+
+- ✅ React Context API with `useOpenPanel()` hook
+- ✅ Full TypeScript support
+- ✅ SSR-safe (Next.js App Router & Pages Router)
+- ✅ Zero configuration with environment variables
+- ✅ Re-exports all SDK types and utilities
+
+## Installation
+
+
+### Install dependencies
+
+
+
+ ```bash
+ npm install @openpanel/react
+ ```
+
+
+ ```bash
+ pnpm add @openpanel/react
+ ```
+
+
+ ```bash
+ yarn add @openpanel/react
+ ```
+
+
+
+This package requires React 18+ or React 19+.
+
+### Initialize
+
+Wrap your app with the `OpenPanelProvider` in your root component.
+
+
+
+ ```tsx title="app/layout.tsx"
+ import { OpenPanelProvider } from '@openpanel/react';
+
+ export default function RootLayout({
+ children
+ }: {
+ children: React.ReactNode
+ }) {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+ }
+ ```
+
+
+ ```tsx title="pages/_app.tsx"
+ import { OpenPanelProvider } from '@openpanel/react';
+ import type { AppProps } from 'next/app';
+
+ export default function App({ Component, pageProps }: AppProps) {
+ return (
+
+
+
+ );
+ }
+ ```
+
+
+ ```tsx title="main.tsx"
+ import { OpenPanelProvider } from '@openpanel/react';
+ import { App } from './App';
+
+ export function Root() {
+ return (
+
+
+
+ );
+ }
+ ```
+
+
+
+### Using Environment Variables
+
+The provider automatically reads from environment variables if no `clientId` prop is provided:
+
+```tsx
+
+ {children}
+
+```
+
+Create a `.env.local` file (Next.js) or `.env` file:
+
+```bash title=".env.local"
+NEXT_PUBLIC_OPENPANEL_CLIENT_ID=your_client_id_here
+```
+
+The SDK checks for (in order):
+1. `NEXT_PUBLIC_OPENPANEL_CLIENT_ID` (Next.js client-side)
+2. `OPENPANEL_CLIENT_ID` (server-side or other environments)
+
+#### Options
+
+
+
+
+
+
+## Usage
+
+### Client Components
+
+Use the `useOpenPanel()` hook in any component wrapped by the provider:
+
+```tsx title="components/subscribe-button.tsx"
+'use client';
+
+import { useOpenPanel } from '@openpanel/react';
+
+export function SubscribeButton() {
+ const op = useOpenPanel();
+
+ const handleClick = () => {
+ op?.track('button_clicked', {
+ button_name: 'subscribe',
+ page: 'homepage'
+ });
+ };
+
+ return ;
+}
+```
+
+### Tracking Events
+
+Track custom events with properties:
+
+```tsx
+const op = useOpenPanel();
+
+op?.track('purchase', {
+ product_id: 'prod-123',
+ amount: 99.99,
+ currency: 'USD'
+});
+```
+
+### Identifying Users
+
+Identify users to associate events with specific profiles:
+
+```tsx
+const op = useOpenPanel();
+
+op?.identify({
+ profileId: 'user-123',
+ email: 'user@example.com',
+ name: 'John Doe',
+ properties: {
+ plan: 'premium',
+ company: 'Acme Inc'
+ }
+});
+```
+
+### Setting Global Properties
+
+Set properties that will be sent with every event:
+
+```tsx
+const op = useOpenPanel();
+
+op?.setGlobalProperties({
+ app_version: '1.0.2',
+ environment: 'production'
+});
+```
+
+### Incrementing Properties
+
+Increment a numeric property on a user profile:
+
+```tsx
+const op = useOpenPanel();
+
+op?.increment({
+ profileId: 'user-123',
+ property: 'page_views',
+ value: 1
+});
+```
+
+### Decrementing Properties
+
+Decrement a numeric property on a user profile:
+
+```tsx
+const op = useOpenPanel();
+
+op?.decrement({
+ profileId: 'user-123',
+ property: 'credits',
+ value: 5
+});
+```
+
+### Clearing User Data
+
+Clear the current user's data (useful for logout):
+
+```tsx
+const op = useOpenPanel();
+
+op?.clear();
+```
+
+## Server-Side Rendering (SSR)
+
+The provider is SSR-safe and works seamlessly with Next.js:
+
+- ✅ OpenPanel client only initializes in the browser
+- ✅ No hydration errors
+- ✅ Safe to use in Server Components (wrap client components that use the hook)
+
+### Example with Server Component
+
+```tsx title="app/page.tsx"
+import { AnalyticsButton } from './analytics-button';
+
+export default function Page() {
+ return (
+
+ );
+}
+```
+
+```tsx title="app/analytics-button.tsx"
+'use client';
+
+import { useOpenPanel } from '@openpanel/react';
+
+export function AnalyticsButton() {
+ const op = useOpenPanel();
+
+ return (
+
+ );
+}
+```
+
+## TypeScript Support
+
+All types from `@openpanel/sdk` are re-exported for your convenience:
+
+```tsx
+import {
+ useOpenPanel,
+ type OpenPanelOptions,
+ type TrackProperties
+} from '@openpanel/react';
+
+const properties: TrackProperties = {
+ product_id: 'prod-123',
+ amount: 99.99
+};
+
+const op = useOpenPanel();
+op?.track('purchase', properties);
+```
+
+## Advanced Usage
+
+### Server-Side Tracking (Next.js)
+
+For server-side event tracking in Next.js, use the JavaScript SDK directly:
+
+```tsx title="utils/op.ts"
+import { OpenPanel } from '@openpanel/sdk';
+
+export const opServer = new OpenPanel({
+ clientId: 'your-client-id',
+ clientSecret: 'your-client-secret',
+});
+```
+
+Then use it in server components or API routes:
+
+```tsx title="app/api/subscribe/route.ts"
+import { opServer } from '@/utils/op';
+
+export async function POST() {
+ await opServer.track('user_subscribed', {
+ plan: 'premium'
+ });
+
+ return Response.json({ success: true });
+}
+```
+
+For more information on server-side tracking, refer to the [Next.js SDK](/docs/sdks/nextjs#server-side) documentation.
\ No newline at end of file
diff --git a/packages/sdks/react/index.tsx b/packages/sdks/react/index.tsx
new file mode 100644
index 000000000..6d73dc4bc
--- /dev/null
+++ b/packages/sdks/react/index.tsx
@@ -0,0 +1,110 @@
+/**
+ * OpenPanel React Provider
+ *
+ * A React context provider for OpenPanel analytics that provides a clean,
+ * idiomatic way to use OpenPanel in React/Next.js applications.
+ *
+ * This is temporary until OpenPanel publishes an official React/Next.js package.
+ *
+ * @see https://openpanel.dev
+ */
+
+'use client';
+
+import type { OpenPanelOptions } from '@openpanel/sdk';
+import { OpenPanel as OpenPanelBase } from '@openpanel/sdk';
+import { type ReactNode, createContext, useContext, useRef } from 'react';
+
+export * from '@openpanel/sdk';
+
+/**
+ * React-specific OpenPanel client
+ */
+export class OpenPanel extends OpenPanelBase {
+ constructor(options: OpenPanelOptions) {
+ super({
+ ...options,
+ sdk: 'react',
+ sdkVersion: '19.0.0',
+ });
+ }
+}
+
+/**
+ * Configuration options for the OpenPanel provider
+ */
+interface OpenPanelProviderProps extends Omit {
+ /** React children to render */
+ children: ReactNode;
+ /** OpenPanel client ID. Falls back to environment variables if not provided */
+ clientId?: string;
+}
+
+const OpenPanelContext = createContext(null);
+
+/**
+ * Hook to access the OpenPanel client instance
+ *
+ * @throws {Error} If used outside of OpenPanelProvider
+ * @returns OpenPanel client instance or null if not initialized
+ *
+ * @example
+ * ```tsx
+ * const openpanel = useOpenPanel();
+ * openpanel?.track('button_clicked', { label: 'Subscribe' });
+ * ```
+ */
+export function useOpenPanel() {
+ const context = useContext(OpenPanelContext);
+ if (context === undefined) {
+ throw new Error('useOpenPanel must be used within OpenPanelProvider');
+ }
+ return context;
+}
+
+/**
+ * Provider component that initializes and provides OpenPanel to child components
+ *
+ * Automatically reads from environment variables:
+ * - NEXT_PUBLIC_OPENPANEL_CLIENT_ID
+ * - OPENPANEL_CLIENT_ID
+ *
+ * @param children - React children to render
+ * @param clientId - OpenPanel client ID. Falls back to environment variables if not provided
+ * @param options - Additional OpenPanel configuration options
+ *
+ * @example
+ * ```tsx
+ *
+ *
+ *
+ * ```
+ */
+export function OpenPanelProvider({
+ children,
+ clientId,
+ ...options
+}: OpenPanelProviderProps) {
+ const openpanelRef = useRef(null);
+
+ if (!openpanelRef.current && typeof window !== 'undefined') {
+ const id =
+ clientId ||
+ process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID ||
+ process.env.OPENPANEL_CLIENT_ID ||
+ '';
+
+ if (id) {
+ openpanelRef.current = new OpenPanel({
+ clientId: id,
+ ...options,
+ });
+ }
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/sdks/react/package.json b/packages/sdks/react/package.json
new file mode 100644
index 000000000..c158e847a
--- /dev/null
+++ b/packages/sdks/react/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@openpanel/react",
+ "version": "1.0.1-local",
+ "module": "index.ts",
+ "scripts": {
+ "build": "rm -rf dist && tsup",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@openpanel/sdk": "workspace:1.0.0-local"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "devDependencies": {
+ "@openpanel/tsconfig": "workspace:*",
+ "@types/node": "catalog:",
+ "@types/react": "catalog:",
+ "tsup": "^7.2.0",
+ "typescript": "catalog:"
+ }
+}
diff --git a/packages/sdks/react/tsconfig.json b/packages/sdks/react/tsconfig.json
new file mode 100644
index 000000000..fa4341f12
--- /dev/null
+++ b/packages/sdks/react/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@openpanel/tsconfig/base.json",
+ "compilerOptions": {
+ "incremental": false,
+ "outDir": "dist"
+ },
+ "exclude": ["dist"]
+}
diff --git a/packages/sdks/react/tsup.config.ts b/packages/sdks/react/tsup.config.ts
new file mode 100644
index 000000000..ce043ebef
--- /dev/null
+++ b/packages/sdks/react/tsup.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from 'tsup';
+
+export default defineConfig({
+ format: ['cjs', 'esm'],
+ entry: ['index.tsx'],
+ external: ['react', 'react-dom'],
+ dts: true,
+ splitting: false,
+ sourcemap: false,
+ clean: true,
+ minify: true,
+});