Skip to main content

Rich Text Editor

A WYSIWYG editor supporting text formatting such as bold, lists, and hyperlinks. Use when users need to author structured or styled content within the product. Don't use for plain text form fields — use Input or Text Area instead.

OverviewStyleAccessibility

Description

Anatomy

PartRequired?Notes
Toolbar Required Contains formatting action buttons grouped by type: text style (Bold, Italic, Underline), structure (Heading levels, Unordered List, Ordered List), and insert actions (Link, Image). Rendered above the editable region.
Toolbar button Required Individual icon buttons within the toolbar. Each has an active/pressed state to reflect the format applied at the current cursor position. Minimum 44x44px touch target.
Toolbar divider Optional Visual separator between toolbar button groups. Uses --op-color-border-default.
Editable content area Required The contenteditable region where users type and see formatted output. Renders formatted HTML output inline (WYSIWYG). Min-height should accommodate at least three lines of text.
Placeholder text Optional Descriptive hint shown when the content area is empty. Uses --op-color-text-placeholder. Disappears on first keystroke.
Character / word count Optional Displayed below the content area when a limit is configured. Shows remaining characters or a warning when the limit is approached.
Field label Recommended Sits above the toolbar, associated with the editor via aria-labelledby. Follows the same label pattern as Form Control.
Hint text Optional Supporting instruction below the label, associated via aria-describedby. Explains formatting expectations or content limits.
Validation message Recommended Inline error or success message displayed below the content area. Uses status tokens and the Validation Message component.
Focus ring Required 3px solid ring using --op-color-interactive-focus applied to the content area container on keyboard focus.

Variants

States

State Behaviour
Default Toolbar and content area are visible and interactive. Content area shows placeholder text if empty.
Focused Content area receives a 3px focus ring using --op-color-interactive-focus. The toolbar remains visible and active. Screen reader announces the labelled field is in edit mode.
Active (toolbar button pressed) A toolbar button shows its active state — background fill using --op-color-interactive-default at reduced opacity, icon uses --op-color-text-on-interactive — when the corresponding format is applied at the cursor position.
Error Content area border changes to --op-color-status-error. Validation message appears below the editor. aria-invalid="true" is applied to the content area container.
Disabled Toolbar buttons and content area are non-interactive. All elements render at reduced opacity using --op-color-text-disabled. aria-disabled="true" applied to the container.
Read-only No toolbar rendered. Content area replaced by a styled HTML output region with no contenteditable attribute. Content is visually identical to the authored output.
Character limit warning Character count indicator switches to --op-color-status-warning colour when the user is within 10% of the character limit.
Character limit exceeded Character count indicator switches to --op-color-status-error. Further input may be blocked or flagged depending on configuration.

Usage guidelines

Do / Don't

Do

Set a meaningful character limit and display the count so authors can self-manage content length before submission.

Don't

Leave character limits unconfigured for public-facing content — unconstrained HTML output can break page layouts.

Do

Associate a clear field label and hint text explaining the expected content and any formatting conventions (e.g. 'Use headings to structure long descriptions').

Don't

Use placeholder text as a substitute for a label — placeholder disappears on input and is not reliably announced by screen readers.

Do

Use the Minimal variant for short supporting text fields where only basic emphasis is needed.

Don't

Use the Standard variant with a full toolbar for fields expected to contain only one or two sentences.

Do

Sanitise HTML output server-side before storing or rendering content, even when the editor enforces formatting constraints client-side.

Don't

Trust raw editor output as safe HTML — XSS risks apply if output is rendered unescaped.

Do

Provide clear validation feedback when required rich text fields are empty or exceed limits.

Don't

Rely solely on the character counter to communicate limits — pair it with a validation message on submit.

Layout & Spacing

