# Emend Editor with AI Bubble Menu

> A Tiptap editor with AI Bubble Menu. The default starter.

A shadcn/ui block from the [@emend](https://getemend.vercel.app/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/emend/emend-editor).

- Page, with a live preview: https://shadcn.exchange/r/emend/emend-editor
- Screenshot: https://shadcn.exchange/shot/emend/emend-editor/light.png
- Registry item: https://getemend.vercel.app/r/emend-editor.json
- Listed: 2026-10-24, source last changed 2026-10-24

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/emend/emend-editor.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@emend/ai`
- Registry items: `@emend/emend-editor-base`, `@emend/ai-bubble-menu`

## Files

### `registry/blocks/emend-editor/emend-editor.tsx`

```tsx
"use client"

import type { Editor } from "@tiptap/core"
import type { EmendTransport } from "@emend/ai"
import { useEditorAi } from "@emend/ai/react"
import { EmendAi } from "@emend/ai/tiptap"
import { useMemo, type ReactNode } from "react"
import { AiBubbleMenuView } from "@/components/emend/ai-bubble-menu"
import { EmendEditorBase, type EmendEditorBaseProps } from "./emend-editor-base"
import { createEmendContextProjection } from "./extensions"

export interface EmendEditorProps extends Omit<
  EmendEditorBaseProps,
  | "renderWorkspace"
  | "onAssistantToggle"
  | "assistantOpen"
  | "onComposerToggle"
  | "composerOpen"
> {
  readonly transport?: EmendTransport
}

export function EmendEditor({ transport, ...props }: EmendEditorProps) {
  const extensions = useMemo(
    () => [EmendAi, ...(props.extensions ?? [])],
    [props.extensions]
  )
  return (
    <EmendEditorBase
      {...props}
      extensions={extensions}
      renderWorkspace={
        transport
          ? (editor, content) => (
              <EditorAi editor={editor} transport={transport}>
                {content}
              </EditorAi>
            )
          : undefined
      }
    />
  )
}

function EditorAi({
  children,
  editor,
  transport,
}: {
  readonly children: ReactNode
  readonly editor: Editor
  readonly transport: EmendTransport
}) {
  const contextProjection = useMemo(
    () => createEmendContextProjection(editor),
    [editor]
  )
  const session = useEditorAi({
    editor,
    transport,
    previewMode: "inline",
    contextProjection,
  })
  return (
    <div className="emend-editor__workspace">
      <div className="emend-editor__main">
        <div className="emend-editor__content">{children}</div>
        <AiBubbleMenuView editor={editor} session={session} />
      </div>
    </div>
  )
}
```

### `registry/blocks/emend-editor/index.ts`

```ts
export { EmendEditor, type EmendEditorProps } from "./emend-editor"
export { createEmendEditorExtensions } from "./extensions"
export type { EditorImageUpload } from "./editor-image-control"
export { editorMentionItems, type EditorMentionItem } from "./editor-mention"
export type { EditorToolbarVariant } from "./editor-toolbar"
```
