Skip to main content

rc-textarea

Textarea wrapper with line decorations, gutter rendering, folding actions, and plugin hooks.

Package
@rcarls/rc-textarea
Element
<rc-textarea>
Native dependency
Requires a native textarea child
State model
Native textarea value
Main events
rc-textarea-focusrc-textarea-blurrc-textarea-changerc-textarea-select

Installation​

npm install @rcarls/rc-webcomponents@next
import '@rcarls/rc-webcomponents/rc-textarea/define';

Usage​

Place a native <textarea> as the direct child. It is hidden after upgrade and keeps the form value, name, and validation; its label names the editor.

<label for="message">Message</label>

<rc-textarea line-numbers word-wrap auto-grow>
<textarea id="message" name="message" rows="10" placeholder="Start typing..."></textarea>
</rc-textarea>

Live demo​

Plain textarea with auto-grow and no gutter. This is the minimal starting point.

<rc-textarea auto-grow>
<textarea rows="4" aria-label="Text editor"></textarea>
</rc-textarea>

Options​

Attributes​

AttributePropertyDescription
line-numberslineNumbersShow sequential line numbers in the gutter.
guttergutterShow an empty gutter that plugins can populate with LineDecoration.gutterContent.
word-wrapwordWrapWrap long lines instead of scrolling horizontally.
auto-growautoGrowLet the field grow vertically with content.
line-directionlineDirectionbase (default) gives every line the editor's direction; auto gives each line its own.

Read-only state comes from the slotted native control: set readonly on the <textarea>. The editor keeps the enabled field appearance, stays focusable and selectable, announces aria-readonly, and rejects edits. The host reflects data-readonly, and the readOnly getter reports the state.

Use a plugin with LineDecoration.gutterContent for sparse numbering. Put accessible names on the slotted textarea with aria-label or a real <label for="...">.

Content language and direction​

The slotted <textarea> describes the content, and the host describes the interface. Put the text's language and direction on the textarea, as on any form control:

  • lang is mirrored onto the editor, so the browser picks the right glyphs (Han characters differ between ja and zh), spelling dictionary and hyphenation. Plugins read it as contentLang, and getWordAtCursor() segments words for it.
  • dir sets the editor's base direction. ltr and rtl are used as written. auto takes the direction of the value's first strong character and uses LTR when there is none, as a native textarea does, and updates as the value changes. Without dir the editor inherits. dirname submits the resolved direction with the form, since the native control always holds the value.
  • spellcheck is mirrored when the textarea sets it. Without it, spelling checks stay off.

Changing the textarea's lang or dir also reruns plugin rendering, so highlighting and decorations can follow the new content language or direction.

The host's own lang is the interface language, as for every rc-* component. It formats line numbers, so an ar-EG interface shows ١ ٢ ٣, and ar-EG-u-nu-latn keeps Latin digits. The gutter, fold controls and line-action popover follow the host's direction, not the content's.

line-direction="auto" suits prose that mixes scripts, such as an Arabic recipe with an English brand name on one line. Each line with a letter orders and aligns by its own first strong character (unicode-bidi: plaintext). Lines without one, such as an empty line or a quantity, keep the base direction, so the caret on a new line in an Arabic recipe starts on the right. Type U+200E or U+200F at the start of a line to force its direction. Leave it at base for code. Widgets are isolated, so they never reorder the text around them or set a line's direction.

JavaScript properties​

PropertyTypeDescription
valuestringCurrent plain-text value. Host writes are silent.
defaultValuestring | undefinedInitial uncontrolled value.
pluginRCTextareaPlugin | nullDeclarative plugin hook for reactive frameworks.
lineDirection'auto' | 'base'How each line's direction is chosen; reflects line-direction.
decorationsDecorationInput[]External decoration layer merged with plugin and pattern decorations.
selectionStart / selectionEndnumberCurrent plain-text selection offsets (read-only).

Events​

All events bubble and are composed.

EventDetailFires when
rc-textarea-change{ value: string }User editing changes the plain-text value.
rc-textarea-focusnoneThe editor receives focus.
rc-textarea-blurnoneThe editor loses focus.
rc-textarea-select{ selectionStart: number, selectionEnd: number }The selection changes while the editor is focused.
editor.addEventListener('rc-textarea-change', (e) => {
console.log(e.detail.value);
});

