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:
Click on links (if enabled)
View tooltips and UI elements
Users cannot:
Readonly vs. Disabled
The readonly extension is different from disabling the editor:
| Feature | Readonly | Disabled |
|---|
| 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.