Connection lost. Reconnecting… attempt 1 of 8
Paused. Your work is held on the server.
Could not reconnect.
This session has expired on the server.
An unhandled error has occurred.
Sedna.UI
v0.17.0 · main

Rich-text editor chrome

tier 2 — classes

The frame, the toolbar and the body for an editor your app brings — TipTap, Quill, ProseMirror or a plain contenteditable. Not an editor: the document model, the sanitiser and the security surface stay with the editor you chose.

Around an editor

Live — type in it. .editor is the frame and takes a focused field's ring while anything inside has focus. .editor-toolbar is a wrapping row of .btn-ghost.btn-icon.btn-sm with .editor-sep between groups, and a mark that is on is aria-pressed="true" — the attribute your editor already reports, which the toolbar colours. .editor-body is where the editor mounts and what scrolls, capped by --editor-max-height; add .prose to it so what is typed looks like what is later rendered. For Markdown, the library's own editor is the better fit.

Handover for ORD-4209. Three of five lines are reserved in EU-West.

  • Hold the remaining two for stock, re-checked hourly.
  • Alex Fischer owns the reservation until Monday.
<div class="editor">
    <div class="editor-toolbar" role="toolbar" aria-label="Formatting">
        <button class="btn btn-ghost btn-icon btn-sm" type="button" aria-pressed="true" aria-label="Bold"><i class="ri-bold"></i></button>
        <button class="btn btn-ghost btn-icon btn-sm" type="button" aria-pressed="false" aria-label="Italic"><i class="ri-italic"></i></button>
        <button class="btn btn-ghost btn-icon btn-sm" type="button" aria-pressed="false" aria-label="Underline"><i class="ri-underline"></i></button>
        <span class="editor-sep" role="separator"></span>
        <button class="btn btn-ghost btn-icon btn-sm" type="button" aria-pressed="false" aria-label="Bulleted list"><i class="ri-list-unordered"></i></button>
        <button class="btn btn-ghost btn-icon btn-sm" type="button" aria-pressed="false" aria-label="Numbered list"><i class="ri-list-ordered"></i></button>
        <span class="editor-sep" role="separator"></span>
        <button class="btn btn-ghost btn-icon btn-sm" type="button" aria-label="Insert a link"><i class="ri-link"></i></button>
    </div>
    <div class="editor-body prose" contenteditable="true" role="textbox" aria-multiline="true" aria-label="Handover note">
        <p><strong>Handover for ORD-4209.</strong> Three of five lines are reserved in EU-West.</p>
        <ul>
            <li>Hold the remaining two for stock, re-checked hourly.</li>
            <li>Alex Fischer owns the reservation until Monday.</li>
        </ul>
    </div>
</div>

Quill, through Spillgebees.Blazor.RichTextEditor

Live — type, format, add a link. The one editor the library dresses. The app adds the Spillgebees.Blazor.RichTextEditor package itself — Sedna.UI references nothing third-party — and hands the chrome's classes to the component: .editor as ContainerClass, .editor-toolbar through ToolbarOptions, and .editor-body.prose as EditorContainerClass. The toolbar is the app's own: .btn-ghost.btn-icon.btn-sm buttons carrying Quill's ql-* class, which Quill fills with its icon and marks aria-pressed.

Prefer buttons to Quill's pickers. A <select class="ql-header"> is dressed too — Quill turns it into a dropdown labelled with the app's option text — but the label Quill builds is a role="button" with no accessible name, and Quill copies the select's aria-label onto an element that may not carry one. A button per value, as here, has neither problem.

Do not link the package's stylesheet. It is unlayered, so it would beat every rule the library has; the library carries what Quill needs to work. The link tooltip's words come from --editor-link-open, --editor-link-edit, --editor-link-save and --editor-link-remove, set on any ancestor in the app's language. Built and tested against version 3.0.0 of the package (Quill 2).

@using Spillgebees.Blazor.RichTextEditor.Components
@using Spillgebees.Blazor.RichTextEditor.Components.Toolbar

@* The link tooltip's words, in the app's language. Any ancestor of the editor will do. *@
<div style='--editor-link-open: "Open"; --editor-link-edit: "Edit"; --editor-link-save: "Save"; --editor-link-remove: "Remove"'>
    <RichTextEditor @bind-Content="_content"
                    ContainerClass="editor"
                    ToolbarOptions="_toolbar"
                    EditorContainerClass="editor-body prose"
                    Placeholder="Write the handover note"
                    UseAccessibleKeybindings="true">
        <ToolbarContent>
            <button class="btn btn-ghost btn-icon btn-sm ql-header" type="button" value="2" aria-label="Heading"></button>
            <button class="btn btn-ghost btn-icon btn-sm ql-header" type="button" value="3" aria-label="Subheading"></button>
            <span class="editor-sep" role="separator"></span>
            <button class="btn btn-ghost btn-icon btn-sm ql-bold" type="button" aria-label="Bold"></button>
            <button class="btn btn-ghost btn-icon btn-sm ql-italic" type="button" aria-label="Italic"></button>
            <button class="btn btn-ghost btn-icon btn-sm ql-underline" type="button" aria-label="Underline"></button>
            <span class="editor-sep" role="separator"></span>
            <button class="btn btn-ghost btn-icon btn-sm ql-list" type="button" value="bullet" aria-label="Bulleted list"></button>
            <button class="btn btn-ghost btn-icon btn-sm ql-list" type="button" value="ordered" aria-label="Numbered list"></button>
            <button class="btn btn-ghost btn-icon btn-sm ql-list" type="button" value="check" aria-label="Checklist"></button>
            <span class="editor-sep" role="separator"></span>
            <button class="btn btn-ghost btn-icon btn-sm ql-blockquote" type="button" aria-label="Quote"></button>
            <button class="btn btn-ghost btn-icon btn-sm ql-code-block" type="button" aria-label="Code block"></button>
            <button class="btn btn-ghost btn-icon btn-sm ql-link" type="button" aria-label="Link"></button>
            <button class="btn btn-ghost btn-icon btn-sm ql-clean" type="button" aria-label="Clear formatting"></button>
        </ToolbarContent>
    </RichTextEditor>
</div>

@code {
    // The toolbar row takes the chrome's class through the options record.
    private readonly ToolbarOptions _toolbar = new() { ToolbarContainerClass = "editor-toolbar" };

    private string _content =
        "<p><strong>Handover for ORD-4209.</strong> Three of five lines are reserved in EU-West.</p>"
        + "<ol><li data-list=\"ordered\">Hold the remaining two for stock, re-checked hourly.</li>"
        + "<li data-list=\"ordered\">Confirm the carrier slot for Thursday.</li>"
        + "<li data-list=\"checked\">Reservation moved to Alex Fischer.</li>"
        + "<li data-list=\"unchecked\">Tell the customer about the split delivery.</li></ol>";
}