Accessibility​

The editor is a role="textbox" with aria-multiline="true". It takes its accessible name from the native textarea's aria-label, else the text of the textarea's labels.

KeyBehavior
TabInserts \t.
Escape, then TabMoves focus to the next element instead of inserting a tab.
Shift+TabMoves focus to the previous element.
Ctrl/Cmd+ZUndo.
Ctrl/Cmd+Y / Ctrl/Cmd+Shift+ZRedo.
PasteInserts plain text only; normalizes line endings.
EnterInserts a line break.

While the editor contains an accessible widget, such as an expanded folding panel, Tab moves focus instead of inserting a tab, so the widget stays reachable.

Theming​

rc-textarea is design-system neutral and uses CSS system colors by default.

Component tokens​

TokenDefaultUse
--rc-textarea-font-familymonospaceEditor and gutter font family. While unset, the editor copies the native <textarea>'s font, line height, and spacing.
--rc-textarea-font-size1emEditor and gutter font size.
--rc-textarea-line-height1.5Editor and gutter line height.
--rc-textarea-padding-block0.5emEditor and gutter block padding; any padding-block value.
--rc-textarea-padding-inline0.5emEditor inline padding (both sides) and gutter start padding; one length, which the active line highlight bleeds through.
--rc-textarea-gutter-min-inline-size24pxMinimum gutter width including padding; Material uses 48px.
--rc-textarea-backgroundFieldField background.
--rc-textarea-colorvar(--rc-field-text, FieldText)Field text color.
--rc-textarea-caret-colorvar(--rc-textarea-color, FieldText)Caret color.
--rc-textarea-border1px solid ButtonBorderField border.
--rc-textarea-border-radius2pxField corner radius.
--rc-textarea-focus-outline2px solid HighlightFocus ring.
--rc-textarea-active-line-bgtransparentFull-width active line highlight.
--rc-textarea-gutter-bgCanvasGutter background.
--rc-textarea-gutter-colorGrayTextGutter text color.
--rc-textarea-gutter-border1px solid ButtonBorderGutter separator.
--rc-textarea-gutter-padding-inline-end0.75emSpace between gutter labels and text.

Plugin-provided line actions expose the --rc-line-actions-* and --rc-line-action-* hooks listed in the generated API table. Inline actions inherit the editor surface by default; popover actions use system-color surface defaults where they need independent chrome.

rc-textarea {
color-scheme: dark;
--rc-textarea-font-family: 'Fira Code', monospace;
--rc-textarea-font-size: 13px;
--rc-textarea-background: #1e1e2e;
--rc-textarea-color: #cdd6f4;
--rc-textarea-active-line-bg: rgb(255 255 255 / 0.06);
}

CSS parts​

The exposed CSS parts are root, gutter, gutter-cells, editor-area, editor, fold-trigger, and fold-icon. The last two exist when a folding action is rendered; fold-icon exists only when its plugin supplies an icon.

rc-textarea::part(editor) {
tab-size: 2;
}

rc-textarea::part(fold-trigger) {
border-radius: 0.25em;
color: CanvasText;
}

Decoration styles​

Decoration elements (.mark, .line, .widget) live inside the shadow root. Use api.adoptStyleSheet() from a plugin to reach them; document stylesheets cannot pierce the shadow boundary.

Pattern Highlights​

Use addPattern() for lightweight regex decoration without writing a plugin.

const todoId = editor.addPattern({
pattern: /\bTODO\b/g,
bold: true,
color: 'var(--editor-todo-color)',
});

editor.removePattern(todoId);
editor.clearPatterns();

Patterns can also style named capture groups and add line diagnostics:

editor.addPattern({
pattern: /^(?<key>\w[\w-]*):\s*(?<value>.+)$/gm,
captureGroups: {
key: { bold: true, color: 'var(--editor-key-color)' },
value: { color: 'var(--editor-value-color)' },
},
createLineDecoration: () => ({ className: 'config-line' }),
});

Folding actions​

Import FoldingActionController from @rcarls/rc-textarea/folding or @rcarls/rc-webcomponents/rc-textarea/folding when using the aggregate package.

