code-input.js: Editable Syntax-Highlighted Textarea

Category: Form , Javascript | September 30, 2026
AuthorWebCoder49
Last UpdateSeptember 30, 2026
LicenseMIT
Views0 views
code-input.js: Editable Syntax-Highlighted Textarea

code-input.js is a Web Component for editable syntax-highlighted code fields built around a native <textarea>.

It uses Prism.js, highlight.js, or a custom highlighting callback to handle the syntax coloring while the text field keeps textarea values, selection methods, events, and HTML form behavior.

Features

  • Prism.js, highlight.js, and custom highlighting callbacks.
  • Native textarea values, selections, validation methods, and input/focus events.
  • HTML form fields with progressive fallback markup.
  • Direct browser files and ECMAScript module builds.
  • Indentation, bracket pairing, autocomplete, find/replace, and go-to-line plugins.
  • CSS helpers for autogrow, line wrapping, and Prism line numbers.
  • Token-selection callbacks and custom plugin lifecycle hooks.
  • Bidirectional text through the HTML dir attribute.

How To Use It

Installation

Load a syntax highlighter before registering a code-input template.

<link rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/[email protected]/themes/prism.min.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/components/prism-core.min.js"
        data-manual></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/plugins/autoloader/prism-autoloader.min.js"></script>
<link rel="stylesheet"
      href="https://cdn.jsdelivr.net/gh/WebCoder49/[email protected]/code-input.min.css">
<script src="https://cdn.jsdelivr.net/gh/WebCoder49/[email protected]/code-input.min.js"></script>

Or install them via NPM:

npm install @webcoder49/code-input prismjs

Basic Usage

Register a template before the <code-input> element initializes. template chooses the registered template, and language sets the syntax language.

Keep <textarea data-code-input-fallback> inside the element when the field needs pre-registration or no-JavaScript fallback behavior.

<script>
codeInput.registerTemplate(
  "code-editor",
  new codeInput.templates.Prism(Prism)
);
</script>
<code-input
  template="code-editor"
  language="javascript"
  name="source"
  placeholder="Enter JavaScript"
>
  <textarea
    data-code-input-fallback
    name="source"
    placeholder="Enter JavaScript"
  >const total = items.reduce((sum, item) => sum + item.price, 0);</textarea>
</code-input>

ECMAScript Modules

A bundler-based project can import the core, template, plugins, and styles from the npm package:

import codeInput from "@webcoder49/code-input";
import PrismTemplate from "@webcoder49/code-input/templates/prism.mjs";
import Indent from "@webcoder49/code-input/plugins/indent.mjs";
import Prism from "prismjs";
import "@webcoder49/code-input/code-input.css";
import "prismjs/themes/prism.css";
codeInput.registerTemplate(
  "code-editor",
  new PrismTemplate(Prism, [
    new Indent()
  ])
);

Use highlight.js

Use codeInput.templates.Hljs after loading and configuring highlight.js:

codeInput.registerTemplate(
  "code-editor",
  new codeInput.templates.Hljs(hljs)
);

Use a Custom Highlighter

codeInput.Template accepts a custom highlighting callback. Set isCode to false when the field edits highlighted text that is not source code.

const todoTemplate = new codeInput.Template(
  function (codeElement) {
    codeElement.innerHTML = codeElement.innerHTML.replace(
      /\b(TODO|FIXME)\b/g,
      "<mark>$1</mark>"
    );
  },
  true,
  false,
  false
);
codeInput.registerTemplate("notes", todoTemplate);

HTML Attributes

template, language, and common textarea attributes can be set directly on <code-input>. Put form-critical attributes on the fallback textarea as well when fallback submission matters.

AttributeDescription
templateSelects a template registered with codeInput.registerTemplate(). The first registered template becomes the default.
languageSets the highlighting language and the language-* class used by code templates.
langAlternate language attribute used when language is absent.
valueSets the current field value.
placeholderSets placeholder text.
autocompletePassed to the internal textarea.
autocorrectPassed to the internal textarea.
autofocusPassed to the internal textarea.
colsPassed to the internal textarea.
dirnamePassed to the internal textarea.
disabledPassed to the internal textarea.
formAssociates the field with an HTML form.
maxlengthSets the maximum text length.
minlengthSets the minimum text length.
nameSets the submitted form field name.
readonlySets the read-only state.
requiredApplies native textarea required validation when used as an HTML attribute.
rowsSets the textarea row count.
spellcheckControls native spell checking.
wrapSets textarea wrapping behavior.
aria-*Passed to the internal textarea.
dirControls text direction, including bidirectional text.
tabindexThe component transfers keyboard focus to its internal textarea.

