> ## Documentation Index
> Fetch the complete documentation index at: https://layermetry.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# React.js ImageEditor Integration

> Complete guide for integrating the ImageEditor component into your React application

<Note>
  This guide is for **React.js** applications (Create React App, Vite, custom setups).

  For **Next.js**, see the [Next.js ImageEditor Integration](/docs/integration-guides/nextjs-image-editor) guide.
</Note>

<Warning>
  **This page still describes the version 1 setup.** In version 2
  (`@layermetry/media-editor` 2.0.0) React is no longer pinned to 18.2.0 — React 18
  and React 19 both work, and the editor also runs with no React at all, so
  Next.js 15 is fine.

  Follow [Installation](/docs/installation) for the version 2 setup. This page is being
  rewritten.
</Warning>

## Prerequisites

<CardGroup cols={2}>
  <Card title="Node.js 18+" icon="node-js">
    Node.js 18 or newer
  </Card>

  <Card title="React 18 or 19" icon="react">
    React 18 or React 19 — both work. Version 2 does not pin React.
  </Card>
</CardGroup>

## Installation

<Steps>
  <Step title="Install the SDK">
    ```bash theme={null}
    npm install @layermetry/media-editor
    ```
  </Step>

  <Step title="Install Required Dependencies">
    ```bash theme={null}
    npm install framer-motion lucide-react
    ```
  </Step>

  <Step title="Verify React Version">
    Check your React version:

    ```bash theme={null}
    npm list react
    ```

    If you need to install React 18.2.0:

    ```bash theme={null}
    npm install react@18.2.0 react-dom@18.2.0
    ```
  </Step>
</Steps>

## Basic Setup

### Import SDK Styles

Add the SDK CSS to your application entry point:

<CodeGroup>
  ```javascript src/index.js theme={null}
  import React from 'react';
  import ReactDOM from 'react-dom/client';
  import './index.css';
  import App from './App';

  // Import media-editor SDK styles
  import '@layermetry/media-editor/dist/index.css';

  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>
  );
  ```

  ```javascript src/App.js theme={null}
  import React from 'react';
  import '@layermetry/media-editor/dist/index.css';
  import './App.css';

  function App() {
    // Your component code
  }
  ```
</CodeGroup>

### Import the Editor

```javascript theme={null}
import { ImageEditor } from '@layermetry/media-editor';
```

## ImageEditor Interface

### Core Props

```typescript theme={null}
interface ImageEditorProps {
  // Required
  licenseKey: string;              // Your SDK license key (JWT)
  onClose: () => void;             // Called when user closes editor

  // Optional
  apiUrl?: string;                 // Override license validation API URL
  files?: File;                    // Initial image file to load
  callback?: (result: CallbackProps, extras?: EditorExtras) => void;
  theme?: Record<string, string>;  // Custom theme colors
  showThemeCreator?: boolean;      // Show theme customization UI
  headless?: boolean;              // Enable programmatic mode
  brands?: BrandDetails[];         // Brand presets
  defaultTemplate?: Template;      // Load template on start
}
```

<Accordion title="View Callback Types">
  ```typescript theme={null}
  interface CallbackProps {
    base64: string;        // Exported image as base64 data URL
    width: number;         // Canvas width
    height: number;        // Canvas height
    template?: any;        // Scene data if template was used
  }

  interface EditorExtras {
    thumbnail?: string;    // Thumbnail preview
    format?: string;       // Image format (png, jpg, etc)
  }
  ```
</Accordion>

## Step-by-Step Integration

### Step 1: Create Your Component

```javascript src/components/ImageStudio.js theme={null}
import React, { useState, useCallback, useRef } from 'react';
import { ImageEditor } from '@layermetry/media-editor';
import '@layermetry/media-editor/dist/index.css';

function ImageStudio() {
  const fileInputRef = useRef(null);
  const [selectedFile, setSelectedFile] = useState(null);
  const [showEditor, setShowEditor] = useState(false);
  const [exportedImage, setExportedImage] = useState(null);

  // Continue to Step 2...
}

export default ImageStudio;
```

### Step 2: Implement File Selection

```javascript theme={null}
const handleFileSelect = (e) => {
  const file = e.target.files?.[0];
  if (file && file.type.startsWith('image/')) {
    setSelectedFile(file);
    setShowEditor(true);
  }
};

const handleDrop = (e) => {
  e.preventDefault();
  const file = e.dataTransfer.files?.[0];
  if (file && file.type.startsWith('image/')) {
    setSelectedFile(file);
    setShowEditor(true);
  }
};
```

### Step 3: Implement Callbacks

```javascript theme={null}
const handleExport = useCallback((result, extras) => {
  console.log('Export result:', result, extras);

  if (result.base64) {
    setExportedImage(result.base64);
    setShowEditor(false);
  }
}, []);

const handleClose = () => {
  setShowEditor(false);
  setSelectedFile(null);
};

const handleDownload = () => {
  if (exportedImage) {
    const link = document.createElement('a');
    link.href = exportedImage;
    link.download = `edited-image-${Date.now()}.png`;
    link.click();
  }
};
```

