Editor Components
Proton provides a comprehensive rich text editor component for composing formatted content, emails, and documents.
Editor
The main editor component with toolbar and formatting capabilities.
Location: components/editor/Editor.tsx
Basic Usage
import { useState, useRef } from 'react';
import Editor from '@proton/components/components/editor/Editor';
import type { EditorActions } from '@proton/components/components/editor/interface';
import { useModalTwo } from '@proton/components/components/modalTwo/useModalTwo';
const MyEditor = () => {
const [content, setContent] = useState('');
const editorRef = useRef<EditorActions>(null);
const modalLink = useModalTwo();
const modalImage = useModalTwo();
const modalDefaultFont = useModalTwo();
const openEmojiPickerRef = useRef<() => void>(null);
const handleReady = (actions: EditorActions) => {
editorRef.current = actions;
};
return (
<Editor
onChange={setContent}
onReady={handleReady}
modalLink={modalLink}
modalImage={modalImage}
modalDefaultFont={modalDefaultFont}
openEmojiPickerRef={openEmojiPickerRef}
toolbarConfig={{}}
setToolbarConfig={() => {}}
/>
);
};
Props
onChange
(value: string) => void
required
Callback when content changes
onReady
(actions: EditorActions) => void
required
Callback when editor is ready, provides editor actions API
Modal state for link insertion
Modal state for image insertion
Modal state for default font settings
openEmojiPickerRef
RefObject<() => void>
required
Ref to trigger emoji picker
Toolbar configuration object
setToolbarConfig
SetEditorToolbarConfig
required
Function to update toolbar config
Editor metadata (plain text mode, etc.)
CSS classes for editor container
Disable editor. Default: false
Use simplified editor. Default: false
Handler for file attachments
Show blockquote toggle button
Blockquote toggle handler
Mail settings for default font
User settings (for emoji picker locale)
Show/hide toolbar. Default: true
toolbarCustomRender
(toolbar: ReactNode, display: boolean) => ReactNode
Custom toolbar renderer
Enable file drop zone. Default: true
isSmallViewportForToolbar
Force small toolbar breakpoint
Examples
Basic Editor
Simple Editor
Plain Text Editor
With Attachments
const BasicEditor = () => {
const [content, setContent] = useState('');
const editorRef = useRef<EditorActions>(null);
const modalLink = useModalTwo();
const modalImage = useModalTwo();
const modalDefaultFont = useModalTwo();
const openEmojiPickerRef = useRef<() => void>(null);
const [toolbarConfig, setToolbarConfig] = useState({});
return (
<Editor
onChange={setContent}
onReady={(actions) => { editorRef.current = actions; }}
modalLink={modalLink}
modalImage={modalImage}
modalDefaultFont={modalDefaultFont}
openEmojiPickerRef={openEmojiPickerRef}
toolbarConfig={toolbarConfig}
setToolbarConfig={setToolbarConfig}
/>
);
};
const SimpleEditor = () => {
const [content, setContent] = useState('');
return (
<Editor
simple
onChange={setContent}
onReady={() => {}}
modalLink={useModalTwo()}
modalImage={useModalTwo()}
modalDefaultFont={useModalTwo()}
openEmojiPickerRef={useRef(null)}
toolbarConfig={{}}
setToolbarConfig={() => {}}
/>
);
};
const PlainTextEditor = () => {
const [content, setContent] = useState('');
return (
<Editor
metadata={{ isPlainText: true }}
onChange={setContent}
onReady={() => {}}
modalLink={useModalTwo()}
modalImage={useModalTwo()}
modalDefaultFont={useModalTwo()}
openEmojiPickerRef={useRef(null)}
toolbarConfig={{}}
setToolbarConfig={() => {}}
/>
);
};
const EditorWithAttachments = () => {
const [content, setContent] = useState('');
const [attachments, setAttachments] = useState<File[]>([]);
const handleAddAttachments = (files: File[]) => {
setAttachments([...attachments, ...files]);
};
return (
<div>
<Editor
onChange={setContent}
onReady={() => {}}
onAddAttachments={handleAddAttachments}
hasDropzone
modalLink={useModalTwo()}
modalImage={useModalTwo()}
modalDefaultFont={useModalTwo()}
openEmojiPickerRef={useRef(null)}
toolbarConfig={{}}
setToolbarConfig={() => {}}
/>
{attachments.length > 0 && (
<div className="mt-4">
<h4>Attachments</h4>
{attachments.map((file, i) => (
<div key={i}>{file.name}</div>
))}
</div>
)}
</div>
);
};
EditorActions
The editor provides an actions API through the onReady callback:
interface EditorActions {
// Get editor content
getContent: () => string;
// Set editor content
setContent: (content: string) => void;
// Insert content at cursor
insertContent: (content: string) => void;
// Focus editor
focus: () => void;
// Check if editor is empty
isEmpty: () => boolean;
// Clear editor content
clear: () => void;
}
Using Editor Actions
const EditorWithActions = () => {
const [content, setContent] = useState('');
const editorRef = useRef<EditorActions>(null);
const handleInsertSignature = () => {
editorRef.current?.insertContent('<p>Best regards,<br>John Doe</p>');
};
const handleClear = () => {
editorRef.current?.clear();
};
const handleGetContent = () => {
const currentContent = editorRef.current?.getContent();
console.log('Current content:', currentContent);
};
return (
<div>
<div className="flex gap-2 mb-4">
<Button onClick={handleInsertSignature}>Insert Signature</Button>
<Button onClick={handleClear}>Clear</Button>
<Button onClick={handleGetContent}>Get Content</Button>
</div>
<Editor
onChange={setContent}
onReady={(actions) => { editorRef.current = actions; }}
// ... other required props
/>
</div>
);
};
Customize the toolbar with toolbarConfig:
const CustomToolbarEditor = () => {
const [toolbarConfig, setToolbarConfig] = useState({
bold: true,
italic: true,
underline: true,
strikethrough: false,
unorderedList: true,
orderedList: true,
link: true,
image: false,
alignment: true,
fontSize: true,
fontFace: true,
textColor: true,
backgroundColor: false,
});
return (
<Editor
toolbarConfig={toolbarConfig}
setToolbarConfig={setToolbarConfig}
// ... other props
/>
);
};
Best Practices
Content Sanitization
import DOMPurify from 'dompurify';
const SafeEditor = () => {
const [content, setContent] = useState('');
const handleChange = (newContent: string) => {
// Sanitize content before storing
const sanitized = DOMPurify.sanitize(newContent);
setContent(sanitized);
};
return <Editor onChange={handleChange} /* ... */ />;
};
Auto-save
const AutoSaveEditor = () => {
const [content, setContent] = useState('');
const [lastSaved, setLastSaved] = useState<Date | null>(null);
useEffect(() => {
const timer = setTimeout(() => {
if (content) {
saveContent(content);
setLastSaved(new Date());
}
}, 2000);
return () => clearTimeout(timer);
}, [content]);
return (
<div>
<Editor onChange={setContent} /* ... */ />
{lastSaved && (
<p className="text-sm color-weak">
Last saved: {lastSaved.toLocaleTimeString()}
</p>
)}
</div>
);
};
Keyboard Shortcuts
const EditorWithShortcuts = () => {
const editorRef = useRef<EditorActions>(null);
useHotkeys(
document,
[
['Mod+B', () => console.log('Bold')],
['Mod+I', () => console.log('Italic')],
['Mod+K', () => console.log('Insert link')],
],
{ enableOnFormTags: ['INPUT', 'TEXTAREA'] }
);
return <Editor onReady={(actions) => { editorRef.current = actions; }} /* ... */ />;
};
Accessibility
<Editor
title="Email content editor"
aria-label="Compose email body"
// ... other props
/>
The editor includes:
- ARIA labels for toolbar buttons
- Keyboard navigation
- Screen reader support
- Focus management
Source Code
View source:
- Editor:
packages/components/components/editor/Editor.tsx:1
- RoosterEditor:
packages/components/components/editor/rooster/RoosterEditor.tsx
- PlainTextEditor:
packages/components/components/editor/plainTextEditor/PlainTextEditor.tsx