Rich Text Editor

An editor for formatted text, with headings, lists, links, tables and images, whose value is HTML

<sa-rich-text-editor name="Bio" label="Guide bio" description="Shown on the guide's public profile."
                     placeholder="Tell travellers about this guide…"
                     value="<h2>Meet Amara</h2><p>Amara has led <strong>small-group walking tours</strong> through Cape Town for twelve years. She grew up in Bo-Kaap and knows every <em>spice merchant</em> by name.</p><ul><li><p>Table Mountain sunrise hikes</p></li><li><p>Bo-Kaap food walks</p></li></ul>" />

Usage

<sa-rich-text-editor> is a self-closing tag. Bind it with asp-for to a string property, or give it a name and value. The value is HTML.

<sa-rich-text-editor asp-for="..." />

The toolbar has undo and redo, a text style menu (text and three heading levels), bold, italic, underline, strikethrough, inline code, bulleted and numbered lists, quotes, code blocks, links, tables and dividers. Selecting text shows a bubble with the common marks. Typing / opens a menu of blocks to insert, and Markdown shortcuts such as ##, -, 1., > and ``` turn into their blocks as you type. Hover over a table row or column for a handle that selects it, opens a menu to insert, merge or delete, and drags it to a new position. Tab moves to the next cell and adds a row at the end of the table.

Value

The editor writes its HTML into a <textarea> inside it, which is what the form posts. Lists post as plain ul and ol elements, and an empty editor posts an empty value. A value without any tags, such as text saved before the field became rich text, opens as paragraphs, one for each block separated by a blank line. The textarea fires an input event whenever the text changes.

Until the editor's script has loaded, and if it can't load, the textarea shows in its place with the HTML as text, so the form still works.

Sanitize the posted HTML on the server before you store or display it. The editor only writes safe markup, but a request can come from anywhere and carry scripts or event handlers. Allow only the markup the editor writes: p, h1 to h3, strong, em, u, s, code, pre, blockquote, ul, ol, li, a with href, img with src, width and height, table, tbody, tr, th and td with colspan, rowspan and data-colwidth, hr and br, and limit links and images to http, https and mailto URLs or relative ones. A library such as HtmlSanitizer does this. The StellarAdmin admin panel's rich text fields sanitize for you.

To show saved HTML formatted as the editor shows it, put it in an element with the sa-rich-text class:

<div class="sa-rich-text">@Html.Raw(sanitizedHtml)</div>

Script

The editor's script is a separate file, rich-text-editor.js, about 120 KB compressed. You don't add it to your layout: the editor renders a <script type="module"> for it inside its element. Pages without an editor never download it, a page with several editors downloads it once, and an editor that htmx swaps into the page loads it when it arrives. The editor still needs stellar-admin.js on the page for its menus and tooltips.

Examples

Model binding

Bound with asp-for, the editor takes its label, description and placeholder from the property's [Display] attribute.

<div class="w-full max-w-2xl">
    <sa-rich-text-editor asp-for="Highlights"/>
</div>

Images

Set upload-url to let users add images. They paste, drop or insert an image from the toolbar or the / menu, and it uploads straight away, with its progress over the image. Drag the handle on a selected image to resize it. Without upload-url there is no image button, and pasted image files are ignored.

<sa-rich-text-editor name="Itinerary" label="Itinerary" upload-url="/ImageUpload?handler=Upload" max-file-size="2097152"
                     description="Paste, drop or insert photos of each stop. Up to 2 MB each."
                     value="<h3>Day 1: Lisbon</h3><p>Arrive in Lisbon and take the tram up to Alfama for dinner.</p>" />

The demo answers uploads in the browser instead of sending them to a server, so nothing you pick leaves your machine.

The editor posts each image as the multipart field file to upload-url, with the form's antiforgery token both as the __RequestVerificationToken field and in the RequestVerificationToken header. The endpoint stores the file and answers with the URL to show it from:

{ "url": "https://cdn.voyager.example/itineraries/lisbon-4f2a.jpg" }

For a failed upload, answer with an error status and { "error": "..." }, which the editor shows under the text, and the image is removed. accept and max-file-size are checked in the browser before uploading, and default to JPEG, PNG, WebP, GIF and AVIF, up to 5 MB. Check both again on the server. The endpoint answers the same way as an image upload's, with url instead of value.

Read only

Set readonly to show the text without letting the user change it. A read-only editor has no toolbar and still posts its value.

<sa-rich-text-editor name="Terms" label="Booking terms" readonly="true" rows="4"
                     value="<p>Bookings can be cancelled free of charge up to <strong>14 days</strong> before departure.</p>" />

Height

The editor has room for rows lines of text, 8 by default, grows with its content and scrolls once it is 32rem tall.

Accessibility

The editing area has role="textbox" and aria-multiline="true", and is named by the field's label and described by its description and error. Clicking the label focuses it. The toolbar has role="toolbar", and each control has a name and a tooltip with its keyboard shortcut, shown with ⌘ on Apple devices. Toggles such as bold report their state as checkboxes. Errors from uploads appear in a polite live region under the text.

Part classes

Pass a RichTextEditorClassNames instance from the StellarAdmin.UI namespace using class-names. Each property accepts an optional string of space-separated CSS classes, which are added to the part's existing classes.

PropertyTarget
RootThe generated field wrapper, when the control renders an implicit field.
LabelThe generated field label, when rendered.
DescriptionThe generated help text, when rendered.
ErrorThe generated validation message element, when rendered.
ContentThe generated container around the label and supporting text in a horizontal implicit field.
ControlThe editor's container, with the border.
EditorThe area the text is edited in.
ToolbarThe toolbar.

Root, Label, Description, Error, and Content are inherited from FieldClassNames; see Field part classes.

API Reference

<sa-rich-text-editor>

Renders a web component containing the toolbar, the editing area, a <textarea> that posts the HTML, the menus and the module script that loads the editor.

Prop

Type

On this page