JavaScript Properties

Use the HTML required attribute plus checkValidity() or reportValidity() when validation state is important.

PropertyDescription
autocompleteGets or sets the autocomplete attribute.
colsGets or sets the textarea column count.
defaultValueGets or sets the initial value used for form reset.
textContentReads or updates the stored initial value.
dirNameGets or sets dirname.
disabledGets or sets the disabled state.
formReturns the associated form.
labelsReturns associated labels.
maxLengthGets or sets maxlength.
minLengthGets or sets minlength.
nameGets or sets the form field name.
placeholderGets or sets placeholder text.
readOnlyGets or sets the read-only state.
requiredJavaScript required-state accessor. See the 2.9.0 caveat above.
rowsGets or sets the row count.
selectionDirectionGets or sets selection direction.
selectionEndGets or sets the selection end index.
selectionStartGets or sets the selection start index.
textLengthReturns the current value length.
typeReturns "textarea".
validationMessageReturns the native textarea validation message.
validityJavaScript validity accessor. See the 2.9.0 caveat above.
valueGets or sets the current editor text.
willValidateReturns the validation-state flag from the textarea after registration.
wrapGets or sets wrapping behavior.

JavaScript Methods

MethodDescription
blur(options)Calls blur() on the editable or fallback textarea.
checkValidity()Runs native form validation.
focus(options)Focuses the editable or fallback textarea.
reportValidity()Runs validation and asks the browser to report the result.
setCustomValidity(message)Sets a custom validation message.
setRangeText(replacement, start, end, selectMode)Replaces a text range and schedules a highlighting update.
setSelectionRange(start, end, direction)Updates the selection range.
setKeyboardNavInstructions(instructions, includeAriaDescriptionFirst)Sets instructions shown during keyboard navigation and can append them to the ARIA description.

Element References

PropertyDescription
templateObjectCurrent codeInput.Template after registration.
textareaElementInternal editable textarea.
preElement<pre> used for highlighted output.
codeElement<code> inside the highlighted <pre>.
dialogContainerElementNon-scrolling container used by plugin dialogs and keyboard instructions.
pluginDataPer-element storage object for plugins.

Events

code-input_load fires after the element, its template, and its plugins finish registration. These textarea events can be listened for on <code-input>:

  • change
  • selectionchange
  • invalid
  • input
  • focus
  • blur
  • focusin
  • focusout
const editor = document.querySelector("code-input");
editor.addEventListener("code-input_load", function () {
  editor.focus();
});
editor.addEventListener("input", function () {
  console.log(editor.value);
});

Template API

APIDescription
codeInput.registerTemplate(name, template)Registers a template under a string name.
new codeInput.Template(highlight, preElementStyled = true, isCode = true, includeCodeInputInHighlightFunc = false, plugins = [])Creates a template around a custom highlighting callback.
Template.highlightHighlighting callback used by the template.
Template.preElementStyledSelects whether the <pre> or <code> element receives the highlighter’s primary styles.
Template.isCodeControls code-language handling and language-* classes.
Template.includeCodeInputInHighlightFuncPasses the <code-input> element to the highlighting callback when true.
Template.pluginsPlugin instances attached to the template.
new codeInput.templates.Prism(Prism, plugins = [], preElementStyled = true)Creates a Prism.js template.
new codeInput.templates.Hljs(hljs, plugins = [], preElementStyled = false)Creates a highlight.js template.
codeInput.templates.characterLimit(plugins = [])Creates the proof-of-concept character-limit template. It reads data-character-limit and optional data-overflow-msg.
codeInput.templates.rainbowText(colors, delimiter = "", plugins = [])Creates the proof-of-concept repeating-color template.
codeInput.templates.prism()Deprecated Prism.js factory kept for backward compatibility.
codeInput.templates.hljs()Deprecated highlight.js factory kept for backward compatibility.

When rainbowText() receives no color array, its built-in palette is:

