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.
FigmaDescription
Provides an in-browser authoring surface with a toolbar for applying formatting — headings, bold, italic, unordered and ordered lists, hyperlinks, and inline images — and outputs structured HTML content.
Consultation description fields, stakeholder communication templates, news and announcement body content, survey question descriptions, and email body authoring within Open Point.
Government staff need to produce readable, structured content for community-facing pages and notifications without writing raw HTML. The rich text editor bridges the gap between plain text inputs and a full CMS, keeping authoring inside the product workflow.
Anatomy
| Part | Required? | 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
Standard
Default editor with full toolbar
Use for consultation descriptions, announcement bodies, or any long-form content that may need headings, lists, and links. Most common authoring context.
Minimal
Reduced toolbar — bold, italic, and unordered list only
Use when rich formatting is permitted but the content scope is narrow — for example, a survey question description or a tooltip body that supports basic emphasis but not full document structure.
Read-only
Renders formatted HTML output without an editable surface
Use to display previously authored rich text content in a form review step, a confirmation page, or a detail view where the user cannot edit.
Disabled
Full editor rendered but non-interactive
Use when the field exists in the form but cannot be edited due to workflow state — for example, a locked consultation that has been published.
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
When to use
- Authoring long-form content that will be published to community members — consultation descriptions, project updates, news articles.
- Composing email or notification templates where staff need to apply emphasis, structure content with lists, or insert hyperlinks.
- Survey or form question descriptions that benefit from formatted instructions.
- Any field where the output is rendered as HTML and basic document structure (headings, lists, links) adds clarity for readers.
When not to use
- Short, single-line text inputs — use Input instead.
- Multi-line plain text with no formatting requirement — use Text Area instead.
- Code or technical string fields — use a monospaced Input or Text Area.
- Fields where the output is processed programmatically and formatting would introduce unwanted markup.
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
| Part | Token | Value |
|---|---|---|
| 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:
,
, , , ,,
,
- ,
- , ,
.
- 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
- 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
| Key | Action |
|---|---|
| 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:
- Toolbar: arrow-key navigation between buttons (roving tabindex pattern). Only one toolbar button is in the tab order at a time.
- 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 attribute | When to use | Example |
|---|---|---|
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.