FoldingActionController creates independent native buttons with aria-expanded. Give each action a stable key, a contextual label, and a target: an app-owned panel element or { fromLine, toLine } for inclusive, one-based source lines after its anchor. Source folds start expanded; set initiallyExpanded: false for diagnostic panels. The controller manages panel visibility and focus return; plugins create panel content as an accessible block widget and version its widget key when content changes. clear() resets folding state when document identity changes or the plugin is destroyed. A Markdown plugin can supply parser-backed ranges without teaching the editor Markdown grammar.

Supply an icon with the controller's createTriggerIcon(expanded) option. The icon is decorative and receives the fold-icon CSS part. The built-in Markdown plugin forwards the same option through its factory:

const plugin = createMarkdownPlugin({
folding: {
createTriggerIcon(expanded) {
const icon = document.createElement('span');
icon.textContent = expanded ? '▾' : '▸';
return icon;
},
},
});

Use ::part(fold-trigger) and ::part(fold-icon) from consumer CSS to style the control and icon. Keep focus visibility and the button's hit area intact.

gutterLine places a control in the gutter; a trailing offset uses the reserved lane sized by --rc-textarea-trailing-size. createGutterContent(button) and updateGutterContent(wrapper) add and refresh a decorative status marker. The gutter has a 24px minimum width in the base component and 48px in Material. Each row follows its text line height, so an inline folding control can be shorter than 24px under WCAG 2.5.8's line-height exception. The full gutter cell is the hit area for a single action; multiple actions share the cell without overlapping targets. Enter and Space activate the native button. Folding never changes the field value or undo history.

Plugins​

Plugins add marks, line decorations, and widgets over the plain-text value. See rc-textarea plugins for the plugin API, helpers, and recipes. The editor internals are documented for contributors in ARCHITECTURE.md.

API​

Properties​

PropertyMarkupTypeDefaultDescription
lineNumbersline-numbersbooleanfalseShow sequential line numbers in the gutter. Enables the gutter implicitly.
guttergutterbooleanfalseEnable the gutter column without any built-in content. Plugins can populate individual cells via `LineDecoration.gutterContent`. For sequential line numbers use `lineNumbers` / `line-numbers` instead.
wordWrapword-wrapbooleanfalseWrap long lines within the field to prevent horizontal overflow.
autoGrowauto-growbooleanfalseAllow the field to grow vertically with content to prevent vertical overflow.
lineDirectionline-direction'auto' | 'base''base'How each line's direction is chosen. `base` gives every line the editor's base direction, which suits code. `auto` gives a line with a letter the direction of its first strong character, for prose that mixes scripts, and holds lines without one (empty, digits, punctuation) to the base direction.
readOnlyJS property onlybooleanNot specifiedWhether the slotted `<textarea>` is `readonly`. The native control is the source of truth; set `readonly` on it to change this state.
pluginJS property onlyRCTextareaPlugin | nullNot specifiedDeclarative plugin hook for framework integrations.
decorationsJS property onlyDecorationInput[]Not specifiedImperatively set an external layer of decorations. Ideal for reactive frameworks. ```tsx // Solid <rc-textarea decorations={decorations()} /> // React 19+ <rc-textarea decorations={decorations} /> ``` Merges with plugin decorations and pattern decorations on every render. Setting this property triggers a new render; setting it to `undefined` or `[]` clears any previously set external decorations.
valuevaluestringNot specifiedThe current field value. Setting this property puts the component into controlled mode (host owns the value, changes are reflected immediately, no change events dispatched)
defaultValuedefault-valuestring | undefinedNot specifiedInitial uncontrolled value.

Methods​