[
  "red",
  "orangered",
  "orange",
  "goldenrod",
  "gold",
  "green",
  "darkgreen",
  "navy",
  "blue",
  "magenta"
]

JavaScript Editor Plugins

Load a plugin file before constructing its plugin class, then pass the instance to the template.

<script src="https://cdn.jsdelivr.net/gh/WebCoder49/[email protected]/plugins/indent.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/WebCoder49/[email protected]/plugins/auto-close-brackets.min.js"></script>
<script>
codeInput.registerTemplate(
  "code-editor",
  new codeInput.templates.Prism(Prism, [
    new codeInput.plugins.Indent(),
    new codeInput.plugins.AutoCloseBrackets()
  ])
);
</script>
PluginPublic constructor and methods
AutoCloseBracketsnew codeInput.plugins.AutoCloseBrackets(bracketPairs = {"(": ")", "[": "]", "{": "}", '"': '"'})
Autocompletenew codeInput.plugins.Autocomplete(updatePopupCallback); callback arguments are popup element, textarea, selection end, and optional selection start.
Autodetectnew codeInput.plugins.Autodetect(); uses highlight.js language detection.
FindAndReplacenew codeInput.plugins.FindAndReplace(useCtrlF = true, useCtrlH = true, translations = {}, alwaysCtrl = true); showPrompt(codeInputElement, replacePartExpanded) opens the dialog.
GoToLinenew codeInput.plugins.GoToLine(useCtrlG = true, translations = {}); showPrompt(codeInputElement) opens the dialog.
Indentnew codeInput.plugins.Indent(defaultSpaces = false, numSpaces = 4, bracketPairs = {"(": ")", "[": "]", "{": "}"}, escTabToChangeFocus = true, translations = {}); the browser build also contains disableTabIndentation() and enableTabIndentation().
SelectTokenCallbacksnew codeInput.plugins.SelectTokenCallbacks(callbacks, onlyCaretNotSelection = false, caretAtStartIsSelected = true, caretAtEndIsSelected = true, createSubTokens = false, partiallySelectedTokensAreSelected = true, parentTokensAreSelected = true)
SelectTokenCallbacks.TokenSelectorCallbacksnew TokenSelectorCallbacks(tokenSelectedCallback, selectChangedCallback); createClassSynchronisation(selectedClass) creates class-sync callbacks. Default class: code-input_select-token-callbacks_selected.
SpecialCharsnew codeInput.plugins.SpecialChars(colorInSpecialChars = false, inheritTextColor = false, specialCharRegExp); currently unstable with other plugins, Unicode characters, or highlight.js.

Plugin Translation Keys

Plugin dialogs and keyboard instructions accept translation objects.

PluginTranslation keys
FindAndReplacestart, none, oneFound, matchIndex, error, infiniteLoopError, closeDialog, findPlaceholder, findCaseSensitive, findRegExp, replaceTitle, replacePlaceholder, findNext, findPrevious, replaceActionShort, replaceAction, replaceAllActionShort, replaceAllAction
GoToLinecloseDialog, input, guidanceFormat, guidanceLineRange, guidanceColumnRange, guidanceValidLine, guidanceValidColumn
IndenttabForIndentation, tabForNavigation

Dialog Plugin Requirements

The default Find and Replace dialog needs an editing area at least 500px wide and 170px tall. The Go To Line dialog needs at least 300px by 150px.

Browser keyboard shortcuts can intercept Ctrl/Cmd+F, Ctrl+H, or Ctrl/Cmd+G. Provide a button that calls showPrompt() when the command must stay reachable independently of browser shortcut behavior.

CSS-Only Plugin Helpers

Autogrow

Load the Autogrow stylesheet and use code-input_autogrow_height, code-input_autogrow_width, or both.

Available size variables:

  • --code-input_autogrow_min-height
  • --code-input_autogrow_max-height
  • --code-input_autogrow_min-width
  • --code-input_autogrow_max-width
<link rel="stylesheet"
      href="https://cdn.jsdelivr.net/gh/WebCoder49/[email protected]/plugins/autogrow.min.css">
<code-input
  class="code-input_autogrow_height"
  style="
    --code-input_autogrow_min-height: 120px;
    --code-input_autogrow_max-height: 320px;
  "
  template="code-editor"
  language="css"
>
  <textarea data-code-input-fallback>body {
  font-family: system-ui;
}</textarea>
</code-input>

