Skip to main content

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
modalImage
any
required
Modal state for image insertion
modalDefaultFont
any
required
Modal state for default font settings
openEmojiPickerRef
RefObject<() => void>
required
Ref to trigger emoji picker
toolbarConfig
ToolbarConfig
Toolbar configuration object
setToolbarConfig
SetEditorToolbarConfig
required
Function to update toolbar config
metadata
Partial<EditorMetadata>
Editor metadata (plain text mode, etc.)
className
string
Additional CSS classes
editorClassname
string
CSS classes for editor container
editorToolbarClassname
string
CSS classes for toolbar
disabled
boolean
Disable editor. Default: false
simple
boolean
Use simplified editor. Default: false
onFocus
() => void
Focus event handler
onMouseUp
() => void
Mouse up event handler
onKeyUp
() => void
Key up event handler
onAddAttachments
(files: File[]) => void
Handler for file attachments
showBlockquoteToggle
boolean
Show blockquote toggle button
onBlockquoteToggleClick
() => void
Blockquote toggle handler
mailSettings
MailSettings
Mail settings for default font
userSettings
UserSettings
User settings (for emoji picker locale)
isPlainText
boolean
Use plain text mode
displayToolbar
boolean
Show/hide toolbar. Default: true
toolbarCustomRender
(toolbar: ReactNode, display: boolean) => ReactNode
Custom toolbar renderer
hasDropzone
boolean
Enable file drop zone. Default: true
isSmallViewportForToolbar
boolean
Force small toolbar breakpoint
title
string
Editor title attribute

Examples

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}
    />
  );
};

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>
  );
};

Toolbar Configuration

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

Build docs developers (and LLMs) love