controls
Editor
A text field with a toolbar for bold, italic, headings, lists and links.
Rich widgetNeeds a script for its behavior.
Usage
A toolbar, an editable area and a hidden input that carries the HTML with the form. Select text and use the toolbar, or Ctrl B and Ctrl I.
Practised roundabouts and merging on the ring road.
<div class="ca-field w-full max-w-xl">
<span class="ca-field__label" id="lesson-notes-label">Lesson notes</span>
<div class="ca-editor">
<div class="ca-editor__toolbar" role="toolbar" aria-label="Formatting" aria-controls="lesson-notes">
<button class="ca-editor__tool" type="button" data-command="bold" aria-pressed="false" aria-label="Bold"><b aria-hidden="true">B</b></button>
<button class="ca-editor__tool" type="button" data-command="italic" aria-pressed="false" aria-label="Italic" tabindex="-1"><i aria-hidden="true">I</i></button>
<button class="ca-editor__tool" type="button" data-command="formatBlock" data-value="h3" aria-label="Heading" tabindex="-1">H</button>
<button class="ca-editor__tool" type="button" data-command="insertUnorderedList" aria-pressed="false" aria-label="Bulleted list" tabindex="-1">
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M9 6h11M9 12h11M9 18h11" /><circle cx="4" cy="6" r="1" /><circle cx="4" cy="12" r="1" /><circle cx="4" cy="18" r="1" /></svg>
</button>
<button class="ca-editor__tool" type="button" data-command="insertOrderedList" aria-pressed="false" aria-label="Numbered list" tabindex="-1">
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M10 6h10M10 12h10M10 18h10M4 4v4M3 18h3l-3 3h3" /></svg>
</button>
<button class="ca-editor__tool" type="button" data-command="link" popovertarget="lesson-notes-link" aria-label="Add a link" tabindex="-1">
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66l-1 1" /><path d="M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 0 0 5.66 5.66l1-1" /></svg>
</button>
<button class="ca-editor__tool" type="button" data-command="unlink" aria-label="Remove the link" tabindex="-1">
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 0 0 5.66 5.66M4 4l16 16" /></svg>
</button>
</div>
<form class="ca-editor__link" id="lesson-notes-link" popover aria-label="Add a link">
<div class="ca-field">
<label class="ca-field__label" for="lesson-notes-url">Link address</label>
<input class="ca-input ca-input--sm" id="lesson-notes-url" name="url" type="url" placeholder="https://" required />
</div>
<button class="ca-button ca-button--primary ca-button--sm" type="submit">Add the link</button>
</form>
<div class="ca-editor__content" id="lesson-notes" contenteditable="true" role="textbox" aria-multiline="true" aria-labelledby="lesson-notes-label"><p>Practised <b>roundabouts</b> and merging on the ring road.</p></div>
<input type="hidden" name="notes" value="<p>Practised <b>roundabouts</b> and merging on the ring road.</p>" />
</div>
</div>Examples
Guidance and accessibility
When to use
- Use the editor for short formatted text, such as notes, descriptions or messages, where bold, lists and links help.
- For plain text, use a text area. For long documents, a full editor fits better.
- Always sanitize the HTML on the server before you store or show it.
Accessibility
- The toolbar is one tab stop: arrow keys, Home and End move between tools.
Each tool has a name, and formats such as bold say whether they apply with
aria-pressed. - The editable area has
role="textbox",aria-multiline="true"and a visible label connected witharia-labelledby. - The link tool opens a small form with a labelled field; Escape closes it.
- Paste inserts plain text, so the content keeps the editor’s own styles.
- Formatting uses
document.execCommand, which every browser supports in editable areas although it is marked legacy.
Classes
Generated from the component contract. Parts use __, modifiers use --.
| Class | Kind | Use it for |
|---|---|---|
ca-editor | Component | Frame around the toolbar and the editable area. |
ca-editor__toolbar | Part | The role="toolbar" of formatting tools. |
ca-editor__tool | Part | One button with data-command; formats show their state with aria-pressed. |
ca-editor__content | Part | The contenteditable area with role="textbox". |
ca-editor__link | Part | The [popover] form that asks for a link address. |
Skins
The same markup, dressed by each skin. Structure and behavior stay the same; only the look changes.
Base skin
Neutral grayscale starting point; product skins extend it and change what differs.
Practised roundabouts and merging on the ring road.
Calaca skin
The Calaca UI brand: hueso paper and tinta ink, cempasúchil marigold, rosa and jade.
Practised roundabouts and merging on the ring road.
Proof skin
Pilot skin pushed far from base: square, heavy, offset shadows, larger targets.
Practised roundabouts and merging on the ring road.
Crossview skin
The CrossView proofing table: cool ground, bond-paper sheets, rule-grey hairlines, one red mark.
Practised roundabouts and merging on the ring road.
Framework notes
Link the prebuilt stylesheet of your skin next to your brand theme. No build step.
<link rel="stylesheet" href="theme.css" />
<link rel="stylesheet" href="ca-base.css" />In Astro, Vite and other Tailwind projects, import Calaca after Tailwind and your theme in your main stylesheet.
@import "tailwindcss";
@import "./theme.css";
@import "@calaca/ui/core.css";
@import "@calaca/ui/skins/base.css";Same stylesheet as above; write the classes in className.
<button className="ca-button ca-button--primary ca-button--md" type="button">Save changes</button>Load the editor script once per page; it runs every editor's toolbar, link form and paste. In Astro or Next.js, import @calaca/ui/behavior/editor once in the layout. Sanitize the HTML on the server before you store or show it.
<script type="module" src="ca-editor.js"></script>