Wrap

Load its stylesheet and apply code-input_wrap.

<link rel="stylesheet"
      href="https://cdn.jsdelivr.net/gh/WebCoder49/[email protected]/plugins/wrap.min.css">
<code-input
  class="code-input_wrap"
  template="code-editor"
  language="javascript"
>
  <textarea data-code-input-fallback></textarea>
</code-input>

Prism Line Numbers

Use Prism’s line-numbers plugin together with code-input’s compatibility stylesheet and the line-numbers class.

<link rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/[email protected]/plugins/line-numbers/prism-line-numbers.min.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/plugins/line-numbers/prism-line-numbers.min.js"></script>
<link rel="stylesheet"
      href="https://cdn.jsdelivr.net/gh/WebCoder49/[email protected]/plugins/prism-line-numbers.min.css">
<code-input
  class="line-numbers"
  template="code-editor"
  language="javascript"
>
  <textarea data-code-input-fallback></textarea>
</code-input>

Plugin Authoring API

Custom JavaScript plugins extend codeInput.Plugin. Pass the attribute names that the plugin needs to watch to super().

Hook or memberDescription
constructor(observedAttributes)Registers attributes watched by the plugin.
observedAttributesStores the watched attribute names.
beforeHighlight(codeInput)Runs before highlighting.
afterHighlight(codeInput)Runs after highlighting.
beforeElementsAdded(codeInput)Runs before the internal editing elements are inserted.
afterElementsAdded(codeInput)Runs after the editing elements are ready.
attributeChanged(codeInput, name, oldValue, newValue)Runs when a watched attribute changes.
addTranslations(destination, source)Replaces matching translation values in an existing translation object.

Styling and Customization

Keep display: grid on visible <code-input> elements. Version 2 uses that grid for the textarea and highlighted code alignment.

Use the library’s padding variables in place of CSS padding, and include a unit for zero values.

  • Use --padding for all sides.
  • Use --padding-left, --padding-right, --padding-top, and --padding-bottom for individual sides.
  • Override Prism theme backgrounds on the highlighted <pre> when the theme’s selector wins over the host background.
  • Override highlight.js backgrounds through .hljs.
  • Use code-input:has(textarea:focus) for focus styles.
  • Load regular and bold weights when a remote monospace font is paired with a highlighting theme that uses bold tokens.
  • Set color or caret-color on <code-input> only when replacing the automatic theme-derived colors.
  • Keep overlays at least three z-index levels above the <code-input> host when they need to appear over it.
  • Exclude code-input textarea from global textarea max-width or max-height rules.
  • Change runtime classes through classList. Replacing className can remove classes used by code-input.js.
CSS variablePurpose
--paddingSets editor padding on all sides. Default: 16px.
--padding-leftOverrides left padding.
--padding-rightOverrides right padding.
--padding-topOverrides top padding.
--padding-bottomOverrides bottom padding.

HTML Forms and Fallback Textarea

Form-related attributes on <code-input> are passed to the internal textarea after registration. Keep the fallback textarea when the form should also work before registration or when JavaScript is unavailable.

<form action="/save-snippet" method="post">
  <code-input
    template="code-editor"
    language="javascript"
    name="snippet"
    required
    aria-label="JavaScript"
  >
    <textarea
      data-code-input-fallback
      name="snippet"
      required
      aria-label="JavaScript"
    ></textarea>
  </code-input>
  <button type="submit">Save</button>
</form>

Single-Page Application Caching

DOM caching can preserve an already initialized <code-input> and restore it in an invalid state. Mark the internal textarea as fallback markup immediately before the framework caches the page.

For Turbo, run this during turbo:before-cache:

document
  .querySelectorAll("code-input textarea")
  .forEach(function (textarea) {
    textarea.setAttribute("data-code-input-fallback", "true");
  });

Vue and Nuxt

Vue

Tell the Vue compiler that code-input is a custom element:

vue({
  template: {
    compilerOptions: {
      isCustomElement: (tag) => tag === "code-input"
    }
  }
})

Nuxt

code-input.js uses browser APIs and cannot initialize during server-side rendering. Import the core, template, and plugins from client-side code, and register the template before the component mounts in the browser.

Alternatives & Related Resources

You Might Be Interested In:


Leave a Reply