MethodDescription
wrapSelection(prefix: string, suffix: string)Wrap the current selection with `prefix` and `suffix`. No-op when the selection is collapsed (no text selected). Uses the model path directly (not `execCommand`) because callers such as toolbar buttons move focus away from the editor before this fires, which clears the DOM selection. `_savedSelection` retains the last model-level anchor/focus offsets.
replaceSelection(text: string)Replace the current selection with `text`. When the selection is collapsed this is equivalent to `insertText`.
clearHistory()Reset the undo/redo history. Call when the field receives entirely new content.
usePlugin(plugin: RCTextareaPlugin)Register and mount a plugin imperatively. Replaces any currently active plugin. Prefer the `plugin` property for reactive-framework integrations.
removePlugin()Unmount the active plugin, clear its decorations, and release its stylesheets.
addPattern(pattern: Omit<TextPattern, 'id'>)Register a text pattern that generates decorations on every render pass. Returns the pattern ID, which can be passed to `removePattern` to unregister it.
removePattern(id: string)Remove a previously registered pattern by the ID returned from `addPattern`.
clearPatterns()Remove all registered patterns and trigger a re-render.

Events​

EventDetail typeDescription
rc-textarea-focusCustomEventFired when the field gains focus
rc-textarea-blurCustomEventFired when the field loses focus
rc-textarea-changeCustomEventFired when the field value changes
rc-textarea-selectNo detail type documentedFired when the selection changes while the editor is focused

Slots​

NameDescription
(default)Accepts a native `<textarea>` element for form wiring.

CSS Custom Properties​

PropertyDefaultDescription
--rc-textarea-border1px solid ButtonBorderBorder around the field
--rc-textarea-border-radius2pxBorder radius of the field
--rc-textarea-backgroundFieldBackground color of the field
--rc-textarea-colorvar(--rc-field-text, FieldText)Text color
--rc-textarea-font-familymonospaceFont family. While it is unset on the host, the editor copies the native `<textarea>`'s font, line height, and spacing as inline styles, so the enhanced editor matches the native control's typography.
--rc-textarea-font-size1emFont size
--rc-textarea-line-height1.5Line height
--rc-textarea-padding-block0.5emBlock padding of the editor and gutter; any `padding-block` value
--rc-textarea-padding-inline0.5emInline padding on both sides of the editor and before the gutter; one length, which the active line highlight bleeds through
--rc-textarea-gutter-min-inline-size24pxMinimum width of the gutter, including its padding
--rc-textarea-trailing-size2.5emReserved inline space and minimum block size for trailing controls
--rc-textarea-focus-outline2px solid HighlightFocus ring outline
--rc-textarea-caret-colorFieldTextCaret color
--rc-textarea-active-line-bgtransparentActive line highlight color, including editor and gutter padding
--rc-textarea-gutter-bgCanvasGutter background color
--rc-textarea-gutter-colorGrayTextGutter text color
--rc-textarea-gutter-border1px solid ButtonBorderGutter right border
--rc-textarea-gutter-font-familyvar(--rc-textarea-font-family, monospace)Gutter font family
--rc-textarea-gutter-content-gap0.25emGap between a retained line number and custom gutter content
--rc-textarea-gutter-padding-inline-end0.75emSpace between gutter labels and text
--rc-line-actions-gap2pxGap between inline line actions; popover actions default to 4px
--rc-line-actions-margin-start6pxInline-start margin before line actions
--rc-line-action-padding1pxInline line-action padding; popover actions default to 4px 6px
--rc-line-action-bordernoneLine-action border; the popover surface defaults to 1px solid ButtonBorder
--rc-line-action-border-radius3pxLine-action border radius; the popover surface defaults to 4px
--rc-line-action-bgtransparentLine-action background; the popover surface defaults to Canvas
--rc-line-action-colorinheritLine-action color; popover actions default to ButtonText
--rc-line-action-font-size1emInline line-action font size; popover actions default to 0.85em
--rc-line-action-opacity0.4Resting opacity of inline line actions
--rc-line-action-hover-opacity1Hover and focus opacity of inline line actions
--rc-line-action-hover-bgtransparentHover and focus background; popover actions default to ButtonFace
--rc-line-action-hover-colorinheritHover and focus text color
--rc-line-action-shadow0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)Line-action popover shadow

CSS Parts​

PartDescription
rootThe outer flex container wrapping the gutter and editor area.
gutterThe gutter column container.
gutter-cellsThe gutter's line-label list.
fold-triggerNative button created by a folding action; style its visual appearance without changing its target or keyboard behavior.
fold-iconConsumer-supplied icon inside a folding action button, when provided.
editor-areaThe container positioning the editor over the slotted textarea.
editorThe contenteditable field surface.