Toolbar: padding --op-space-4 (4px) vertical, --op-space-8 (8px) horizontal; gap between toolbar buttons --op-space-4 (4px). Content area: padding --op-space-12 (12px) all sides; min-height 120px (approximately 5 lines at body size). Gap between label and toolbar: --op-space-8 (8px). Gap between content area and character count or validation message: --op-space-4 (4px). Toolbar divider: height 20px, margin --op-space-8 (8px) horizontal.

Tokens

PartTokenValue
Toolbar background --op-color-bg-subtle Distinguishes the toolbar surface from the content area without heavy contrast.
Toolbar border --op-color-border-default 1px border around the toolbar and content area container.
Toolbar button default icon --op-color-text-secondary Icon colour in default (inactive) state.
Toolbar button active icon --op-color-text-on-interactive Icon colour when the format is applied at the cursor position.
Toolbar button active background --op-color-interactive-default Background fill for active/pressed toolbar button state.
Toolbar button hover background --op-color-interactive-hover Background fill on toolbar button hover.
Content area background --op-color-bg-primary White/light background for the editable region.
Content area border --op-color-border-default 1px border; switches to --op-color-status-error in error state.
Content area border radius --op-radius-md Applied to the outer container (toolbar + content area as a unit).
Placeholder text --op-color-text-placeholder Shown when content area is empty.
Body text in editor --op-color-text-primary Default text colour for authored content.
Focus ring --op-color-interactive-focus 3px solid outline applied to the content area container on keyboard focus.
Disabled opacity opacity: 0.4 Applied to the entire editor container in the disabled state. Follow Orbit disabled convention.
Character count default --op-color-text-secondary Default colour for the character count indicator.
Character count warning --op-color-status-warning Switches when approaching the character limit.
Character count error --op-color-status-error Switches when the character limit is reached or exceeded.

Engineering notes

  • The rich text editor is a legacy component and does not have a corresponding wa-* Web Awesome primitive. It is implemented as a custom component wrapping a contenteditable div with a bespoke toolbar. Check the component registry for the current implementation package before building new instances.
  • HTML output must be sanitised before persistence. Use a server-side allowlist (e.g. DOMPurify server-side or an equivalent library) permitting only the tags the toolbar can produce: <p>, <br>, <strong>, <em>, <u>, <h2>, <h3>, <ul>, <ol>, <li>, <a href>, <img src alt>.
  • The editor container must have role="textbox" aria-multiline="true" and be associated with its label via aria-labelledby and hint text via aria-describedby.
  • Toolbar buttons must be <button type="button"> elements with aria-pressed reflecting the active state of the format at the current cursor/selection. Each button requires an aria-label matching its function (e.g. aria-label="Bold").
  • Keyboard shortcut support (Ctrl/Cmd+B, Ctrl/Cmd+I, Ctrl/Cmd+U) must be implemented and documented in the accessible description or help text.
  • Respect prefers-reduced-motion — suppress any toolbar transition or animation when the user preference is set.
  • For the link insertion flow, ensure the URL input dialog is keyboard-accessible and traps focus while open. Return focus to the content area and the cursor position after the dialog closes.
  • Do not use execCommand() for new implementations — it is deprecated. Use the Selection and Range APIs directly or an actively maintained editor library.
  • When used inside a form, the hidden input or textarea storing the serialised HTML output must be updated on every content change (input event) so form validation and submission work correctly.

Keyboard interaction

KeyAction
Tab Moves focus into the toolbar (first toolbar button) or content area depending on focus order. Tab out of the content area moves to the next focusable element in the form.
Arrow keys Within the toolbar, Left/Right Arrow moves focus between toolbar buttons. Within the content area, arrow keys move the cursor through text as in a standard text editor.
Enter Within the content area, inserts a paragraph break. Within the toolbar, activates the focused toolbar button.
Space Within the toolbar, activates the focused toolbar button (toggle format on/off).
Ctrl+B / Cmd+B Toggles Bold formatting on the selected text or at the cursor.
Ctrl+I / Cmd+I Toggles Italic formatting on the selected text or at the cursor.
Ctrl+U / Cmd+U Toggles Underline formatting on the selected text or at the cursor.
Ctrl+K / Cmd+K Opens the link insertion dialog for the selected text.
Escape If a toolbar popover (e.g. link dialog) is open, closes it and returns focus to the content area.

