Loading the editor…

Team note

The native <textarea> stays the baseline and the value owner. Without the integration script it remains a plain textarea holding raw HTML — honest, submittable, sanitized server-side either way. Type @ to mention, / for snippets.

Bold, italic, lists, quotes and links only. Pasted content is cleaned on entry. The note cannot be empty.

Rich composer

The Actual composer chrome around a rich editor: attachments and actions belong to the composer, never to the serialized HTML. Enter stays a paragraph; sending goes through Ctrl/Cmd+Enter or the send button. The Snippet and Mention buttons reuse the same suggestion seam as / and @.

Bridge contract

Actual maps tokens, it does not restyle the editor's internals. The bridge CSS stays in docs/demo — it is a recipe, not package API, so no actual-richtext.css ships with the framework and no widget version is pinned by it.

.actual-rich-text .rt-shell {
  --rt-bg: var(--surface);
  --rt-fg: var(--text);
  --rt-border: var(--form-invalid-border, var(--control-border, var(--border)));
  --rt-divider: var(--border);
  --rt-muted: var(--text-muted);
  --rt-focus: var(--form-invalid-border, var(--focus));
  --rt-focus-width: var(--focus-ring-width);
  --rt-radius: var(--radius);
  --rt-font-size: var(--control-font-size);
  --rt-min-height: 10rem;
  --rt-toolbar-bg: var(--surface-subtle);
  --rt-button-hover: var(--hover-overlay);
  --rt-button-pressed-bg: var(--state-selected);
  --rt-button-pressed-fg: var(--state-selected-fg);
  --rt-disabled-bg: var(--surface-subtle);
  --rt-disabled-fg: var(--state-disabled);
  --rt-link: var(--primary);
  --rt-mention-bg: color-mix(in oklab, var(--surface) var(--soft-bg-mix), var(--primary));
  --rt-mention-fg: color-mix(in oklab, var(--primary) var(--soft-fg-mix), var(--text));
  --rt-suggestion-bg: var(--surface-raised);
  --rt-suggestion-active: var(--hover-overlay);
  --rt-suggestion-shadow: var(--shadow-popout);
}

/* The hidden textarea owns validity: the wrapper re-owns the hook. */
.actual-rich-text:has(textarea[aria-invalid="true"]),
.needs-validation.was-validated .actual-rich-text:has(textarea:invalid) {
  --form-invalid-border: var(--danger);
}

/* Rich composer: lift the editor and toolbar into the composer grid. */
.rt-composer rich-text,
.rt-composer .rt-shell {
  display: contents;
}

.rt-composer .rt-editor {
  grid-area: editor;
}

.rt-composer .rt-toolbar {
  grid-area: toolbar;
}