Skip to main content
The readonly extension makes the entire editor non-editable, perfect for displaying content that users should view but not modify.

Basic Usage

import { defineReadonly } from '@prosekit/extensions/readonly'

const extension = defineReadonly()
That’s it! The editor is now read-only.

How It Works

The readonly extension sets the ProseMirror editable prop to false, which:
  • Prevents all text input
  • Disables keyboard shortcuts for editing
  • Prevents drag-and-drop
  • Prevents paste operations
  • Allows text selection and copying

Complete Example

import { createEditor } from '@prosekit/core'
import { defineBasicExtension } from '@prosekit/extensions/basic'
import { defineReadonly } from '@prosekit/extensions/readonly'
import { union } from '@prosekit/core'

const extension = union([
  defineBasicExtension(),
  defineReadonly(),
])

const editor = createEditor({
  extension,
  defaultDoc: {
    type: 'doc',
    content: [
      {
        type: 'paragraph',
        content: [{ type: 'text', text: 'This content is read-only.' }],
      },
    ],
  },
})

Conditional Readonly

To conditionally enable/disable readonly mode, create the editor dynamically:
import { createEditor } from '@prosekit/core'
import { defineBasicExtension } from '@prosekit/extensions/basic'
import { defineReadonly } from '@prosekit/extensions/readonly'
import { union } from '@prosekit/core'

function createEditorWithMode(readonly: boolean) {
  const extensions = [defineBasicExtension()]
  
  if (readonly) {
    extensions.push(defineReadonly())
  }
  
  return createEditor({ extension: union(extensions) })
}

// Create editable editor
const editableEditor = createEditorWithMode(false)

// Create readonly editor
const readonlyEditor = createEditorWithMode(true)

React Example with Toggle

import { createEditor } from '@prosekit/core'
import { ProseKit } from '@prosekit/react'
import { defineBasicExtension } from '@prosekit/extensions/basic'
import { defineReadonly } from '@prosekit/extensions/readonly'
import { union } from '@prosekit/core'
import { useMemo, useState } from 'react'

function Editor() {
  const [isReadonly, setIsReadonly] = useState(false)
  
  const editor = useMemo(() => {
    const extensions = [defineBasicExtension()]
    
    if (isReadonly) {
      extensions.push(defineReadonly())
    }
    
    return createEditor({ extension: union(extensions) })
  }, [isReadonly])
  
  return (
    <div>
      <div className="toolbar">
        <button onClick={() => setIsReadonly(!isReadonly)}>
          {isReadonly ? 'Enable Editing' : 'Disable Editing'}
        </button>
      </div>
      
      <ProseKit editor={editor}>
        <div className="editor">
          {/* Editor content */}
        </div>
      </ProseKit>
    </div>
  )
}
Changing the readonly state requires recreating the editor. Plan your extension composition carefully.

Styling Readonly State

Add visual feedback for readonly mode:
/* Add a readonly indicator */
.editor[contenteditable="false"] {
  background-color: #f9fafb;
  border-color: #d1d5db;
  cursor: not-allowed;
}

/* Dim the text slightly */
.editor[contenteditable="false"] * {
  opacity: 0.8;
}

/* Show a lock icon */
.editor[contenteditable="false"]::before {
  content: "🔒";
  position: absolute;
  top: 10px;
  right: 10px;
  opacity: 0.5;
}

Use Cases

Document Preview

Display documents in read-only mode before publishing or approving.

Archive View

Show archived or historical content that shouldn’t be modified.

Permission Control

Restrict editing based on user permissions or roles.

Template Preview

Show document templates without allowing modifications to the original.

User Capabilities

In readonly mode, users can still:
Select text
Copy content
Scroll and navigate
Click on links (if enabled)
View tooltips and UI elements
Users cannot:

Readonly vs. Disabled

The readonly extension is different from disabling the editor:
FeatureReadonlyDisabled
Selection✅ Yes❌ No
Copy✅ Yes❌ No
Scroll✅ Yes⚠️ Depends
Links✅ Clickable❌ Not clickable
Tabbing✅ Can tab to❌ Skipped
Use readonly when you want users to view and interact with the content. Use disabled when you want to completely prevent interaction.

Programmatic Editing

Even in readonly mode, you can still programmatically update the editor:
import { createEditor } from '@prosekit/core'
import { defineReadonly } from '@prosekit/extensions/readonly'

const editor = createEditor({
  extension: defineReadonly(),
})

// This still works!
editor.view.dispatch(
  editor.view.state.tr.insertText('Programmatically added text')
)
The readonly extension only prevents user interaction. Programmatic changes through transactions still work normally.

Tips

Provide clear visual feedback that the editor is in readonly mode. Users should understand why they can’t edit.
Consider showing a message or icon indicating readonly status, especially if it’s not obvious from the context.
If you need to toggle readonly state frequently, consider recreating the editor or using a different approach like disabling specific commands.