The editor ships as beta in v1.4.0 and is still going through heavy testing and real-project use. Its API and markup contract may change before it is declared stable.
Rich Text Editor
A rich text editor with a WYSIWYG surface — the markup contract is a standard textarea field wearing one extra class: the editing surface, toolbar, and popovers are generated at load, and the textarea stays the form value
<div class="nds-form-container nds-textarea nds-editor">
<div class="nds-form-header">
<label for="editor-standard-field"><span class="nds-label">Content</span></label>
</div>
<div class="nds-form-control">
<textarea class="nds-textarea" name="editor-standard" id="editor-standard-field" placeholder="Write here"></textarea>
</div>
<div class="nds-form-footer" data-feedback-target hidden></div>
</div>
Toolbar Composition
One attribute picks the commands for the field: a comment box might ship only inline formatting while a CMS field ships everything. Server-rendered values hydrate from the textarea
NDS Components by Paste
Copy component markup from any doc page and paste it in: it stays a styled, protected component. Deletion stops at component boundaries, Enter adds a line break inside instead of splitting the structure, and the toolbar's remove button lists the component levels at the caret
Images
The image popover inserts by URL, and pasted content keeps its images with safe sources, alt text, and numeric dimensions. Click an image to select it: the popover then edits it in place, the link popover wraps it in a link, and the remove button targets its component. By default no base64 enters the value — uploads appear only when a server endpoint is configured, and embedding is a per-field opt-in
States
Native textarea attributes drive the initial state: readonly keeps content selectable with the source view available, disabled makes the field inert and excludes it from submission. Runtime toggles go through NDS.State
<div class="nds-form-container nds-textarea nds-editor" data-editor-toolbar="bold italic underline | source">
<div class="nds-form-header">
<label for="editor-readonly-field"><span class="nds-label">Review notes</span></label>
</div>
<div class="nds-form-control">
<textarea class="nds-textarea" name="editor-readonly" id="editor-readonly-field" readonly>
<p>These notes are <strong>read only</strong>: content stays selectable and the source view still opens.</p>
</textarea>
</div>
<div class="nds-form-footer" data-feedback-target hidden></div>
</div>
<div class="nds-form-container nds-textarea nds-editor" data-editor-toolbar="bold italic underline | source">
<div class="nds-form-header">
<label for="editor-disabled-field"><span class="nds-label">Archived entry</span></label>
</div>
<div class="nds-form-control">
<textarea class="nds-textarea" name="editor-disabled" id="editor-disabled-field" disabled>
<p>This entry is archived and its value does not submit.</p>
</textarea>
</div>
<div class="nds-form-footer" data-feedback-target hidden></div>
</div>
Built-in Features
Add nds-editor to a standard textarea field. The editing surface, toolbar, popovers, and form wiring are generated at load.
Commands are composed per field with one attribute, and button labels and tooltips ship in Arabic and English automatically.
Word and Google Docs content converts on paste: real lists, semantic bold and italic, tables restyled as NDS tables, junk stripped.
Component markup pasted from any doc page stays a styled, editable component: classes, status attributes, and sizing knobs survive while unsafe markup is stripped.
Deletes stop at component boundaries, Enter adds a line break inside a component instead of splitting it, and removal is always an explicit action.
Insert from a link, upload to your server through the embedded NDS Upload, or opt in to data:image embedding. Click any image to edit it in place, link it, or remove it.
The value lives in your textarea: it submits with the form, supports required validation, and fires input and change events like any field.
Start, center, end, and justify follow text direction, so aligned content reads correctly in both RTL and LTR pages.
A toolbar toggle swaps to the pretty-printed markup for direct editing, carrying your text selection over so you land where you meant to edit.
Ctrl or Cmd with B, I, and U for inline formatting, Tab nests list items, and native undo and redo work throughout.
Initialize, create, and destroy instances, and react to the ready event through the JS API. Destroy returns the plain textarea field.
Usage Guidelines
Best Practices
- Use the editor for user-authored rich content destined for NDS pages: announcements, article bodies, service descriptions, review notes
- Use it for paste-heavy workflows where authors draft in Word or Google Docs: the paste pipeline converts their formatting instead of losing it or letting junk through
- Do not use it for short single-line values or plain text. Use Text Fields instead
- Do not use it as a code editor. The source view exists for occasional markup fixes, not as a primary authoring surface
- Compose the toolbar to the use case with
data-editor-toolbar: a comment field might shipbold italic | ul olwhile a CMS field ships the full default set - The default heading commands are
h2 h3 h4because the page already owns its h1. Add theh1token only for fields that author complete pages - Add
requiredto the textarea (anddata-requiredon the container) for mandatory fields: validation works natively because the value is a real form field - Links marked "Open in new tab" get
target="_blank"withrel="noopener noreferrer"paired automatically, and only that target survives sanitization - Wire image uploads to your server with
setImageUpload({ uploadUrl: '/api/…' }): files POST through the embedded NDS Upload (progress, retry, and error feedback included) and the returned URL is inserted. Without an endpoint the popover is URL-only, and pasted screenshots show a field notice instead of silently embedding - Reserve
setImageUpload({ uploadUrl: 'embed' })for fields without an upload backend: base64 bloats the stored value (~37% over the file size) and embedded images can't be cached or served optimized - To remove a pasted component, use the toolbar's remove button (it lists the component levels at the caret) or select the whole component and delete. Boundary deletes never break a component apart
- Cap tall fields with
--editor-max-size: past the cap the surface scrolls internally and the toolbar stays in reach - The whole component is beta: expect refinements while it hardens through real-project use
Keyboard Interactions
| Keys | Action |
|---|---|
Ctrl/Cmd + B / I / U | Bold, italic, underline |
Enter | New paragraph. Inside a pasted component: a line break instead, so the component structure never splits |
Backspace / Delete | Normal editing in text and inside component parts. At a component boundary the delete stops instead of merging content through it |
Tab / Shift+Tab | In a list item: nest or un-nest the item. In source view: indent or outdent the line or selection |
Escape, then Tab | In source view: release the tab trap and move focus out of the field |
Ctrl/Cmd + Z / Y | Native undo and redo |
Toolbar Commands
Tokens for data-editor-toolbar. Space-separated, | starts a new button group, source renders at the bar's end, none opts out of the whole bar. Omit the attribute for the full default set.
| Token | Command |
|---|---|
undo / redo | Native history steps |
bold / italic / underline / strike | Inline formatting toggles |
clear | Remove inline formatting from the selection |
link | Link popover: link text, URL, an "Open in new tab" option, and unlink for existing links |
image | Image popover: insert from a link with alt text and width/height, edit a clicked image in place. An upload section appears when a server endpoint is configured or embedding is opted in |
h2 / h3 / h4 | Heading toggles for the current block |
h1 | Available but not in the default set: add it only for full-page authoring fields |
align-start / align-center / align-end / align-justify | Logical block alignment that follows text direction |
ul / ol | Bulleted and numbered lists |
remove | Destructive component removal with a confirm popover listing the component levels at the caret |
source | HTML source view toggle, rendered at the end of the bar |
Data Attributes
Configuration rides the native textarea attributes: placeholder, required, readonly, and disabled all carry over to the editing surface. The editor adds these attributes of its own:
| Attribute | Description |
|---|---|
data-editor-toolbar | Set on the container to pick the toolbar commands. See the Toolbar Commands table for tokens and the grouping syntax |
data-state | Managed on the container at runtime: readonly and disabled toggled through NDS.State. Initial state comes from the textarea's native attributes |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--editor-min-size | 12rem | Minimum height of the editing surface and the source view |
--editor-max-size | 70vh | Height cap: past it the surface scrolls internally so the toolbar stays visible |
JavaScript API
The NDS.Editor API adopts textarea fields and creates and destroys instances. The submitted value is always the textarea, so reading and listening work like any form field.