j3w1 / FoundationsVue · HTML · React · Astro

J3W1 / DESIGN SYSTEM

Foundations

True Black / Rose is the permanent default: black canvas, warm dark panels, the original rose foregrounds and dark-red actions. Exact roles and accessibility rules remain authoritative.

Identity

What the j3w1 theme is, where it came from, and the five rules that keep a port recognisable.

True-black canvas (#000000), near-black panels (#100c0c) and chrome (#090707), with dark-red raised layers, rose interface text (#e99499), near-white reading text (#f4eeee), dark-red selections (#531310), a bright red focus ring (#e53935), 1px square borders, zero radii, one monospace family for chrome, reading and code. Red marks interaction: focus, selection, active borders, primary actions. It is not a decoration and it is not applied to everything. Darkness is the canvas; readable content is the priority.

Five rules keep an implementation recognisable:

  1. Preserve the warm-black / rose / red identity. Never substitute a charcoal-and-blue dark theme because the target ships one.
  2. Use the role assigned to a token, never the token whose colour happens to look closest.
  3. Keep content, selection, keyboard focus, diagnostics and changed text distinguishable from one another. Selection is a fill; focus is a ring.
  4. Nothing is rounded, blurred, glowing or gradient-filled. Elevation is a 1px border and, for modals, a backdrop.
  5. Preserve the host application’s behaviour, keyboard handling and accessibility settings. Geometry and typography limits of a host are documented deviations, not reasons to rewrite it.

Where it came from

The palette began as a Manjaro i3 workstation preserved in j3w1/1w3j: a pywal ramp pulled from a red wallpaper, then frozen and hand-tuned slot by slot in config/Xresources while watching what ls, vim and the agnoster prompt actually rendered. The i3 configuration supplied the rest of the idiom: 1px pixel borders, 14/−2 gaps, tabbed layouts, a hidden top bar with Chinese status labels, dunst toasts framed in #ae1914, dmenu selections in #630f0d.

The contemporary reference is j3w1/j3w1.github.io, a working web reinterpretation of that workstation. Its stylesheet named the roles this specification adopts (--terminal, --surface-raised, --selection, --focus, --prose, --muted …) and wrote the first contrast contract. Both sources are pinned by revision in references/sources.json; after adoption this repository is the authority and later changes in either source are proposals, not automatic updates.

What “modern” means here

Clear organisation, responsive behaviour, strong keyboard and screen-reader support, precisely specified states, and controls that are pleasant to operate. It does not mean pill buttons, glass panels, gradients, neon glows, a Material-style redesign, or red on every surface. Sixteen concrete misreadings are rendered as labelled non-examples on the site.

The specification page’s generated literal-color previews have the narrow geometry, checkerboard and motion exception recorded in D-014; the component and consumer rules remain as written.

Profiles

  • default — the approved everyday composition. Monochrome red/rose for chrome, text, borders, interaction and actions; three bounded hues (amber, green, blue) for status, diagnostics and diffs (D-001); monochrome syntax.
  • heritage-ansi — the sixteen Xresources slots and the historical assignments, exact, including the ones that fail the contrast floor. For terminals, archives and fidelity; never for new interface text.
  • extended — a proposed overlay that colours syntax, a full sixteen-slot semantic terminal palette and chart series with the same three hues. Not approved; the site labels it.

Naming

Roles are color.<group>.<role> with a closed group list: surface, text, border, interaction, action, status, code, diagnostic, diff, terminal, chart, icon. Primitives are color.primitive.<family>.<step> where the step rises with luminance. A CSS custom property is the path with dots replaced by dashes: color.interaction.focus.ring--color-interaction-focus-ring. Roles alias primitives; primitives carry provenance.

Foundations

Surfaces, text ladder, borders, interaction states, focus, typography, spacing, density, elevation, motion, icons and layout, with the rules that govern each role.

Values live in tokens/; this document says what each role means and where it may be used. Every rule below is normative for the default profile.

Surfaces

surface.desktop and surface.canvas use true black (#000000). Readable content sits on the canvas or default (#100c0c) panels and cards, raised (#160b0b) for sticky headers, menus and popovers, overlay for dialogs and drawers, sunken (#000000) for reading and detail panes, input for form-control fills, chrome and chrome-alt for window frames and bars. Layering is subtle by design; it may not erase the boundary of a control, which is why controls also carry border.control.

Never tint photographs, video, document content or user-defined data colours to put more red on screen.

Text ladder

RoleValueContrast on canvas / default / raisedUse
text.prose#f4eeee18.31 / 16.96 / 15.83long-form reading, on-fill text
text.bright#ffa2a710.98 / 10.17 / 9.49headings and emphasis
text.default#e994999.16 / 8.48 / 7.92interface and body text
text.muted#bd787d6.15 / 5.69 / 5.31secondary text; the darkest colour permitted for chrome text
text.subtle#ad71755.40 / 5.00 / 4.66metadata, comments, line numbers
text.disabled#8a55593.52 / 3.26 / 3.05disabled controls only (exempt under WCAG 1.4.3; at least 3:1 by policy)
text.accent#e539354.97 / 4.60 / 4.30accent text on canvas and default surfaces only
text.accent-strong#f73f355.72 / 5.29 / 4.94accent text safe on every surface

Ordinary text must reach 4.5:1 against its actual background in every state. #a3676b and #7d1310 are never text roles in default, even when a particular darker background improves their ratio; they remain in service as border.control, icon.decorative, status.neutral.fill and border.disabled. Metadata and comments that a person needs to read are not decorative and use text.subtle, not a graphic colour.

The specification page’s generated literal-color previews have the narrow geometry, checkerboard and motion exception recorded in D-014; the component and consumer rules remain as written.

Borders

Every border is 1px (border.width.default); 2px (border.width.emphasis) is reserved for the selected indicator bar, the sticky-header rule and the invalid border. The quote rule is the one wider edge, at twice the emphasis width, because it marks a block rather than bounding a control. border.divider (#2b0e0d) separates rows and sections; border.default (#531310) is decorative and may never be the only visible edge of a control; border.control (#a3676b, 4.71:1) is the boundary of every form control at rest; border.active (#e53935) marks the focused or active control; border.overlay (#e53935) frames every floating layer. Styles: solid by default; dashed only for focus, drop targets and read-only fields; dotted for hints; double for conflicts. Radius is 0 everywhere, including checkboxes, switches, chips, avatars, badges and tooltips.

Interaction states

Every interactive component declares which of these apply and demonstrates every applicable one, including the combinations. Precedence when several apply: disabled > loading > invalid > selected > pressed > hover; focus-visible is always drawn on top and never suppressed by another state.

StateVisualNon-colour channel
defaultrest tokens
hover (@media (hover: hover) only)background → interaction.hover.bg; links use it too with text.link-hover, menu items → interaction.hover.bg-stronglink underline thickens 1px → 2px; cursor
active / pressedbackground → interaction.pressed.bg; border → border.activeborder change; no translation
focus-visiblethe ring (below)the ring itself; never a glow or a colour shift alone
selectedfill interaction.selection.bg with interaction.selection.text; tabs and navigation use a 2px border.selected-indicatoraria-selected / aria-current; a check glyph in lists
selected + focus-visiblethe selection fill and a ring in the fill’s on-fill colour (interaction.focus.ring-container, 7.48:1 on the selection)both visible at once
selected, container inactiveinteraction.selection.inactive-bg with interaction.selection.inactive-text; no ringlightness drop between the two fills
disabledtext.disabled, border.disabled, interaction.disabled.bg; no hover or pressed styling; still focusable when aria-disableddisabled / aria-disabled; cursor: not-allowed; never opacity
invalidborder → status.danger.border at 2px; message in status.danger.text with the glyph; aria-invalid; aria-describedbyborder width 1 → 2px; glyph; message
invalid + focus-visiblethe 2px danger border and the ring in interaction.focus.ring-container at focus.offset-invaliddouble boundary
read-onlytext.default; 1px dotted border.divider bottom; no fill; focusable; selectablereadonly; dotted edge
loadingsize unchanged; a static glyph replaces the leading icon; aria-busyglyph; no animation under reduced motion
busy / indeterminatestatic 45° stripes of action.primary.bg on interaction.pressed.bg; aria-valuetextstripes
checked / mixedfill action.primary.bg, glyph or in action.primary.textglyph
expanded / collapsedSVG chevron rotated 0 / 90° within the motion budgetaria-expanded
current2px border.selected-indicator and text.brightindicator bar
drop-target / draggingdashed interaction.drop-target border; interaction.marquee filldashed pattern

Rules that apply everywhere:

  1. Selection is a fill; focus is a ring. Neither borrows the other’s form. A ring still has to be visible on whatever it sits over: #e53935 measures 2.52:1 on action.primary.bg and 1.13:1 on status.danger.fill, which is why the ring recolours on those fills instead of relying on hue. It measures 3.38:1 on the interaction.selection.bg recessed by D-027, so a selected row keeps the ordinary control ring.
  2. Focus ring (D-006; component mapping clarification D-015). Controls and rows: outline: 1px dashed {color.interaction.focus.ring}; outline-offset: -2px. Focusable containers (panes, windows, dialogs, cards): outline: 2px solid {color.interaction.focus.ring-container}; outline-offset: -3px. On filled surfaces use the component’s explicit ring mapping: primary buttons and checked controls use interaction.focus.ring-container where declared; destructive buttons use their declared on-fill text role. The selection fill is the exception and takes the ordinary ring, on the measurement in rule 1. Keep the control or container geometry independently of the colour mapping. :focus:not(:focus-visible) draws nothing. Forced-colors mode uses Highlight. A host focus indicator is never removed without this replacement.
  3. Never colour-only. Every state and every status carries a second channel: a glyph (✕ ! ✓ i ·), a border width, an underline pattern, or an ARIA state. Charts carry pattern fills.
  4. Hover changes fills and borders, never text colour alone, and only on hover-capable pointers.
  5. Disabled never uses opacity. Opacity leaks the background and breaks contrast accounting.
  6. Contrast floor. Text ≥ 4.5:1 and control boundaries, rings and meaningful graphics ≥ 3:1 against the actual background of the state, measured after compositing translucent colours.
  7. Motion. Durations are 80 / 120 / 150 ms at most, on opacity, transform and background only; under prefers-reduced-motion: reduce every duration is 0.01 ms, nothing slides or shimmers, and no behaviour waits on transitionend.

Actions

Four button tones. primary is the only filled button at rest (action.primary.bg #7d1310 with text.prose, 9.28:1); secondary is an outline (border.control, text text.bright); tertiary is a ghost or icon button (text.default, hover interaction.hover.bg); destructive is an outline in action.destructive.text (#f73f35) that fills with action.destructive.hover-bg (#dc282e) and #f9faf9 text (4.58:1) on hover, and a filled variant for confirm dialogs. Destructive actions also require a confirmation step or an undo affordance; the colour is not the safeguard.

Status, diagnostics and diffs (D-001)

Five statuses: danger (#f73f35 text, #dc282e fill), warning (#c9973f), success (#86a46f), info (#7e9ebb), neutral (#bd787d text, #a3676b fill). Each has text, fill, on-fill, tint and border roles, a mandatory glyph (✕ ! ✓ i ·) and a distinct underline or border pattern (wavy, dashed, solid, dotted, double). The three extension hues are desaturated and warm-shifted so they sit inside the identity, all between 6.5:1 and 7.6:1 on every surface, and are forbidden in surface, text, border, interaction and action. Purple, cyan and magenta do not exist in the theme.

Diagnostics: error = wavy #e53935 underline, #f73f35 text, #e53935 stripe, #2b0e0d line background; warning = wavy amber; info = dotted blue; hint = dotted text.subtle; unused = text.muted with a dotted underline. Diffs: added = #0f1a0e line background, green gutter mark and +, word-level additions as a 2px green underline (no fill, so text contrast is untouched); removed = #2b0e0d background, red gutter mark and , word-level removals underlined and struck through; modified = #1f1a0c background, amber gutter mark and ~; conflicts = a double #f73f35 border. Textual markers and existing status icons are always preserved.

Code and terminal

Editor surfaces use code.bg (#0c0909), code.current-line (#1c0a09), code.line-number (text.subtle, replacing the site’s #7d1310), caret #e99499, selection #420f0c (muted text stays 4.71:1 inside it), search matches #630f0d with the current match inverted (#ffa2a7 on #0c0909), bracket match as a 1px #e53935 border, indent guides #531310, rendered whitespace #7d1310 (decorative). Syntax in default is monochrome: keywords #f73f35, strings #bd787d, comments #ad7175 italic, numbers and constants #d4868b, functions #ffa2a7, variables and operators #e99499, types #b37175, properties and headings #e53935, tags #f73f35, attributes and escapes #d4868b; invalid text takes a wavy underline, deprecated text a line-through. Languages inherit these roles; a language-specific override needs a documented reason.

Terminal roles carry the sixteen slots exactly in every profile. Slots 1, 4, 6, 8, 9, 10, 12, 13, 14 and 15 do not reach 4.5:1 on the terminal background (slot 15, #a3676b, measures 4.45:1; slot 0 is the background itself); the theme documents this rather than normalising it, because programs, not the theme, choose which slot means what. Inside an editor selection (code.selection-bg, #420f0c) muted text stays at 4.71:1 but the keyword, type, property and comment roles fall below 4.5:1; a selection is a transient state and the limitation is recorded, not hidden. The extended overlay proposes a semantic sixteen-slot palette that all reaches 4.5:1.

Links use strong red text.link (#f73f35) and a persistent underline, distinct from the rose body and heading ladder (D-024). Hover uses near-white text.link-hover on interaction.hover.bg (D-027; the strong fill read louder than the link itself and stays with menu items). Current-page and selected navigation use their explicit indicator and on-fill roles.

Typography

One family: SauceCodePro NFM (Source Code Pro through the Nerd Fonts build), falling back to Source Code Pro, Cascadia Mono, Consolas, Liberation Mono, DejaVu Sans Mono and monospace. The site records size-adjust metrics for the fallbacks; no font file is distributed. Weights 400 and 700 plus italic. Scale (size/line-height in px): ui-sm 12/16, ui-md 13/18, ui-lg 14/20, reading 15/24, code 13/19, terminal 13/19 with −0.5px letter-spacing, h1 20/28 bold, h2 16/24 bold, h3 13/18 bold in text.accent, caption 11/16 only with text.muted or lighter. Hosts keep the user’s chosen size; tiny UI text is a reference-implementation metric, not a rule.

Spacing and density (D-009)

Spacing steps: 0, 1, 2, 4, 8, 12, 16, 24, 32, 48 px. Two density modes applied with data-density on any subtree: compact (control 24px, row 28px, ui-sm type, 8px horizontal padding, 2px gaps) matches the workstation; comfortable (control 32px, row 36px, ui-md type, 12px padding, 4px gaps) suits everyday applications. Both meet the 24×24 CSS-pixel target minimum; compact requires 4px between adjacent targets. Table rows are exempt and always take the comfortable row height: a data row is content rather than chrome, and compacting it costs scannability where it is needed most. Controls inside a row still follow the page density. The site’s 3px i3 gap, 14/−2 window gaps and 28px bar are window-manager geometry, not theme tokens.

Elevation and layering

There are no shadows except shadow.floating (0 6px 22px rgb(0 0 0 / 55%)) on the i3 floating-window specimen. Floating layers are a 1px border.overlay on surface.raised; modals add surface.backdrop (rgb(0 0 0 / 65%)). Stacking: canvas 0 < raised 1 < popover 10 < drawer 20 < dialog 30 < toast 40 < skip link 1000.

A popup opened by hovering follows the pointer: it sits 8px from the cursor, flips to the other side of it rather than crossing a viewport edge, keeps an 8px margin from every edge, and is repositioned on pointer move, scroll and resize. A popup with nothing to operate takes no pointer events, so it cannot stand between the cursor and what opened it; one that carries a control or a link stays reachable and holds while the pointer is inside it. Either closes when the pointer leaves both the trigger and the popup.

The same popup opened from the keyboard anchors to its trigger instead — below it, or above when there is no room below — because focus has no pointer to follow. This is a placement rule only: what opens a popup, and whether it is a tooltip, a preview or an inspector, is the component’s own contract.

Icons

SVG line icons on a 16px grid, 1.5px stroke, stroke: currentColor, fill: none, sizes 12 / 16 / 20. No icon font is required; Nerd Font glyphs appear only in terminal and heritage specimens. Icon-only controls carry aria-label and a tooltip. icon.decorative (#a3676b) is for glyphs that convey nothing on their own.

Layout

Reading measure 72ch, content column 680px, application layout 1440px. Breakpoints 320 (reflow floor), 600, 900, 1280. Components use container queries; sidebars collapse to drawers below 900px; data tables scroll horizontally with a sticky first column below 600px; the page never scrolls horizontally at 320px. Right-to-left mirrors breadcrumb separators, pagination, sidebar and drawer sides, wizard progression, affixes and tree indentation.

Accessibility

Contrast targets, focus and keyboard contracts, reduced motion, forced colours, and what an automated pass does and does not prove.

What is claimed

WCAG 2.2 criteria are used as measurable design targets for the roles this theme defines. A green contrast report and a clean automated scan of the site are evidence that the specified colours and the reference markup meet those targets. They are not a conformance claim for any application that adopts the theme: keyboard operation, focus management, labels, reflow and manual review remain the consumer’s responsibility, and the theme’s own limitations are listed at the end of this document.

Contrast

  • Ordinary text: at least 4.5:1 against the actual background of the state it is in, measured on resolved sRGB values after compositing translucent colours.
  • Large text (18.66px bold or 24px regular and above): at least 3:1.
  • Control boundaries, focus rings, meaningful icons and chart marks: at least 3:1 (WCAG 1.4.11).
  • Disabled text is exempt under WCAG 1.4.3 but must still reach 3:1 by house policy (text.disabled #8a5559, 3.52:1 on the true-black canvas).
  • Never round a value into a pass. The report keeps the unrounded ratio and displays two decimals; 4.4996 reads 4.50 and fails.
  • Heritage values that fail (#a3676b as text, #7d1310 anywhere as text) are flagged $deprecated in the heritage-ansi profile and are never presented as ordinary passes.

exports/contrast.md lists every declared pair: the global pairs in spec/contrast.json and every stateTokens and contrast[] entry of every component. Waived pairs carry their reason; a waiver is only valid for decorative graphics that are never the sole boundary of a control.

Focus

Focus is never lost and never invisible. Controls and rows show focus.ring (1px dashed #e53935, offset −2px); focusable containers show focus.ring-container (2px solid #ffa2a7, offset −3px). On fills, use the component’s explicit ring role (D-015): primary and checked controls use focus.ring-container where declared; destructive fills use their on-fill text role. Colour mapping does not change ring geometry. :focus without :focus-visible draws nothing, which keeps pointer clicks quiet without hiding keyboard focus. A component that removes a host’s default indicator must draw this one. State matrices on the site render every declared combination including selected+focus-visible and invalid+focus-visible, because those are where rings disappear in practice.

Keyboard

Native controls keep native keyboard behaviour. Custom widgets follow the WAI ARIA Authoring Practices pattern cited in their frontmatter (aria.apg): roving tabindex in toolbars, tabs, trees and listboxes; aria-activedescendant in comboboxes; focus trapping and Escape in dialogs and drawers; arrow keys in radio groups and menus. Positive tabindex is banned. Every icon-only control has an accessible name. The site’s own controls (search, filters, inspector, copy) are reachable and operable with the keyboard, and the no-JS build keeps every normative section readable.

Motion

Durations are at most 150ms and apply to opacity, transform and background only. Under prefers-reduced-motion: reduce every duration is 0.01ms: toasts, dialogs and drawers appear in place, indeterminate progress shows static stripes, loading glyphs do not spin, skeletons do not shimmer, tab indicators do not slide. No behaviour is sequenced on transitionend.

The specification page’s generated literal-color previews have the narrow geometry, checkerboard and motion exception recorded in D-014; the component and consumer rules remain as written.

Forced colours and high contrast

Under forced-colors: active rings use Highlight, selections use Highlight / HighlightText, borders survive (they are real borders, not box-shadows), and pattern fills keep charts and diffs legible.

Reflow, zoom and text

Components reflow at 320 CSS pixels without horizontal page scrolling, at 200% browser zoom and at 200% text-only zoom. Rings and borders stay 1px / 2px CSS. Targets are at least 24×24 CSS pixels with 4px spacing in compact density. Long labels wrap or truncate with a visible affordance; the site’s stress fixtures (English, Spanish, Chinese, Arabic with dir="rtl") exercise this.

Colour is never the only channel

Every status has a glyph; every state has a border, pattern or ARIA change; diffs keep + − ~ markers; charts carry pattern fills and a table fallback. The heritage-ansi terminal palette has reduced hue differentiation by design, and the specification documents it as a limitation rather than fixing it silently.

Limitations (recorded, not hidden)

  • The theme specifies colours, states and reference markup. It cannot make a host application’s own focus management, labels or reading order correct.
  • The automated scan on the site uses axe-core with the WCAG 2.x A and AA rule sets. It does not evaluate every success criterion, and manual checks (keyboard walks, screen-reader reading, zoom) require separate execution evidence. Missing protocols remain not run, not implied by the scan.
  • text.disabled at 3.52:1 on canvas is below the ordinary-text floor by design and relies on the WCAG exemption for inactive controls.
  • Heritage ANSI slots 1, 4, 6, 8, 9, 10, 12, 13, 14 and 15 fail 4.5:1 on the terminal background (slot 0 is the background). Programs that render errors or directories in those slots inherit that failure; the extended overlay proposes a corrected set.
  • Syntax roles inside an editor selection: keyword, type, property and comment drop below 4.5:1 on code.selection-bg. Selection is transient; the limitation is recorded in the code-editor specification.

Verification evidence and freshness

The coverage ledger describes source facts: specified, demonstrated and test implemented. exports/coverage.json schemaVersion 1 adds testImplemented and retains tested as a compatibility alias with its original file-existence meaning. Component JSON coverage and family totals use the same definitions. No source coverage Boolean proves execution or blanket WCAG conformance.

The linked static verification report supplies actual per-test outcomes and per-component summaries. Evidence schemaVersion 1 records automated or manual method, category, component, explicitly covered states/variants, browser/version, OS, viewport, profile, density, JavaScript setting and a run or protocol reference. Empty state/variant lists mean unspecified, never all. The environment’s profile and density describe the initial configuration; tests that switch them say so in their scope. Browser records from one engine never imply another engine. Skipped, failed, not run and not applicable are separate outcomes; every non-pass has a reason. Manual screen-reader and keyboard claims require protocol records. The existing automated suite targets Chromium; other engines remain not run.

Freshness compares both the tested artifact digest and a conservative source fingerprint covering tracked contract sources, schemas, generators, site code, tests, agent guidance, CI configuration and package lockfile. All such changes invalidate the current claim; unrelated source changes may therefore invalidate more evidence than strictly necessary. A stale record retains its actual outcome and supporting reference but does not establish a current pass.

A build first publishes a static not-run report and a subject manifest. The browser reporter checks that source and specimen assets still match that subject before publishing execution evidence. Report generation writes only under dist/verification/ and verifies all specimen bytes remain unchanged. Report presentation gets a separate static/browser gate and never certifies itself. Run timestamps and revisions live only in this post-test evidence, outside the committed deterministic exports and their digests, avoiding circular hashes.

Local workflow: build, run the browser suite, npm run verification:report, npm run verification:check, then npm run test:verification. A local run names its local execution and digests; it is not represented as hosted CI evidence. For a manual protocol, provide a schema-valid evidence document to node scripts/verification-report.mjs <repo-relative-evidence.json> with its actual tested digests, browser/environment and a protocol: reference containing the protocol identifier and recorded observations. Retain the protocol with the evidence; do not copy private artifacts into this public repository.

j3w1 / command

  • SPEC Overview
  • SPEC Foundations
  • SPEC Components
  • SPEC Patterns
  • SPEC Tokens
  • SPEC Tools
  • SPEC Implement
  • SPEC For agents
  • SPEC Ports
  • SPEC Releases
  • DEMO Vue application demo
  • SPEC Complete UI theme specification