Now.js Framework Documentation

Now.js Framework Documentation

CodeEditor

EN 09 Nov 2026 04:40

CodeEditor

Overview

CodeEditor turns a <textarea> into a code editor with syntax colours, line numbers and the editing keys programmers
expect. It is part of the core bundle, so every project has it without loading anything else.

The textarea stays the editing surface: typing, the caret, selection, undo, input methods (Thai included) and form
submission are all the browser's own. A <pre> drawn behind it shows the same text in colour, and nothing is ever read
back from that layer, so the textarea's value is always exactly the code.

Use it when:

  • A form edits code or structured text: CSS, HTML templates, JSON settings, SQL, PHP, JavaScript
  • A page shows code that the reader may also edit or copy

Two ways to use it:

When
<textarea data-element="code"> (CodeElementFactory) In forms: validation, required, FormError and data-attr work as for any field
new CodeEditor(textarea, options) From script, outside forms (e.g. the playground)
<form data-form="theme">
  <textarea name="css" data-element="code" data-language="css" data-max-height="60vh" required></textarea>
</form>
const editor = new CodeEditor(document.querySelector('#source'), {language: 'javascript', height: 400});
editor.getValue();

Options

Pass options to the constructor, or as data-* attributes on the textarea (data-line-numbers, data-tab-size, ...).
Constructor options win over attributes.

Option Attribute Default Description
language data-language 'text' See Languages
lineNumbers data-line-numbers true Show the line-number gutter
tabSize data-tab-size 2 Columns per indent level (1–8)
indentWithTabs data-indent-with-tabs false Indent with a tab character
autoCloseBrackets data-auto-close-brackets true Type ) ] } and quotes together with the opener
autoIndent data-auto-indent true Keep the indent on Enter, one level deeper after an opener
minLines data-min-lines 3 (5 in forms) Minimum visible lines
height data-height null Fixed height (px or any CSS length); scrolls inside
maxHeight data-max-height null Grow with the content up to this height
theme data-theme null 'light' or 'dark'; by default it follows the page's data-theme
onChange — null function(value, editor) after every change

Without height or maxHeight the editor grows with its content and the page scrolls.

Keys