### Step 4: Render the Editor

```javascript theme={null}
return (
  <div className="min-h-screen">
    {/* Upload UI */}
    {!showEditor && (
      <div>
        <input
          ref={fileInputRef}
          type="file"
          accept="image/*"
          onChange={handleFileSelect}
          style={{ display: 'none' }}
        />
        <button onClick={() => fileInputRef.current?.click()}>
          Choose Image
        </button>
      </div>
    )}

    {/* Image Editor */}
    {showEditor && selectedFile && (
      <div style={{ position: 'fixed', inset: 0, zIndex: 40 }}>
        <ImageEditor
          licenseKey="YOUR_LICENSE_KEY_HERE"
          apiUrl="https://your-api.com/social"
          files={selectedFile}
          onClose={handleClose}
          callback={handleExport}
          theme={customTheme}
          showThemeCreator={false}
        />
      </div>
    )}
  </div>
);
```

## Theme Customization

### Creating a Custom Theme

```javascript theme={null}
const customTheme = {
  // Backgrounds
  'background.primary': '#0f172a',
  'background.secondary': '#1e293b',
  'background.tertiary': '#334155',

  // Text
  'text.primary': '#ffffff',
  'text.secondary': '#cbd5e0',

  // Brand colors
  'accent.primary': '#3b82f6',
  'accent.secondary': '#06b6d4',
  'accent.hover': '#60a5fa',

  // Borders
  'border.default': '#334155',
  'border.subtle': '#1e293b',

  // Components
  'button.primary': '#3b82f6',
  'input.background': '#1e293b',
  'toolbar.background': '#0f172a',
  'canvas.background': '#1a202c',
};
```

### Applying the Theme

```javascript theme={null}
<ImageEditor
  licenseKey={licenseKey}
  files={selectedFile}
  onClose={handleClose}
  callback={handleExport}
  theme={customTheme}
  showThemeCreator={false}
/>
```

## Complete Example

<Accordion title="View Full Component Code">
  ```javascript src/components/ImageStudio.js theme={null}
  import React, { useState, useCallback, useRef } from 'react';
  import { ImageEditor } from '@layermetry/media-editor';
  import '@layermetry/media-editor/dist/index.css';
  import './ImageStudio.css';

  const customTheme = {
    'background.primary': '#0f172a',
    'background.secondary': '#1e293b',
    'text.primary': '#ffffff',
    'accent.primary': '#3b82f6',
  };

  function ImageStudio() {
    const fileInputRef = useRef(null);
    const [selectedFile, setSelectedFile] = useState(null);
    const [showEditor, setShowEditor] = useState(false);
    const [exportedImage, setExportedImage] = useState(null);

    const handleFileSelect = (e) => {
      const file = e.target.files?.[0];
      if (file && file.type.startsWith('image/')) {
        setSelectedFile(file);
        setShowEditor(true);
      }
    };

    const handleExport = useCallback((result) => {
      if (result.base64) {
        setExportedImage(result.base64);
        setShowEditor(false);
      }
    }, []);

    const handleClose = () => {
      setShowEditor(false);
      setSelectedFile(null);
    };

    const handleDownload = () => {
      if (exportedImage) {
        const link = document.createElement('a');
        link.href = exportedImage;
        link.download = `edited-image-${Date.now()}.png`;
        link.click();
      }
    };

    return (
      <div className="image-studio">
        {!showEditor && (
          <div className="upload-container">
            <input
              ref={fileInputRef}
              type="file"
              accept="image/*"
              onChange={handleFileSelect}
              style={{ display: 'none' }}
            />
            <button onClick={() => fileInputRef.current?.click()}>
              Choose Image
            </button>
          </div>
        )}

        {showEditor && selectedFile && (
          <div className="editor-overlay">
            <ImageEditor
              licenseKey="YOUR_LICENSE_KEY"
              apiUrl="https://localhost:3030/social"
              files={selectedFile}
              onClose={handleClose}
              callback={handleExport}
              theme={customTheme}
              showThemeCreator={false}
            />
          </div>
        )}

        {exportedImage && (
          <div className="preview">
            <img src={exportedImage} alt="Edited" />
            <button onClick={handleDownload}>Download</button>
          </div>
        )}
      </div>
    );
  }

  export default ImageStudio;
  ```
</Accordion>

## Next Steps

<CardGroup cols={2}>
  <Card title="Video Editor" icon="video" href="/docs/integration-guides/react-video-editor">
    Learn how to integrate the VideoEditor component
  </Card>

  <Card title="FAQ" icon="circle-question" href="/docs/troubleshooting/faq">
    Common issues and troubleshooting
  </Card>
</CardGroup>

<Note>
  Need help? Contact us at [support@layermetry.com](mailto:support@layermetry.com)
</Note>
