J3W1 / DESIGN SYSTEM
Code editor
The editor surface, gutter, caret, current line, selection, bracket match and monochrome syntax roles that every code-editing or code-viewing host maps onto.
stable · LIGHT DOM · 1.1.0
Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
default
// Compose the window title of the focused container. import { Container } from "./tree.js"; const MAX_LENGTH = 48; export function title(container) { const name = container.name ?? "untitled…"; let label = `${name} (${container.children.length})`; if (label.length > MAX_LENGTH) { label = label.slice(0, MAX_LENGTH) + "…"; } return escape(label); /* escape() is deprecated */ } const broken = 0x1G;
<j3w1-code-editor id="example-code-editor-default"><div class="code-editor" role="textbox" aria-multiline="true" aria-readonly="true" aria-label="title.js, read-only" tabindex="0">
<pre class="code-editor-pre"><span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">1</span><span class="code-editor-text"><span class="code-editor-cm">// Compose the window title of the focused container.</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">2</span><span class="code-editor-text"><span class="code-editor-kw">import</span> <span class="code-editor-punct">{</span> <span class="code-editor-type">Container</span> <span class="code-editor-punct">}</span> <span class="code-editor-kw">from</span> <span class="code-editor-str">"./tree.js"</span><span class="code-editor-punct">;</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">3</span><span class="code-editor-text"><span class="code-editor-kw">const</span> <span class="code-editor-const">MAX_LENGTH</span> <span class="code-editor-op">=</span> <span class="code-editor-num">48</span><span class="code-editor-punct">;</span><span class="code-editor-ws" aria-hidden="true">··</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">4</span><span class="code-editor-text"><span class="code-editor-kw">export</span> <span class="code-editor-kw">function</span> <span class="code-editor-fn">title</span><span class="code-editor-punct code-editor-bracket">(</span><span class="code-editor-var">container</span><span class="code-editor-punct code-editor-bracket">)</span> <span class="code-editor-punct">{</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">5</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-kw">const</span> <span class="code-editor-var">name</span> <span class="code-editor-op">=</span> <span class="code-editor-var">container</span><span class="code-editor-punct">.</span><span class="code-editor-prop">name</span> <span class="code-editor-op">??</span> <span class="code-editor-str">"untitled<span class="code-editor-esc">…</span>"</span><span class="code-editor-punct">;</span></span></span>
<span class="code-editor-line code-editor-line-current"><span class="code-editor-ln" aria-hidden="true">6</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-kw">let</span> <span class="code-editor-var">label</span> <span class="code-editor-op">=</span> <span class="code-editor-str">`${</span><span class="code-editor-var">name</span><span class="code-editor-str">} (${</span><span class="code-editor-var">container</span><span class="code-editor-punct">.</span><span class="code-editor-prop">children</span><span class="code-editor-punct">.</span><span class="code-editor-prop">length</span><span class="code-editor-str">})`</span><span class="code-editor-punct">;</span><span class="code-editor-caret" aria-hidden="true"></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">7</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-kw">if</span> <span class="code-editor-punct">(</span><span class="code-editor-sel"><span class="code-editor-var">label</span><span class="code-editor-punct">.</span><span class="code-editor-prop">length</span> <span class="code-editor-op">></span> <span class="code-editor-const">MAX_LENGTH</span><span class="code-editor-punct">)</span> <span class="code-editor-punct">{</span></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">8</span><span class="code-editor-text"><span class="code-editor-sel"><span class="code-editor-indent"> </span><span class="code-editor-indent"> </span><span class="code-editor-var">label</span> <span class="code-editor-op">=</span> <span class="code-editor-var">label</span><span class="code-editor-punct">.</span><span class="code-editor-fn">slice</span><span class="code-editor-punct">(</span><span class="code-editor-num">0</span><span class="code-editor-punct">,</span> <span class="code-editor-const">MAX_LENGTH</span><span class="code-editor-punct">)</span></span> <span class="code-editor-op">+</span> <span class="code-editor-str">"…"</span><span class="code-editor-punct">;</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">9</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-punct">}</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">10</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-kw">return</span> <span class="code-editor-fn code-editor-deprecated">escape</span><span class="code-editor-punct">(</span><span class="code-editor-var">label</span><span class="code-editor-punct">);</span> <span class="code-editor-cm">/* escape() is deprecated */</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">11</span><span class="code-editor-text"><span class="code-editor-punct">}</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">12</span><span class="code-editor-text"><span class="code-editor-kw">const</span> <span class="code-editor-var">broken</span> <span class="code-editor-op">=</span> <span class="code-editor-num">0x1</span><span class="code-editor-invalid">G</span><span class="code-editor-punct">;</span></span></span></pre>
</div></j3w1-code-editor>markup
<!doctype html> <!-- window frame specimen --> <html lang="en"> <head> <title>j3w1 — theme</title> </head> <body class="frame" data-density="compact"> <h1>Window frame</h1> <marquee>obsolete</marquee> <p>Focus is a ring; selection is a fill.</p> </body> </html><
<j3w1-code-editor id="example-code-editor-markup"><div class="code-editor" role="textbox" aria-multiline="true" aria-readonly="true" aria-label="frame.html, read-only" tabindex="0">
<pre class="code-editor-pre"><span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">1</span><span class="code-editor-text"><span class="code-editor-punct"><!</span><span class="code-editor-kw">doctype</span> <span class="code-editor-attr">html</span><span class="code-editor-punct">></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">2</span><span class="code-editor-text"><span class="code-editor-cm"><!-- window frame specimen --></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">3</span><span class="code-editor-text"><span class="code-editor-punct"><</span><span class="code-editor-tag">html</span> <span class="code-editor-attr">lang</span><span class="code-editor-op">=</span><span class="code-editor-str">"en"</span><span class="code-editor-punct">></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">4</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-punct"><</span><span class="code-editor-tag">head</span><span class="code-editor-punct">></span></span></span>
<span class="code-editor-line code-editor-line-current"><span class="code-editor-ln" aria-hidden="true">5</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-indent"> </span><span class="code-editor-punct"><</span><span class="code-editor-tag">title</span><span class="code-editor-punct">></span>j3w1 <span class="code-editor-esc">&mdash;</span> theme<span class="code-editor-punct"></</span><span class="code-editor-tag">title</span><span class="code-editor-punct">></span><span class="code-editor-caret" aria-hidden="true"></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">6</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-punct"></</span><span class="code-editor-tag">head</span><span class="code-editor-punct">></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">7</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-punct"><</span><span class="code-editor-tag">body</span> <span class="code-editor-attr">class</span><span class="code-editor-op">=</span><span class="code-editor-str">"frame"</span> <span class="code-editor-attr">data-density</span><span class="code-editor-op">=</span><span class="code-editor-str">"compact"</span><span class="code-editor-punct">></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">8</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-indent"> </span><span class="code-editor-punct code-editor-bracket"><</span><span class="code-editor-tag">h1</span><span class="code-editor-punct code-editor-bracket">></span><span class="code-editor-heading">Window frame</span><span class="code-editor-punct"></</span><span class="code-editor-tag">h1</span><span class="code-editor-punct">></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">9</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-indent"> </span><span class="code-editor-punct"><</span><span class="code-editor-tag code-editor-deprecated">marquee</span><span class="code-editor-punct">></span>obsolete<span class="code-editor-punct"></</span><span class="code-editor-tag code-editor-deprecated">marquee</span><span class="code-editor-punct">></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">10</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-indent"> </span><span class="code-editor-punct"><</span><span class="code-editor-tag">p</span><span class="code-editor-punct">></span><span class="code-editor-sel">Focus is a ring; selection is a fill.</span><span class="code-editor-punct"></</span><span class="code-editor-tag">p</span><span class="code-editor-punct">></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">11</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-sel"><span class="code-editor-punct"></</span><span class="code-editor-tag">body</span><span class="code-editor-punct">></span></span><span class="code-editor-ws" aria-hidden="true">·</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">12</span><span class="code-editor-text"><span class="code-editor-punct"></</span><span class="code-editor-tag">html</span><span class="code-editor-punct">></span><span class="code-editor-invalid"><</span></span></span></pre>
</div></j3w1-code-editor>folded
export function title(container) { const name = container.name ?? "untitled"; if (label.length > MAX_LENGTH) { ⋯ } return escape(label); /* escape() is deprecated */ }
<j3w1-code-editor id="example-code-editor-folded"><div class="code-editor" role="textbox" aria-multiline="true" aria-readonly="true" aria-label="title.js with a folded region, read-only" tabindex="0">
<pre class="code-editor-pre"><span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">4</span><span class="code-editor-text"><span class="code-editor-kw">export</span> <span class="code-editor-kw">function</span> <span class="code-editor-fn">title</span><span class="code-editor-punct code-editor-bracket">(</span><span class="code-editor-var">container</span><span class="code-editor-punct code-editor-bracket">)</span> <span class="code-editor-punct">{</span></span></span>
<span class="code-editor-line code-editor-line-current"><span class="code-editor-ln" aria-hidden="true">5</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-kw">const</span> <span class="code-editor-var">name</span> <span class="code-editor-op">=</span> <span class="code-editor-var">container</span><span class="code-editor-punct">.</span><span class="code-editor-prop">name</span> <span class="code-editor-op">??</span> <span class="code-editor-str">"untitled"</span><span class="code-editor-punct">;</span><span class="code-editor-caret" aria-hidden="true"></span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true"><span class="code-editor-fold-gutter">›</span>7</span><span class="code-editor-text"><span class="code-editor-indent"> </span><span class="code-editor-kw">if</span> <span class="code-editor-punct">(</span><span class="code-editor-sel"><span class="code-editor-var">label</span><span class="code-editor-punct">.</span><span class="code-editor-prop">length</span> <span class="code-editor-op">></span> <span class="code-editor-const">MAX_LENGTH</span><span class="code-editor-punct">)</span> <span class="code-editor-punct">{</span></span> <span class="code-editor-fold" role="img" aria-label="3 folded lines">⋯</span> <span class="code-editor-punct">}</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">10</span><span class="code-editor-text"><span class="code-editor-sel"><span class="code-editor-indent"> </span><span class="code-editor-kw">return</span> <span class="code-editor-fn code-editor-deprecated">escape</span><span class="code-editor-punct">(</span><span class="code-editor-var">label</span><span class="code-editor-punct">);</span></span> <span class="code-editor-cm">/* escape() is deprecated */</span></span></span>
<span class="code-editor-line"><span class="code-editor-ln" aria-hidden="true">11</span><span class="code-editor-text"><span class="code-editor-punct">}</span><span class="code-editor-ws" aria-hidden="true">·</span></span></span></pre>
</div></j3w1-code-editor>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@1.1.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/code-editor.css';
import '@j3w1/ui/register/code-editor';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@1.1.0 j3w1-ui copy code-editor --out ./vendor/j3w1/code-editorThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
setText | (text: string): void | Replace the bounded read-only text view; no code or shell execution occurs. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-action | CustomEvent | Request a developer-view action; editing and execution belong to the host. |
Dependencies and verification
Registered components: none. Style dependencies: none.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Purpose
The reference rendering of source code: the surface, gutter, caret, current line, selection, bracket match, indent guides, rendered whitespace, fold markers and the seventeen monochrome syntax roles. It is the contract a real editor’s theme API is mapped onto; this component never edits.
Anatomy
Root .code-editor; parts .code-editor-<part>: -pre (the <pre>),
-line, -ln (line number), -text, -indent, -ws (rendered
whitespace), -caret, -sel (selection range), -bracket, -fold, and
the gutter fold glyph -fold-gutter. Syntax spans are .code-editor-<role>:
-kw keyword, -str string, -cm comment, -num number, -const
constant, -fn function, -var variable, -type type, -op operator,
-punct punctuation, -prop property, -heading heading, -tag tag,
-attr attribute, -esc escape, -invalid invalid, -deprecated
deprecated. Line numbers are color.code.line-number on
color.code.gutter-bg, separated from the text by a 1px
color.code.gutter-rule; indent guides are 1px color.code.indent-guide;
rendered whitespace is color.code.whitespace. Every colour is a role
variable, so the profile switch on the specimen recolours the sample without
touching the markup.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | code on color.code.bg; comments italic; invalid text wavy-underlined in color.code.syntax.invalid; deprecated text struck through | italic, wavy underline, line-through |
| focus-visible | container ring 2px color.interaction.focus.ring-container at −3px; a 1px color.code.caret bar; the caret line filled color.code.current-line across gutter and text; its number in color.code.line-number-active | the ring; the caret bar |
| selected | the range filled color.code.selection-bg; syntax colours kept | the range is announced through the host’s selection; the fill spans line ends |
| selected+container-inactive | the same range in color.interaction.selection.inactive-bg with color.interaction.selection.inactive-text; no caret, no ring | lightness drop between the two fills |
| current | both characters of the matched pair outlined 1px solid color.code.bracket-match | the outline is a box, not a colour change |
| read-only | gutter drops to color.surface.sunken behind the same line numbers; the gutter rule becomes dotted; no caret is drawn | aria-readonly; dotted rule; missing caret |
Precedence: selection is drawn under the text, the current line under the selection, the bracket outline and the caret above everything. The caret never blinks in the reference; a host that blinks it keeps the user’s cursor-blink setting and stops under reduced motion.
Keyboard
The host editor owns every binding. The theme states only what it draws in response: the caret and current line while focused, the selection fill for the host’s selection, the outline for the host’s bracket match. The reference markup is one Tab stop and, being read-only, consumes no key. A host that captures Tab for indentation must offer an escape route (Escape, then Tab) and announce it; the theme does not provide one.
Accessibility
Syntax colour is decoration: no meaning is carried by colour alone. Invalid
text has the wavy underline, deprecated text the line-through, comments the
italic. Selection is a fill and focus is a ring, drawn together and never
substituted for one another. Every syntax role reaches 4.5:1 on
color.code.bg (the lowest is color.code.syntax.property at 4.69:1) and
color.text.default reaches 7.02:1 inside the selection fill. Comment,
type, keyword and property text drop below 4.5:1 inside
color.code.selection-bg (3.81–4.38:1); this is a recorded limitation of the
selection value and is not to be fixed by recolouring the tokens locally. Line
numbers are metadata people read and use color.text.subtle, not a graphic
colour; indent guides, rendered whitespace and the gutter rule are decorative.
In forced-colours mode the caret, selection and bracket outline take the
system Highlight, and the syntax spans keep their text styles.
Portability
A real editor is a host surface: CodeMirror, Monaco, GtkSourceView,
KSyntaxHighlighting, an .icls scheme or a terminal editor each expose a
theme API, and the port maps the roles here onto that API without touching
caret behaviour, keyboard handling or selection semantics. Where a host has no
bracket-match outline it may use the bold weight and records the deviation;
where a host cannot draw a 1px caret it uses its own caret in
color.code.caret. The heritage-ansi and extended profiles change only
role values; the mapping does not change.
Non-examples
Rainbow syntax in the default profile (the three extension hues are
proposed only in extended). A blinking or animated caret in the reference.
A selection that recolours the text to white and loses the syntax roles. A
current-line highlight brighter than the selection. A bracket match drawn as
a background fill, which reads as a selection. Line numbers in the decorative
#7d1310. A glow, gradient or rounded corner on the editor box. Removing the
host’s focus indicator without drawing the container ring.