Theming
Each element keeps its styles in its own shadow root, so your page’s CSS cannot
break it and its CSS cannot leak into your page. What reaches it are the
--ca-* custom properties:
- A built-in theme, picked by
data-ca-theme="<name>"on any ancestor (or on the element), sets every colour property on that element. The elements inside it read them, and so does your page’s own CSS. - A
--ca-*property of your own wins over the theme when it is set on the element that carriesdata-ca-themeor on an element inside it. A theme of your own is a set of them.
With neither, an element shows contraagent Dark.
Reference: <cae-stat-tiles>
The built-in themes
Section titled “The built-in themes”data-ca-theme |
Name | Source |
|---|---|---|
contraagent-dark |
contraagent Dark (the default) | this package |
contraagent-light |
contraagent Light | this package: the hues of contraagent Dark, set for a light page |
github-dark |
GitHub Dark | GitHub Primer primitives, dark (MIT, GitHub Inc.) |
github-light |
GitHub Light | Primer primitives, light (MIT, GitHub Inc.) |
one-dark |
One Dark | Atom One Dark (MIT, GitHub Inc.) |
nord |
Nord | Nord (MIT, Arctic Ice Studio and Sven Greb) |
high-contrast |
High contrast | Primer primitives, dark high contrast (MIT, GitHub Inc.) |
An editor theme takes its surfaces, borders and text from the editor’s own
colours, its accent from the palette’s accent, the statuses from its red,
yellow, green and cyan, and the code colours from its syntax colours. Where a
palette colour reads under 4.5:1 on the theme’s surfaces and tints, its lightness
is raised (or lowered, on a light theme) as little as needed and its hue kept;
src/themes/data.ts lists every value. High contrast keeps text at 7:1 or
more on every surface, has stronger borders and a wider focus ring, and its
status colours stay apart under protanopia, deuteranopia and tritanopia.
Choosing a theme
Section titled “Choosing a theme”Set data-ca-theme on any ancestor. The rules that apply it are a stylesheet
that a document (or a shadow root) adopts once: add it to the page’s document,
and to a shadow root of your own that holds the elements and sets
data-ca-theme inside it. No import adopts it; <ca-monitor> of
@contraagent/ui adopts it for its document when it connects.
import { adoptThemes } from '@contraagent/elements/themes';
adoptThemes(document);adoptThemes(myShadowRoot);The themes are also plain data: THEMES, resolveTheme and themeRules in
@contraagent/elements/themes. themeRules writes the rules for a selector of
your own, and themeBootScript is a script for a page’s <head> that sets the
reader’s theme before the first paint, as the monitor’s page does.
preferredTheme and rememberTheme keep the reader’s choice in this browser;
system follows the system’s colour scheme (contraagent Dark or contraagent
Light).
Use the theme in your own page
Section titled “Use the theme in your own page”The theme’s properties have the same names in your page as in the elements, so
your page and the elements show one theme. Here <html data-ca-theme="nord">
picks it, and the page’s CSS reads it:
/* A page of your own in its elements' theme. With <html data-ca-theme="nord"> and the themes' rules in the document (adoptThemes(document)), the page's own CSS reads the same --ca-* properties as the elements. */body { margin: 0; color-scheme: var(--ca-color-scheme); background: var(--ca-bg); color: var(--ca-text); font: 13px/1.5 system-ui, sans-serif;}.card { background: var(--ca-card); border: 1px solid var(--ca-line); border-radius: 8px; padding: 16px;}.caption { color: var(--ca-text-secondary);}.unit { color: var(--ca-text-muted);}a { color: var(--ca-accent);}:focus-visible { outline: var(--ca-focus-width) solid var(--ca-focus);}/* A property with one default for every theme (a tint, a type size) has its value only inside the elements: compute it the way the elements do. */.failed-badge { color: var(--ca-status-failed); background: color-mix(in srgb, var(--ca-status-failed) var(--ca-tint), transparent);}A theme sets each property that has a value per theme: the colours,
--ca-tint, --ca-focus-width and --ca-color-scheme. Its selector has no specificity,
so any rule of your page that sets a --ca-* property wins over it:
/** A theme's selector on a page: `:where()` gives it no specificity, so any rule of the page that sets a `--ca-*` property wins. */export const pageThemeSelector = (name: ThemeName): string => `:where([data-ca-theme="${name}"])`;The tints (--ca-accent-bg, --ca-status-*-bg, --ca-agent-bg), the type
scale and the corners have one default for every theme; that default exists only
inside the elements, unless your page sets the property.
What a colour means
Section titled “What a colour means”A colour means one thing, on every screen and in every theme:
- Neutral by default. Text, totals, cost, tokens, time and every count that is not a count of a problem are in the text colours. A number is emphasised by its size and weight, not by a colour of its own.
- A status colour marks a status, and only where it applies. A count of a problem (failed attempts, tasks not started) takes its status colour above 0; at 0 it is neutral, with its words (“All attempts done”). A total is never coloured by its worst part.
- A status also has a shape and a word, so it reads without colour: ✓ done, ✕ failed, ◌ not started, a round mark for done and a square one for failed.
- One rule set draws every status. An element that shows a status carries
data-tone, its tone (failed,attention,done,running,muted; none forneutral), which picks its--ca-status-*colour and tint, anddata-markfor its mark. The primitives set both (Primitives). - One accent for links and the selected tab or item; the focus ring has its own colour and width.
- Identity colours are small cues (an agent task’s icon and edge, the token bars, the timeline’s bars): none of them is a status hue.
| Properties | Meaning |
|---|---|
--ca-backdrop, --ca-bg, --ca-card, --ca-panel, --ca-panel2, --ca-raise, --ca-well, --ca-track, --ca-line, --ca-line2, --ca-shadow |
the page, surfaces, borders and shadows |
--ca-text, --ca-text-secondary, --ca-text-muted |
text, from the main text to labels and units |
--ca-accent, --ca-accent-bg |
what can be clicked or is selected |
--ca-focus, --ca-focus-width |
the focus ring |
--ca-selection |
selected text |
--ca-status-failed |
failed: a task, an attempt, a check |
--ca-status-attention |
not failed, needs a look: not started, a usage limit near |
--ca-status-done |
done or passed: in marks and small chips only |
--ca-status-running |
running, live |
--ca-status-muted |
skipped, waiting, not run |
--ca-status-*-bg, --ca-tint |
the tint behind a status pill or chip: by default the status colour at --ca-tint |
--ca-agent, --ca-agent-bg |
identity: an agent task |
--ca-meter |
a bar that measures, not judges: time, usage, progress |
--ca-tok-input, --ca-tok-cache-read, --ca-tok-cache-write, --ca-tok-output |
the kinds of tokens in a bar |
--ca-series-1 to --ca-series-6 |
the series of a chart, in a fixed order; never a status, and each series also named in words |
--ca-code-* |
syntax colours in code, prompts and JSON |
--ca-color-scheme |
light or dark: native controls and scrollbars |
--ca-sans, --ca-mono |
the fonts |
--ca-font-size-xs to --ca-font-size-2xl |
the type scale |
--ca-radius-sm, --ca-radius-md, --ca-radius-lg, --ca-radius-pill |
the corners |
Every property, its meaning and its value in each built-in theme are in the
site’s theme reference, generated from src/themes/tokens-list.ts (TOKENS),
src/themes/token-meanings.ts (TOKEN_MEANINGS)
and src/themes/data.ts (THEMES).
Type and corners
Section titled “Type and corners”The type and radius scale is the same in every theme: no theme sets it, and
each step has a default. A --ca-* property on an ancestor changes a step for
every element under it.
{ name: 'font-size-xs', prop: '--ca-font-size-xs', derive: '10px' }, { name: 'font-size-sm', prop: '--ca-font-size-sm', derive: '11px' }, { name: 'font-size-md', prop: '--ca-font-size-md', derive: '12px' }, { name: 'font-size-lg', prop: '--ca-font-size-lg', derive: '13px' }, { name: 'font-size-xl', prop: '--ca-font-size-xl', derive: '15px' }, { name: 'font-size-2xl', prop: '--ca-font-size-2xl', derive: '28px' }, { name: 'radius-sm', prop: '--ca-radius-sm', derive: '4px' }, { name: 'radius-md', prop: '--ca-radius-md', derive: '8px' }, { name: 'radius-lg', prop: '--ca-radius-lg', derive: '12px' }, { name: 'radius-pill', prop: '--ca-radius-pill', derive: '999px' },A theme of your own
Section titled “A theme of your own”Set the --ca-* properties on an element, here with a class: on the element
that carries data-ca-theme, or on one inside it (on <body> under
<html data-ca-theme>). Build it
the way the built-in themes are built: each text colour keeps a contrast of at
least 4.5:1 (WCAG AA) on every surface and on every status tint.
/* A theme of your own: Solarized Light (https://ethanschoonover.com/solarized/, MIT, (c) Ethan Schoonover), built the way the built-in themes are. Every colour is a --ca-* property, inherited through the elements' shadow roots; one set here overrides the built-in theme's value, token by token.
Surfaces and text come from the palette's base tones, the accent is its blue, the statuses its red, yellow, green and cyan, the code colours its syntax colours. Solarized's accents are mid-tones: where one reads under 4.5:1 on these surfaces, it is darkened in lightness only, with its hue kept. */.solarized-light { --ca-color-scheme: light; /* surfaces and text: base3, base2 and base02 to base01 */ --ca-backdrop: #eee8d5; --ca-bg: #eee8d5; --ca-card: #fdf6e3; --ca-panel: #fdf6e3; --ca-panel2: #fdf6e3; --ca-raise: #eee8d5; --ca-well: #f5efdc; --ca-track: #e4ddc8; --ca-line: #e4ddc8; --ca-line2: #93a1a1; --ca-shadow: #586e7533; --ca-text: #073642; --ca-text-secondary: #365059; --ca-text-muted: #4b6068; /* interaction: blue */ --ca-accent: #02629b; --ca-focus: #268bd2; --ca-selection: #268bd233; /* status: red, yellow, green, cyan, base01 */ --ca-status-failed: #bd0112; --ca-status-attention: #7a5b01; --ca-status-done: #586604; --ca-status-running: #046b64; --ca-status-muted: #4e646c; --ca-tint: 10%; /* identity: violet, and the token bars */ --ca-agent: #5a5eaf; --ca-meter: #93a1a1; --ca-tok-input: #268bd2; --ca-tok-cache-read: #e4ddc8; --ca-tok-cache-write: #93a1a1; --ca-tok-output: #6c71c4; /* code */ --ca-code-keyword: #657402; --ca-code-string: #077a73; --ca-code-number: #c82979; --ca-code-constant: #c14204; --ca-code-comment: #626f6f; --ca-code-function: #0071b3; --ca-code-type: #896705; --ca-code-name: #0071b3;}