Why it matters

Government digital services must meet WCAG 2.1 AA. Rich text editors are historically problematic for screen reader users because contenteditable regions can lose announced context. Authors using assistive technology — including staff with disabilities using Open Point — must be able to apply formatting, navigate the toolbar, and understand the current format state without relying on visual cues alone.

Focus

Focus is managed in two regions:

  1. Toolbar: arrow-key navigation between buttons (roving tabindex pattern). Only one toolbar button is in the tab order at a time.
  2. Content area: receives focus as a single tab stop. The cursor position is maintained when focus returns after toolbar use. When a toolbar button is activated via keyboard, focus returns to the content area immediately so the author can continue typing. The link dialog traps focus while open (focus lock) and returns focus to the content area on close. Focus ring (3px, --op-color-interactive-focus) must be visible on both toolbar buttons and the content area container.

ARIA

Role or attributeWhen to useExample
role="textbox" aria-multiline="true" Applied to the contenteditable container. <div role="textbox" aria-multiline="true" contenteditable="true" aria-labelledby="rte-label" aria-describedby="rte-hint rte-error">
aria-labelledby Associates the field label with the editor region. aria-labelledby="consultation-description-label"
aria-describedby Associates hint text and validation message with the editor region. aria-describedby="rte-hint rte-validation"
aria-invalid Set to true on the content area container when the field has a validation error. aria-invalid="true"
aria-pressed Applied to each toolbar button. Reflects whether the format is currently active at the cursor. <button type="button" aria-label="Bold" aria-pressed="true">
aria-disabled Applied to the editor container and each toolbar button in the disabled state. aria-disabled="true"
role="toolbar" Applied to the toolbar container element. <div role="toolbar" aria-label="Text formatting">
aria-label (toolbar) Labels the toolbar region for screen readers. aria-label="Text formatting"

Contrast

Body text in editor: --op-color-text-primary on --op-color-bg-primary — must meet 4.5:1 (WCAG AA normal text). Placeholder text: --op-color-text-placeholder on --op-color-bg-primary — placeholder text is exempt from contrast requirements under WCAG 1.4.3 but aim for at least 3:1 to support low-vision users. Toolbar icons (default): --op-color-text-secondary on --op-color-bg-subtle — verify 3:1 for UI components (WCAG 1.4.11). Toolbar icons (active): --op-color-text-on-interactive on --op-color-interactive-default — must meet 3:1 for UI components. Character count warning: --op-color-status-warning on --op-color-bg-primary — verify 3:1. Character count error / validation text: --op-color-status-error on --op-color-bg-primary — verify 4.5:1.

Touch targets

All toolbar buttons must have a minimum 44x44px touch target. If toolbar icons are smaller than 44px visually, use padding to expand the interactive area. The content area itself is a large touch target by nature but should have a minimum height of 120px so it is easy to tap on mobile.

Things to avoid

  • Do not rely on colour alone to communicate the active state of a toolbar button — pair the colour change with aria-pressed and, where space allows, a visible label or tooltip.
  • Do not allow the toolbar to scroll off screen on small viewports while the content area is focused — the toolbar must remain visible or accessible via a fixed/sticky position.
  • Do not use placeholder text as the only indication of what the field expects — always provide a visible label.
  • Do not suppress the browser's native focus ring without providing an equivalent — always show the --op-color-interactive-focus ring on the content area container.
  • Do not disable spell-check in the content area (spellcheck="false") unless there is a documented reason — government authors benefit from spell-check for public-facing content.

Was this page helpful?

Updated 5 October 2026