Key Effect
Tab / Shift+Tab Indent / outdent; with several lines selected, every line
Enter New line with the same indent; after { [ ( (or an HTML opening tag) one level deeper, and the closer moves to its own line
`( [ { " ' `` Typed with the closer; with a selection, wraps it
A closer next to the caret Steps over it instead of typing a second one
Backspace Inside an empty pair removes both; in leading spaces removes one indent level
Ctrl+/ (⌘/) Comment or uncomment the selected lines
Ctrl+Z / Ctrl+Shift+Z The browser's undo / redo, including the editor's own edits
Esc, then Tab / Shift+Tab Leave the editor for the next / previous field with the keyboard (Tab alone indents)
Click a line number Select that line

Esc lets only the very next Tab leave. If the user presses another key, types or pastes (input-method text
included), clicks in the code or a line number, leaves the editor and comes back, or a script moves the caret in
between, Tab indents again. A modifier pressed on its own (the Shift of Shift+Tab) does not count.

Ctrl+Enter is left alone, so a page can bind it (the playground runs the code with it).

The editor does not stop Esc from reaching document, so a window that closes on Esc (Modal, a dialog) still
closes. To keep a window with unsaved code open, the page stops Esc itself, below document.

API

new CodeEditor(textarea, options?)

Attaches an editor. textarea is an element or a selector. A textarea that already has an editor returns that editor.
The textarea is moved inside the editor's element, editor.root; a textarea not yet in the page needs
editor.root inserted by the caller.

Instance methods

Method Description
getValue() The code (the textarea's value)
setValue(value, options?) Replace the code. By default like assigning textarea.value: not undoable, no input event. {history: true} makes it an undoable edit (focuses the editor, fires input)
getLanguage() / setLanguage(name) Read or change the language and recolour
insertText(text) Replace the selection as if typed (undoable)
indent(outdent?) Indent or outdent the selected lines
toggleComment() Comment or uncomment the selected lines
focus() Focus the editor
refresh() Re-measure and redraw (after changing fonts or sizes)
destroy() Put the textarea back as it was

Assigning textarea.value from any script recolours the code too, and form.reset() is followed.

Properties

editor.textarea, editor.root (the .code-editor element), editor.language, editor.options.

Static

Member Description
CodeEditor.get(textarea) The editor attached to a textarea, or null
CodeEditor.highlight(code, language) Escaped HTML with <span class="ce-{type}"> tokens
CodeEditor.tokenize(code, language) [{type, text}]; joining text gives the code back exactly
CodeEditor.registerLanguage(name, definition, aliases?) Add a language
CodeEditor.resolveLanguage(name) Registered name for a name or alias ('text' when unknown)
CodeEditor.defaults Default options

Events

The textarea's own input event fires on every change made by typing or by the editor's keys (not on
setValue() without history).

Languages

Name Aliases Comment toggle
javascript js, mjs, cjs, jsx, ts, typescript //
json — —
html htm, xml, svg, xhtml, vue <!-- --> (<script> and <style> bodies are coloured as JavaScript and CSS, <?php ?> as PHP)
css scss, less /* */
php — //
sql mysql --
text plain, txt, plaintext — (no colours, no auto-closing)

Adding a language

CodeEditor.registerLanguage('ini', {
  lineComment: ';',
  rules: [
    {type: null, match: /\s+/y, skip: true},
    {type: 'comment', match: /[;#][^\n]*/y, skip: true},
    {type: 'selector', match: /\[[^\]\n]*\]/y},
    {type: 'property', match: /[\w.-]+(?=\s*=)/y},
    {type: 'string', match: /"[^"\n]*"?/y}
  ]
});

Rules are tried in order at every position and the first match wins; every RegExp must be sticky (y). Text no rule
matches stays uncoloured. A rule may also have when(previousToken, state), onMatch(text, state), a type function,
or scan(text, pos, state) returning {tokens, end} for constructs a RegExp cannot express; a definition may give
state() for per-scan state (the CSS language uses it to tell selectors from declarations).

Token types used by the built-in languages, each with a --ce-{type} colour variable: keyword, string, comment,
number, constant, function, property, builtin, operator, punctuation, regex, variable, tag,
attr-name, attr-value, selector, entity, meta.

Styling

Styles are in Now/css/code-editor.css. Override the variables on .code-editor:

.code-editor {
  --code-editor-font-size: 14px;
  --code-editor-line-height: 22px;   /* px: both layers must line up */
  --code-editor-bg: #fff;
  --ce-keyword: #8b5cf6;
}

The textarea and the coloured layer must keep the same font, size, line height, padding and tab size, or the caret drifts
away from the letters: change them only through the --code-editor-* variables. On touch screens the font grows to
16px so phones do not zoom into the field.

Performance

  • Files up to 300 lines are drawn whole; longer files draw only the lines in view (plus a margin), so typing stays
    fast in files of thousands of lines (about 25 ms per key at 1,500 dense lines).
  • After an edit only the changed region is tokenized again.
  • Beyond 300,000 characters the code is shown without colours.

Common Pitfalls

⚠️ 1. Use data-element="code" inside forms

A plain new CodeEditor() takes the textarea out of ElementManager (data-enhance="false"), so field validation does
not run on it. Forms should use CodeElementFactory.

⚠️ 2. setValue() is not undoable by default

Loading content from script is not something the user did. For actions the user takes, such as loading an example or
clearing the editor, pass {history: true} so Ctrl+Z brings the previous code back.

⚠️ 3. Lines do not wrap

Long lines scroll sideways. Wrapping would make line numbers and caret positions depend on the width.