---
title: "Using Tiptap with the React Composable API"
description: "Learn how to use Tiptap's composable React API with the declarative Tiptap component for a streamlined integration experience."
canonical_url: "https://tiptap.dev/docs/guides/react-composable-api"
---

# Using Tiptap with the React Composable API

Learn how to use Tiptap's composable React API with the declarative Tiptap component for a streamlined integration experience.

Tiptap provides a declarative `<Tiptap>` component that simplifies editor setup and automatically provides context to all child components. This composable API is an alternative to the hook-based `useEditor` + `<EditorContent />` approach, offering a more React-idiomatic way to work with Tiptap.

## When to use the Composable API

The composable API is ideal when you:

- Want a more declarative, component-based approach
- Need to access the editor from multiple child components
- Prefer automatic context management over manual prop passing
- Are building complex editor UIs (toolbars, menus, sidebars, blocks)

For simpler use cases or when you need more direct control, the [hook-based useEditor approach](https://tiptap.dev/docs/editor/getting-started/install/react.md) may be more appropriate.

## Installation

Before using the composable API, make sure you have Tiptap installed in your React project. Follow the [React installation guide](https://tiptap.dev/docs/editor/getting-started/install/react.md) to set up the required dependencies.

## Using the Tiptap component

The `<Tiptap>` component is the root provider that makes the editor instance available to all child components via React context.

### Basic setup

Create a new React component and import the `Tiptap` component along with `useEditor` from `@tiptap/react` (menu components are imported from `@tiptap/react/menus`):

```tsx
// src/Editor.tsx
"use client"

import { Tiptap, useEditor } from '@tiptap/react'
import { BubbleMenu, FloatingMenu } from '@tiptap/react/menus'
import StarterKit from '@tiptap/starter-kit'

function Editor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  if (!editor) return null

  return (
    <Tiptap editor={editor}>
      <Tiptap.Content />

      <BubbleMenu editor={editor}>
        <button>Bold</button>
        <button>Italic</button>
      </BubbleMenu>

      <FloatingMenu editor={editor}>
        <button>Add heading</button>
      </FloatingMenu>
    </Tiptap>
  )
}

export default Editor
```

### Available subcomponents

| Component        | Description                                                                    |
| ---------------- | ------------------------------------------------------------------------------ |
| `Tiptap.Content` | Renders the editor content area. Replaces `<EditorContent editor={editor} />`. |

Menu components are imported separately from `@tiptap/react/menus`.

## Accessing the editor in child components

One of the main benefits of the composable API is that child components can access the editor without prop drilling.

### Using the useTiptap hook

The `useTiptap` hook returns the editor instance from context.

```tsx
import { useTiptap } from '@tiptap/react'

function MenuBar() {
  const { editor } = useTiptap()

  if (!editor) return null

  return (
    <div className="menu-bar">
      <button
        onClick={() => editor.chain().focus().toggleBold().run()}
        className={editor.isActive('bold') ? 'is-active' : ''}
      >
        Bold
      </button>
      <button
        onClick={() => editor.chain().focus().toggleItalic().run()}
        className={editor.isActive('italic') ? 'is-active' : ''}
      >
        Italic
      </button>
    </div>
  )
}
```

Then include the menu bar anywhere inside your `<Tiptap>` component:

```tsx
<Tiptap editor={editor}>
  <MenuBar />
  <Tiptap.Content />
</Tiptap>
```

### Using useTiptapState for reactive state

For performance-sensitive components, use `useTiptapState` to subscribe to specific parts of the editor state. This prevents unnecessary re-renders when unrelated state changes.

```tsx
import { useTiptapState } from '@tiptap/react'

function WordCount() {
  const { editor } = useTiptap()

  const wordCount = useTiptapState((state) => {
    const text = state.editor.state.doc.textContent
    return text.split(/\s+/).filter(Boolean).length
  })

  if (!editor) {
    return null
  }

  return <span>{wordCount} words</span>
}
```

The selector function receives an `EditorStateSnapshot` and should return only the data your component needs. The component will only re-render when the selected value changes.

## Server-side rendering (SSR)

The composable API works seamlessly with server-side rendering. Since the editor instance is only created on the client, you can guard rendering until it exists:

```tsx
"use client"

import { Tiptap, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

export function MyEditor() {
  const editor = useEditor({
    extensions: [StarterKit],
    content: '<p>Hello World!</p>',
  })

  if (!editor) {
    return <div className="skeleton">Loading editor...</div>
  }

  return (
    <Tiptap editor={editor}>
      <Tiptap.Content />
    </Tiptap>
  )
}
```

## Performance considerations

The composable API is designed with performance in mind:

- **Automatic context optimization**: The editor context is memoized to prevent unnecessary re-renders
- **Selective subscriptions**: Use `useTiptapState` to subscribe only to the state you need

For more performance tips, see the [React Performance Guide](https://tiptap.dev/docs/guides/performance.md).

## Backwards compatibility

The `<Tiptap>` component automatically provides the `EditorContext`, which means you can use the `useCurrentEditor` hook inside it for backwards compatibility with existing code:

```tsx
import { useCurrentEditor } from '@tiptap/react'

function EditorJSONPreview() {
  const { editor } = useCurrentEditor()

  if (!editor) return null

  return <pre>{JSON.stringify(editor.getJSON(), null, 2)}</pre>
}
```

However, for new code, we recommend using `useTiptap()`.

## API Reference

### Tiptap component

The root provider component that makes the editor instance available via React context.

**Props:**

| Prop       | Type             | Description                            |
| ---------- | ---------------- | -------------------------------------- |
| `editor`   | `Editor \| null` | The editor instance from `useEditor()` |
| `children` | `ReactNode`      | Child components                       |

**Example:**

```tsx
<Tiptap editor={editor}>
  <Tiptap.Content />
</Tiptap>
```

### useTiptap hook

Returns the Tiptap context value.

**Returns:**

| Property | Type             | Description         |
| -------- | ---------------- | ------------------- |
| `editor` | `Editor \| null` | The editor instance |

**Example:**

```tsx
const { editor } = useTiptap()

if (!editor) return null
```

### useTiptapState hook

Subscribes to a slice of the editor state using a selector function.

**Signature:**

```tsx
const value = useTiptapState(selector, equalityFn?)
```

**Parameters:**

| Parameter    | Type                                | Description                                      |
| ------------ | ----------------------------------- | ------------------------------------------------ |
| `selector`   | `(state: EditorStateSnapshot) => T` | Function to select state                         |
| `equalityFn` | `(a: T, b: T) => boolean`           | Optional equality function to control re-renders |

**Example:**

```tsx
const isBold = useTiptapState((state) =>
  state.editor.isActive('bold')
)
```

## Comparison: Composable API vs Hook-based Approach

| Feature                | Composable API                         | Hook-based Approach                 |
| ---------------------- | -------------------------------------- | ----------------------------------- |
| Setup complexity       | Low – declarative components           | Medium – manual prop passing        |
| Context management     | Automatic                              | Manual via EditorContext.Provider   |
| Child component access | Easy via `useTiptap()`                 | Requires prop drilling or context   |
| SSR support            | Guarded render (`if (!editor)`)        | Manual null checks                  |
| Performance            | Optimized with `useTiptapState`        | Optimized with `useEditorState`     |
| Best for               | Complex UIs with many child components | Simple UIs or direct control needed |

## Next steps

- [Optimize your React integration](https://tiptap.dev/docs/guides/performance.md)
- [Configure your editor](https://tiptap.dev/docs/editor/getting-started/configure.md)
- [Add styles to your editor](https://tiptap.dev/docs/editor/getting-started/style-editor.md)
- [Learn more about Tiptap concepts](https://tiptap.dev/docs/editor/core-concepts/introduction.md)
