J3W1 / DESIGN SYSTEM
Diagnostics
Inline problem markers in code (underline patterns, an error line and an overview stripe) and the problems panel that lists them as a listbox, each severity carrying its glyph.
stable · LIGHT DOM · 1.1.0
Light DOM retains static content, native semantics, label relationships and host composition without duplicating controls.
Live variants
inline
✕ Language server stopped; markers may be stale.
import { readFile } from "node:fs/promises"; const title = document.title; function render(frame) { const count = frame.children.length; escape(title); return frmae.title + count; } render(document.body) == null;
No problems in title.js
<j3w1-diagnostics id="example-diagnostics-inline"><div class="diagnostics diagnostics-inline">
<p class="diagnostics-status" role="status">✕ Language server stopped; markers may be stale.</p>
<div class="diagnostics-box" role="textbox" aria-multiline="true" aria-readonly="true" aria-label="title.js with 6 diagnostics, read-only" tabindex="0">
<pre class="diagnostics-code"><span class="diagnostics-line"><span class="diagnostics-ln" aria-hidden="true">1</span><span class="diagnostics-text">import { <span class="diagnostics-unused" title="readFile is declared but never used">readFile</span> } from "node:fs/promises";</span><span class="diagnostics-mark diagnostics-mark-hint" aria-hidden="true">·</span></span>
<span class="diagnostics-line"><span class="diagnostics-ln" aria-hidden="true">2</span><span class="diagnostics-text">const title = document.title;</span><span class="diagnostics-mark" aria-hidden="true"></span></span>
<span class="diagnostics-line"><span class="diagnostics-ln" aria-hidden="true">3</span><span class="diagnostics-text">function <span class="diagnostics-hint" title="Hint: render could be an arrow function">render</span>(frame) {</span><span class="diagnostics-mark diagnostics-mark-hint" aria-hidden="true">·</span></span>
<span class="diagnostics-line"><span class="diagnostics-ln" aria-hidden="true">4</span><span class="diagnostics-text"> const count = <span class="diagnostics-info" title="Info: frame.children may be undefined; consider ?.">frame.children</span>.length;</span><span class="diagnostics-mark diagnostics-mark-info" aria-hidden="true">i</span></span>
<span class="diagnostics-line"><span class="diagnostics-ln" aria-hidden="true">5</span><span class="diagnostics-text"> <span class="diagnostics-deprecated" title="escape() is deprecated">escape</span>(title);</span><span class="diagnostics-mark diagnostics-mark-hint" aria-hidden="true">·</span></span>
<span class="diagnostics-line diagnostics-line-error"><span class="diagnostics-ln" aria-hidden="true">6</span><span class="diagnostics-text"> return <span class="diagnostics-error" title="Error: frmae is not defined">frmae</span>.title + count;</span><span class="diagnostics-mark diagnostics-mark-error" aria-hidden="true">✕</span></span>
<span class="diagnostics-line"><span class="diagnostics-ln" aria-hidden="true">7</span><span class="diagnostics-text">}</span><span class="diagnostics-mark" aria-hidden="true"></span></span>
<span class="diagnostics-line"><span class="diagnostics-ln" aria-hidden="true">8</span><span class="diagnostics-text">render(document.body) <span class="diagnostics-warning diagnostics-target" title="Warning: expected === and instead saw ==">==</span> null;</span><span class="diagnostics-mark diagnostics-mark-warning" aria-hidden="true">!</span></span></pre>
</div>
<p class="diagnostics-empty"><span class="diagnostics-glyph diagnostics-glyph-success" aria-hidden="true">✓</span> No problems in title.js</p>
</div></j3w1-diagnostics>panel
✕ Language server stopped; the list may be stale.
- title.js:6:10 no-undef
- title.js:8:23 eqeqeq
- title.js:4:17 ts-2532
- title.js:1:10 no-unused-vars
- title.js:5:3 deprecation
No problems
<j3w1-diagnostics id="example-diagnostics-panel"><div class="diagnostics diagnostics-panel">
<div class="diagnostics-header">
<span class="diagnostics-title" id="example-diagnostics-panel-dg-title">Problems</span>
<span class="diagnostics-count"><span class="diagnostics-glyph diagnostics-glyph-error">✕</span> 1 <span class="sr-only">error</span></span>
<span class="diagnostics-count"><span class="diagnostics-glyph diagnostics-glyph-warning">!</span> 1 <span class="sr-only">warning</span></span>
<span class="diagnostics-count"><span class="diagnostics-glyph diagnostics-glyph-info">i</span> 1 <span class="sr-only">info</span></span>
<span class="diagnostics-count"><span class="diagnostics-glyph diagnostics-glyph-hint">·</span> 3 <span class="sr-only">hints</span></span>
</div>
<p class="diagnostics-status" role="status">✕ Language server stopped; the list may be stale.</p>
<ul class="diagnostics-list" role="listbox" aria-labelledby="example-diagnostics-panel-dg-title" aria-activedescendant="example-diagnostics-panel-dg-row-2" tabindex="0">
<li class="diagnostics-row" role="option" id="example-diagnostics-panel-dg-row-1" aria-selected="false">
<span class="diagnostics-glyph diagnostics-glyph-error" aria-hidden="true">✕</span>
<span class="diagnostics-message"><span class="sr-only">Error: </span>frmae is not defined</span>
<span class="diagnostics-loc">title.js:6:10</span>
<span class="diagnostics-rule">no-undef</span>
</li>
<li class="diagnostics-row diagnostics-row-active" role="option" id="example-diagnostics-panel-dg-row-2" aria-selected="false">
<span class="diagnostics-glyph diagnostics-glyph-warning" aria-hidden="true">!</span>
<span class="diagnostics-message"><span class="sr-only">Warning: </span>Expected === and instead saw ==</span>
<span class="diagnostics-loc">title.js:8:23</span>
<span class="diagnostics-rule">eqeqeq</span>
</li>
<li class="diagnostics-row" role="option" id="example-diagnostics-panel-dg-row-3" aria-selected="false">
<span class="diagnostics-glyph diagnostics-glyph-info" aria-hidden="true">i</span>
<span class="diagnostics-message"><span class="sr-only">Info: </span>frame.children may be undefined; consider optional chaining</span>
<span class="diagnostics-loc">title.js:4:17</span>
<span class="diagnostics-rule">ts-2532</span>
</li>
<li class="diagnostics-row" role="option" id="example-diagnostics-panel-dg-row-4" aria-selected="false">
<span class="diagnostics-glyph diagnostics-glyph-hint" aria-hidden="true">·</span>
<span class="diagnostics-message"><span class="sr-only">Hint: </span>readFile is declared but never used</span>
<span class="diagnostics-loc">title.js:1:10</span>
<span class="diagnostics-rule">no-unused-vars</span>
</li>
<li class="diagnostics-row" role="option" id="example-diagnostics-panel-dg-row-5" aria-selected="false">
<span class="diagnostics-glyph diagnostics-glyph-hint" aria-hidden="true">·</span>
<span class="diagnostics-message"><span class="sr-only">Hint: </span>escape() is deprecated; use encodeURIComponent()</span>
<span class="diagnostics-loc">title.js:5:3</span>
<span class="diagnostics-rule">deprecation</span>
</li>
</ul>
<p class="diagnostics-empty"><span class="diagnostics-glyph diagnostics-glyph-success" aria-hidden="true">✓</span> No problems</p>
</div></j3w1-diagnostics>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/diagnostics.css';
import '@j3w1/ui/register/diagnostics';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 diagnostics --out ./vendor/j3w1/diagnosticsThe 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
How problems appear on code and in the list that indexes them. Six kinds are distinguished by pattern and glyph as well as colour: error, warning, info, hint, unused and deprecated. The panel is the keyboard route to every marker.
Anatomy
Root .diagnostics with the variant class .diagnostics-inline or
.diagnostics-panel. Inline parts: .diagnostics-code (the <pre>),
-line, -ln, -text, -mark (the stripe cell), and the ranges
.diagnostics-error, -warning, -info, -hint, -unused, -deprecated;
.diagnostics-line-error marks the row filled color.diagnostic.error.bg;
.diagnostics-target is the range that belongs to the selected row. Panel
parts: -header, -title, -count, -status, -list, -row, -glyph
(with -glyph-error and so on), -message, -loc, -rule, -empty.
Patterns: error and warning wavy, info and hint dotted, unused dotted in
color.diagnostic.unused.text, deprecated line-through in
color.code.syntax.deprecated. Glyphs: ✕ error in
color.diagnostic.error.text, ! warning in color.diagnostic.warning.text,
i info in color.diagnostic.info.text, · hint in color.text.subtle.
The stripe mark repeats the glyph in the stripe colour
(color.diagnostic.error.stripe, color.diagnostic.warning.stripe).
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | ranges decorated as above on color.code.bg; the error row filled color.diagnostic.error.bg; panel rows on color.surface.default with 1px color.border.divider between them | underline pattern; glyph |
| hover | panel row background → color.interaction.hover.bg; inline ranges unchanged (the hover tooltip is a host surface) | cursor: default on rows |
| focus-visible | the active row: 1px dashed color.interaction.focus.ring at −2px; inline box: the container ring 2px color.interaction.focus.ring-container at −3px | the ring |
| selected | row filled color.interaction.selection.bg, text and glyph color.interaction.selection.text; the matching inline range filled color.code.selection-bg under its underline | aria-selected; the glyph stays; the fill spans the row |
| selected+focus-visible | the selection fill and a 1px dashed ring in color.interaction.focus.ring-container | both at once |
| error | the provider failed: a role=status line with ✕ in color.status.danger.text above the stale rows; the inline stripe shows the same ✕ at its top | glyph; live announcement |
| empty | no problems: rows and stripe marks hidden; ✓ No problems in color.text.muted with the glyph in color.status.success.text; inline decorations removed | glyph; the absence of decoration |
Precedence: selected > hover; focus-visible is drawn on top. A selected row recolours its glyph to the on-selection text so the shape, not the hue, carries the severity.
Keyboard
The panel follows the APG listbox pattern with aria-activedescendant:
the list is one Tab stop, Up and Down move the active row, Home and End jump,
type-ahead matches message text, Enter opens the location. Selection follows
focus. Inline markers add no keys; next-problem and previous-problem
navigation and the hover card are host bindings and host surfaces.
Accessibility
Never colour-only: every severity has its underline pattern in code and its
glyph in the panel, and the glyph is part of the option’s accessible name
(“Error: frmae is not defined, title.js line 6”). The error row background
is an addition to the wavy underline, not a replacement. Contrast:
color.diagnostic.error.text reaches 4.88:1 on the error row and 5.26:1 on
the panel; every underline reaches 3:1 on its actual background including
the selected range (the error underline measures 3.81:1 on the code
selection fill); color.diagnostic.unused.text reaches 5.81:1
so unused code is still readable. aria-selected and aria-activedescendant
carry the state; the status line is role=status, announced once. In
forced-colours mode the underline patterns persist because they are
text-decoration, not backgrounds.
Portability
Underline styles are native in Pango, Qt, JetBrains and most terminal emulators; where wavy is unavailable the port uses a plain underline plus the glyph and records the deviation. Hosts that draw their own overview ruler map the stripe colours; hosts without one omit the stripe and keep the glyphs. The listbox maps to any single-selection list widget.
Non-examples
Red-only errors without the ✕ glyph or the wavy pattern. A wavy underline
for every severity, which leaves the pattern meaningless. Purple or cyan for
info and hint. A hover on a panel row that changes only the text colour. A
selected row whose glyph keeps its severity colour on the red fill (2.47:1).
A panel row with a rounded highlight or a left accent bar instead of the fill.
Removing the error line background to “calm” the editor.