Kontour Design System#

Status: canonical design direction for Kontour product interfaces and brand usage. Audience: designers, engineers, product teams, and coding/design agents. Core promise: Trust the work AI does.

This file is the design constitution for Kontour AI. It follows the DESIGN.md format: machine-readable tokens in the front matter, roles and rules in the body. It was restructured from the owner-supplied design draft (2026-09-27) and reconciled against the shipped --k-* token contract.

This file states roles and rules. Values live in tokens/. The front matter is generated from tokens/tokens.css, tokens/themes.css, and react/styles.css by scripts/generate-design-md.mjs; npm run check:design fails when it drifts. Change a value in tokens/, then run node scripts/generate-design-md.mjs --write. Never hand-edit the front matter. Prose restates a shipped value only as a backticked token name, an equals sign, and a backticked value (optionally with a scope such as (light) or (flow) after the name). The same check verifies every such quote against tokens/, wherever it appears (table header rows and "Draft" columns included). It also fails when any other value-looking text (a px/em/rem size, a hex or rgb()-style color, or a bare number) appears anywhere on a prose line, or anywhere in a table row (header rows included), that names a token. For that second rule only, table columns whose header starts with "Draft" are exempt, since they hold the draft's direction rather than shipped values. It ignores link targets (link text is still scanned), #n issue references except 3-, 4-, 6- or 8-digit ones (those read as hex colors, so such an issue beside a token must be written as a link), acronym versions such as WCAG 2.2, and OPEN-n ids. Not covered: a value placed on a different line from the token name.

Reading the packaged copy under node_modules/@kontourai/ui/: the generator script is not in the package. The tokens/*.css files next to this file are the source of every value; the front matter is a projection of them made before publishing.

Authority#

Precedence, highest first: approved canonical SVGs and tokens → this file → approved product docs → existing, unsuperseded production patterns → reference artwork → generated concepts. Token values are canonical as shipped (owner decision, 2026-10-01): tokens/ holds the palette and the spacing, radius, and type scales, and this file restates them. Where the earlier draft named different values, the draft was not adopted; its values stay in this file only as a record of what was considered. Changing a token value is a design-system change and needs its own decision. Generated images are references, not production assets. A generated concept sheet is held privately by the owner; it is reference only, never a production source.

Anything marked OPEN is unresolved. Do not silently resolve it in either direction: an OPEN item records what ships today and where the design is headed, and both stay true until someone decides. An item marked Decided keeps its id and carries the date and who decided. The full register is in Open Decisions.

Reading the token block#

The DESIGN.md format has no concept of modes or themes, so the generated keys encode them:

Key shape Meaning Example
name The CSS custom property minus --k-, dark default (:root) space-5 is --k-space-5
name-light The value under [data-theme="light"] bg-light
name-<theme> The value under .theme-<theme> (dark) brand-flow
name-<theme>-light The value under [data-theme="light"] .theme-<theme> brand-flow-light

A reference such as focus-ring: "{colors.focus}" means "the focus value of the same mode and theme". CSS custom properties that use var() resolve where declared and descendants inherit the computed value. So every scope that sets the focus role redeclares the compatibility --k-focus-ring alias, and every scope that sets a status tone redeclares its soft fill (npm run check:tokens enforces both). Each theme's light block matches the theme class and data-theme="light" on one element, the class below the attribute, and the attribute below the class, so those three placements resolve to the theme's light values; which of them applies when modes or themes nest is set by Theme and mode scoping. One thing is not covered:

  • A local inline override of an input (say, the focus role on one panel) does not update the alias on that element's descendants. That is why consumer CSS should read --k-focus rather than the alias. primary is not a CSS token; it is derived from whatever .btn-primary paints, so the format's required primary color stays truthful.

Type levels (display, title, body-md, label, eyebrow, badge, button) are read from the shipped selectors that use them, and text-* levels are the bare size steps. Components are read from their classes in react/styles.css; properties that are shorthands or calc() expressions, and rules inside @media, are left out rather than approximated. Tokens the format cannot carry (font-family stacks, shadows, soft status fills, motion, stroke widths, line-height and letter-spacing scales, stacking order, trust-state line styles) are listed with their values in a comment at the end of the front matter, so the drift check covers every token.

The generator buckets each --k-* token by name pattern; an alias (var(--k-other)) takes its target's bucket; any other token whose value is a literal hex or rgb()/rgba() color is treated as a color. A token that fits none of these, or a token rule nested in an at-rule, makes the generator fail until someone decides how this file represents it.

npx @google/design.md lint DESIGN.md reports every color that no generated component references as an orphaned-tokens warning: default structure tokens such as bg, line, line-strong, and text-faint, the status tones, the trust-state inks and fills, and every light and theme variant. That is expected: only five components are generated, for the dark default, and status tones and trust states are consumed by their classes rather than a component entry.

Token architecture#

The draft's direction: primitive tokens → semantic tokens → component tokens → justified product overrides, and "components consume semantic tokens whenever practical." What ships maps onto it only partly:

  • Primitive (not shipped): there is no separate palette layer; tokens/tokens.css assigns literal values straight to semantic names. The draft's named brand palette (Ink, Stone) was not adopted (#74, decided 2026-10-01); see Product themes.
  • Semantic (shipped, partly): the --k-* structure, text, status, trust-state, action, and focus roles. --k-action and --k-action-contrast form the primary action pair; --k-focus supplies keyboard focus. They are independent of the product brand slot.
  • Component (not shipped as tokens): primitives in react/styles.css read semantic tokens directly. The front matter's components entries are derived from those rules; they are not a separate token layer.
  • Justified product overrides (shipped as theme classes): product themes set the identity slot (--k-brand, --k-brand-contrast) and their interaction roles independently. .theme-console also sets a fuller surface and status skin. A white-label deployment can change only the identity slot without repainting primary buttons or focus rings.

Components consume semantic tokens. Do not scatter raw hex values, font sizes, arbitrary radii, or one-off spacing through repositories; npm run check:tokens already forbids literal colors in react/styles.css.

Overview#

Kontour AI makes software that helps end users understand, inspect, validate, and trust work performed by AI. A contour makes hidden structure legible; Kontour applies that idea to AI work:

Turn uncertainty into clarity.

The product should not demand trust. It exposes enough evidence, provenance, state, and explanation for users to establish justified confidence.

The brand feels precise, calm, rigorous, clear, capable, contemporary, and human; technical without hype. Avoid cyberpunk, magical-AI imagery, surveillance aesthetics, fear, generic enterprise SaaS, and complexity for its own sake.

Interfaces feel calm, deliberate, inspectable, organized, and information-rich without noise.

The Kontour world#

Kontour owns the terrain. Products own what happens within it.

Identity Phenomenon Meaning
Kontour Contour field resolving into clarity AI work becoming legible
Station S-shaped river or path Orient, inspect, establish state
Flow Directional movement Work progressing through a system
Surface Defined, exposed region A complete state made visible
Future products Purpose-specific phenomenon Must map to actual behavior

Products share a grammar (contour-derived geometry, compatible line weights, controlled organic forms, environmental semantics, flat and monochrome viability, typography, and restrained material depth), not one recolored icon.

Brand hierarchy#

The company name is Kontour AI. Decided: the preferred lockup text endorses a product under the company name.

Station
by Kontour AI

Surface
by Kontour AI

OPEN-9 — Flow endorsement form and product tiering. Whether the endorsed product is presented as "Flow" or "Flow Agents", and how other products are tiered, is open.

Don't invent compound names that stack the company name, a product name, and "AI" or "Platform" (e.g. "Kontour <Product> AI"); use the endorsement form above, or the approved one once an open entry is decided.

Colors#

Color roles, not values. The values for each mode and theme are in the front matter.

Token Role
--k-bg Page canvas.
--k-panel Default container surface.
--k-panel-raised Raised or inset surface inside a panel (controls, nested regions).
--k-line, --k-line-strong Hairline separation; line-strong for control borders and emphasis.
--k-text, --k-text-muted Primary and secondary text.
--k-text-faint Tertiary text (hints, timestamps, counts). Meets WCAG AA for normal text on --k-bg, --k-panel, and --k-panel-raised in every theme and mode; npm run check:contrast rates all three.
--k-brand, --k-brand-contrast The product identity slot, and its paired text color when a brand-filled surface is used.
--k-action, --k-action-contrast Primary action fill and its text; an independently checked pair.
--k-focus Keyboard focus color, checked against page and panel surfaces.
--k-status-contrast Text on filled status tones, independent of the brand slot.
--k-positive, --k-caution, --k-negative, --k-active, --k-neutral Shared status tones (see below).
--k-*-soft Low-alpha tone fills behind status text; derived at runtime from the active tone.
--k-focus-ring Compatibility alias of --k-focus; redeclared in scopes that change focus. New primitives read the focus role directly.
--k-trust-<state> Trust-state ink: the label, glyph, and border of that state's chip (see Trust UX). One per state, never shared.
--k-trust-<state>-fill The chip's own background, so its label pair does not depend on the surface it sits on.
--k-trust-<state>-line The state's line style, a border-style keyword: the non-color cue for chip borders; charts translate it to a stroke (see Data Visualization).
--k-basis-caveat-decoration The trust-basis caveat cue: a dashed underline, a text-decoration value that names no color, so it takes the muted text's color (see Trust basis).

White-label overrides#

Override --k-brand to change identity accents such as eyebrows, the topbar edge, progress, and the activity spinner. An override of that property alone does not change primary buttons, checked controls, or focus because those read the interaction roles.

Where an override goes decides whether it applies:

  • Set values per mode. One value cannot serve both modes, so an inline style on a single element is only suitable when the mode never changes.
  • Declare them with the same selectors the theme uses, in a stylesheet loaded after the tokens: the .theme-<theme> base block, and for light mode all three forms of its light block ([data-theme="light"].theme-<theme>, [data-theme="light"] .theme-<theme>:where(:not(...)) and :where(.theme-<theme>) [data-theme="light"]:where(:not(...)), copying each :not(...) tail from tokens/themes.css), and for a dark element below a light one the theme's dark-island block. A declaration on :root loses to the theme's light block whenever a theme class is present, and an inline style on <html> does not reach a theme class placed on <body>.
  • With no theme class, override :root for dark and [data-theme="light"] for light, and add the dark reset [data-theme="dark"]:where([data-theme="light"] *) beside :root: a dark element below a light one resets there, not to :root.

Migrating overrides. Dark islands now resolve dark (ui#80), so an override written with the earlier selectors can misplace values. Change each selector as follows:

Earlier selector Now
.theme-<theme> (dark) keep it, and add the theme's dark-island block [data-theme="dark"]:where(.theme-<theme> *):where([data-theme="light"] *):where(:not(...)); without it a dark element below a light one shows the shipped product brand
[data-theme="light"] .theme-<theme> [data-theme="light"] .theme-<theme>:where(:not(...)); without the tail a dark theme element on a light page takes the light override onto dark surfaces
:where(.theme-<theme>) [data-theme="light"] :where(.theme-<theme>) [data-theme="light"]:where(:not(...)), so the override stops at a nested theme
:root (no theme class) add [data-theme="dark"]:where([data-theme="light"] *)

Each :not(...) stands for a tail to copy from tokens/themes.css; docs/consumer-guide.md shows a full example. npm run check:design checks every selector named in this section (except the earlier ones) against the shipped CSS, for every theme.

Rules for the values:

  • Brand used as text (eyebrows, panel counts) must meet AA text contrast on the panel in each mode. If a brand-filled surface carries text, set --k-brand-contrast with it and check that pair too.
  • --k-action and --k-action-contrast are a pair: override both, per mode, never the fill alone. The shipped action text is chosen for the shipped fill, and a new fill without its text can drop below AA.
  • Check --k-focus against both the page and the panel (non-text contrast).
  • Status-filled surfaces use --k-status-contrast; leave it unless the status tones change.
  • A runtime that applies a theme at run time (rather than a reviewed stylesheet) must reject a pair that fails the thresholds npm run check:contrast applies to the roles: AA text contrast for text on a fill, and non-text contrast for the focus ring and checked controls. Use validateBrandOverride from @kontourai/ui/contrast, which applies those thresholds and the allowlist above; see "Validating a white-label theme" in docs/consumer-guide.md.

--k-focus-ring remains as an alias for existing consumer CSS; new CSS reads --k-focus.

Product color is identity, not status#

Product accents express which product you are in. They never encode state, success, risk, or confidence, and never form an implicit status scale in charts. Status uses the status tones.

The shared status scale maps product words to five tones: positive, caution, negative, active, neutral (see docs/adr/0001-console-kit-token-contract.md). Never encode critical state through color alone; status always carries text, and icon or color reinforces it.

OPEN-7 — status model. Decided (owner, 2026-10-01): five tones plus the nine trust states. The status model is the five tones above and, separately, the trust states (Surface's claim statuses, decided in OPEN-12) with their own tokens, TrustState, and k-trust-state (see Trust UX). The draft's separate semantic tokens for success, warning, danger, information, pending, and neutral are not adopted. A review of the token consumers (Station, Surface, Survey, and Flow) found none that reads a status token outside the five tones (#88). Product words map onto the five tones through toneForValue or a product's own mapping: success is positive, warning and pending are caution, error is negative.

A new tone needs a named consumer: a shipped product surface with a status that none of the five tones and none of the nine trust states can express, recorded in an issue before any token is added. Use a trust state, not a tone, for the status of a claim; never let two different trust states render identically.

Product themes#

One theme class on a stable root selects the product identity (docs/consumer-guide.md):

  • .theme-flow, .theme-surface set brand and interaction colors.
  • .theme-survey sets brand and interaction colors plus canvas and faint text, in each mode (its light canvas is the default light canvas).
  • .theme-station sets the draft accent as its base with mode-specific contrast adjustments.
  • .theme-console is a full skin: brand, interaction colors, surfaces, lines, text, status, radii, and its own font stacks.

OPEN-6 — brand palette and product accents. Decided (owner, 2026-10-01): the shipped values are the palette. No token changed with this decision. The draft's values are listed beside them as a record only:

Role Canonical (shipped) Draft value, not adopted
Corporate ink none; dark canvas --k-bg = #0a0e13 Ink #0B1220
Corporate light ground none; light canvas --k-bg (light) = #f5f4ef Stone #F2EFEA, White #FFFFFF
Station accent .theme-station: --k-brand (station) = #966aff, --k-focus (station) = #9364ff, --k-action (station) = #7c3aed; --k-brand (station-light) = #7c3aed #7C3AED
Flow accent .theme-flow: --k-brand (flow) = #3890ae, --k-action (flow) = #2f88a6; --k-brand (flow-light) = #1f6f88 #2563EB
Surface accent --k-brand (surface) = #14a37a, --k-brand (surface-light) = #0f6b52 #14B8A6
Default brand --k-brand = #5ce0c6, --k-brand (light) = #0e7c64 not specified

The draft's Ink, Stone, and Flow and Surface accent values are not adopted, so the palette migration (#74) closes without a value change. There is no separate palette layer: do not add --kontour-* custom properties. Dark-mode colors are the shipped :root values. No color ramps ship; adding one would be a new decision. Changing any value above is a design-system change: it needs its own decision and must keep npm run check:contrast passing.

Station ships the owner's draft accent, adjusted only where contrast requires it. In dark mode, the draft accent remains the action fill with white text; the focus ring uses a lighter same-hue accent, and brand text a slightly lighter one again so it reads at 4.5:1 on the page, panel, and raised panel. In light mode, the draft accent is brand, action, and focus. npm run check:contrast rates every one of these pairs in both modes.

Flow dark, Console light, and Survey light follow the same rule. Brand used as text is rated at 4.5:1 on the page, panel, and raised panel in every theme and mode, by check:contrast and by validateBrandOverride alike. Flow's dark brand is its accent lightened at the same OKLCH chroma and within 0.3° of its hue until brand text reads at 4.5:1 on all three surfaces; the action and focus keep the accent. Console's light brand is its lime darkened (hue within 0.1°) for the same reason. It keeps white brand-contrast text, since dark text on the darker lime would fall below 4.5:1, and the action and focus keep the brighter lime with dark text. Survey's light brand is its accent darkened at the same hue and chroma just enough to read on the light canvas; the action and focus keep the accent, and brand-contrast stays white.

OPEN-10 — Console and Survey themes. Decided with OPEN-6 (2026-10-01). The draft did not cover theme-console or theme-survey. Their shipped values are canonical like every other token value, so nothing about them waits on a palette migration.

OPEN-11 — marketing-site palette. Decided (owner, 2026-10-01): a documented exception, for now. The marketing site (kontourai.io) keeps its own palette and is not required to use these product tokens or themes. The exception covers that site only; product interfaces keep to the --k-* tokens. Nothing here schedules a convergence: moving the site onto these tokens would be a new decision.

Modes#

OPEN-5 — default mode. Decided (owner, 2026-10-01): dark is the default. :root carries the dark contract (color-scheme: dark), and light is opt-in with [data-theme="light"]. The draft's light Stone/Ink default is not adopted. Design every surface for both modes.

Choosing a mode:

  • Dark (the default). Set nothing: a page with no data-theme renders dark. data-theme="dark" only has an effect below a light element, where it restores the dark contract.

  • Light. Put data-theme="light" on the root element (<html data-theme="light">), or on any element to make that subtree light.

  • Follow the OS preference. The tokens ship no prefers-color-scheme rule, so a page that sets nothing stays dark whatever the OS prefers. To follow the OS, the page sets the attribute from the media query. Run this inline in <head>, before the first paint, so a light-preferring visitor does not see a dark flash:

    <script>
      (() => {
        const root = document.documentElement;
        const prefersLight = matchMedia("(prefers-color-scheme: light)");
        const applyMode = () => {
          let stored = null;
          try { stored = localStorage.getItem("theme"); } catch {}
          const light = stored === "light" || (stored !== "dark" && prefersLight.matches);
          if (light) root.dataset.theme = "light";
          else delete root.dataset.theme;
        };
        applyMode();
        prefersLight.addEventListener("change", applyMode);
      })();
    </script>
    

    A stored choice wins over the OS preference: if the product has its own mode switch, it stores "light" or "dark" (here under the theme key; use your own) and the script applies that value, on load and when the OS preference changes. With nothing stored, the page follows the OS. A strict Content Security Policy blocks inline scripts, so give this one a nonce or load it as an external script placed first in <head>.

Dark mode is not inversion. Preserve hierarchy, restraint, product identity, contour legibility, status semantics, and contrast. Light-mode status and brand hues are darkened so text clears WCAG AA on light panels; keep that property when changing them (npm run check:contrast). Fine contour artwork may need optical adjustment on dark surfaces. The dark surface, text, and line values are the shipped :root values (decided with OPEN-6).

Theme and mode scoping#

Rule: the nearest data-theme picks the mode, and the nearest theme class picks the product. Both are counted from the element itself outward, at any depth, and independently:

  • An element with data-theme resolves to its mode's contract plus its nearest theme's values for that mode. A theme further out contributes no mode-dependent tokens to it (its mode-independent ones, such as radii and fonts, still inherit).
  • data-theme="dark" inside a light element restores the dark contract (surfaces, text, status, trust states, and roles) and its nearest theme's dark values. With no light element above it there is nothing to restore, and it inherits.
  • A theme class with no data-theme of its own takes the nearest mode above it. Its values layer over what it inherits, in both modes: .theme-flow inside .theme-console keeps Console's surfaces and uses Flow's brand and roles.
  • Each theme's light block sets every mode-dependent token its base block sets, so light mode never keeps a dark value, and sets nothing else: a token outside the mode reset (a radius, a font) would carry its light value into a dark island below. npm run check:contrast fails either way.

How it is built: plain selectors with :where(:not(...)) tails. The class-below-attribute light form stops at a nearer data-theme="dark"; the attribute-below-class form stops at a nearer (or its own) other theme class; dark islands have their own blocks that only match below a light element. :where() keeps every selector's weight as it was, so a white-label override written later with the same selectors still wins in every documented placement. Browser support is that of :where() and complex selectors inside :not(): Chromium 88, Firefox 84, and Safari 14 or later.

@scope was considered and not used: a scoped rule outranks an unscoped rule of the same weight, so shipped scoped rules would beat existing white-label overrides written with the documented selectors, and every value would be declared twice (a scoped copy beside the fallback for browsers without @scope).

Not covered: the theme class of an element below a mode that switches back (light, then dark, then light again above it) matches as if the inner light were absent. Put data-theme on that theme element. The same holds for a theme nested inside itself through another theme.

Typography#

Three family roles:

Token Role Draft role name
--k-font-display Display and titles: page headings, dialog titles. font-brand
--k-font-ui Interface text: controls, buttons, body. font-ui
--k-font-mono Identifiers, labels, badges, metadata, numerals in dense data. font-mono

Typography is precise, contemporary, calm, intelligent, slightly human, and highly legible. Avoid sci-fi, techno, novelty-map, excessively geometric, and generic rounded SaaS faces. Identifiers and technical values use the mono role and must wrap and copy gracefully.

OPEN-4 — typefaces. Decided (owner, 2026-10-01): keep the shipped families. Fraunces (display), Hanken Grotesk (UI), and IBM Plex Mono (mono), self-hosted in tokens/fonts/; .theme-console swaps display and UI for a condensed stack. The draft asked for a humanist or restrained neo-grotesk with open counters, strong numerals, excellent UI rendering, useful weights, and enough character to avoid sameness, and left the family undecided; this decision closes that search. Generated concept typography is not a production font and not a wordmark; the wordmark is a separate decision (see Wordmark).

OPEN-3 — type scale. Decided (owner, 2026-10-01): the shipped scale is canonical. Shipped steps run from --k-text-xs = 11px to --k-text-2xl = clamp(26px, 3.4vw, 38px); the top step is fluid, and the front matter records its upper bound. The draft's named levels are not adopted and are kept as a record only (do not create these tokens):

Draft level (not adopted) Size / line Use
display-xl 64 / 68 major hero
display-lg 48 / 52 page hero
heading-xl 36 / 42 page heading
heading-lg 28 / 34 section heading
heading-md 22 / 28 panel heading
body-lg 18 / 28 emphasized prose
body-md 16 / 24 default body
body-sm 14 / 20 dense UI
label 13 / 18 controls, metadata
caption 12 / 16 secondary metadata

Note that shipped control text is --k-text-md = 14px, the size of the draft's body-sm.

Shipped styling sets text-transform: uppercase on buttons, badges, status labels, field labels, metric labels (.metric span), top-bar meta labels (.topbar-meta-item span), eyebrows, panel headings, and titles (.topbar h1, .dialog__title). That is current implementation, not a rule from the draft; keep it consistent until a typography decision changes it.

Wordmark#

Primary naming: Kontour AI. Generated concept typography is not a production font or a custom wordmark.

Decided (owner, 2026-10-05) — wordmark. The wordmark spells "Kontour", not "Kontour AI". That decides the word on the wordmark only: the company name and the endorsement text in Brand hierarchy ("<Product> by Kontour AI") are unchanged.

Adopted with it, as proposed in #114. The cut of icons/kontour-wordmark.svg, outlined from Fraunces at weight 600 and optical size 144 (see Production assets), is in force. It was not separately put to the owner, so changing it does not reverse an owner decision.

Layout#

Use a 4px base rhythm, and only the spacing tokens.

OPEN-1 — spacing scale. Decided (owner, 2026-10-01): the shipped six steps are canonical. The draft numbered its steps differently and is not adopted. Danger when reading older layouts: the same step numbers mean different sizes.

Canonical (shipped) Draft, not adopted
--k-space-1 = 4px space-1: 4px
--k-space-2 = 8px space-2: 8px
--k-space-3 = 12px space-3: 12px
--k-space-4 = 16px space-4: 16px
--k-space-5 = 24px space-5: 20px
--k-space-6 = 32px space-6: 24px
not shipped space-8: 32px
not shipped space-10: 40px
not shipped space-12: 48px
not shipped space-16: 64px
not shipped space-20: 80px
not shipped space-24: 96px

A layout written against the draft's space-5 or space-6 renders larger than intended on the shipped tokens. Always read the step's value from the front matter, never from its number. Do not add --space-* properties. Any renumbering is a breaking change for every consumer.

Layout rules:

  • Prioritize outcome and status, reveal evidence progressively, keep provenance accessible.
  • Responsive layouts preserve hierarchy rather than merely stacking desktop panels.
  • Technical identifiers wrap and copy gracefully.
  • Group with cards and panels only when the grouping means something; avoid card soup.
  • Final breakpoints are OPEN.

Agents may extend spacing and components responsively when consistent with the tokens and existing patterns.

Elevation & Depth#

Depth comes from tonal layers first: --k-bg → --k-panel → --k-panel-raised, separated by --k-line. Prefer restrained borders and surface shifts to heavy shadows.

  • --k-shadow is the soft panel shadow.
  • --k-elevation-overlay is the single layered shadow for floating surfaces (dialogs, menus, palettes, toasts), so elevation tunes in one place.
  • --k-z-* is the stacking scale; overlays read it instead of inventing local z-index values.

Shadow and stacking values are in tokens/tokens.css and listed in the front matter comment.

Shapes#

Do not round everything. Shipped radius roles:

Token Role
--k-radius-control Tight radius for dense controls (inputs, selects).
--k-radius-overlay Dialogs, popovers, and other floating surfaces.
--k-radius-sm Buttons and badges.
--k-radius-md Framed placeholder cards (.empty--prominent, .empty--compact).

Shipped .panel sets no radius: panels are square-cornered, so do not assume containers are rounded. .theme-console sets --k-radius-sm (console) = 0, --k-radius-md (console) = 0, --k-radius-control (console) = 0, and --k-radius-overlay (console) = 0.

OPEN-2 — radius scale. Decided (owner, 2026-10-01): the shipped radii are canonical. They are --k-radius-sm = 9px, --k-radius-md = 14px, --k-radius-control = 4px, --k-radius-overlay = 10px. The draft's 6 / 10 / 16 / 24px as radius-sm / md / lg / xl is not adopted. The names overlap and the values do not; do not add --radius-* properties.

Components#

The shipped primitives (@kontourai/ui/react, @kontourai/ui/elements) implement these rules; docs/explorer-manifest.json lists each one's states and token contract.

  • Buttons. Clear primary, secondary, tertiary, and destructive hierarchy. Mapped to the shipped variants: primary → primary; secondary → ghost; tertiary → no shipped variant (OPEN); destructive → no defined pattern (OPEN; negative is the nearest shipped variant, a status-toned button). positive and caution are status-toned buttons outside the draft's hierarchy. The product accent is not automatically the primary action color: .btn-primary reads --k-action and --k-action-contrast, while focus reads --k-focus. Existing themes keep their familiar fills except the Console light action text, which uses a dark contrast role to meet AA. Future role values can differ from the brand independently.
  • Inputs (Field, Input, Select, Textarea, Checkbox, Toggle). Visible labels; placeholders are not labels. Validation explains recovery.
  • Cards and panels (Panel). Only for meaningful grouping.
  • Tables. Optimize scanning, alignment, sorting and filtering, copyable values, and explicit statuses. (No shipped primitive yet.)
  • Inspectors. Preserve context while exposing deeper evidence and provenance. (No shipped primitive yet.)
  • Dialogs (Dialog). Reserve interruption for actions that need focus or confirmation.
  • Status (Badge, StatusBadge, StatusBar). Always include text; icon and color reinforce it.
  • Trust state (TrustState, k-trust-state). A chip naming one of Surface's nine claim statuses, with an optional visible detail that says what was checked. See Trust UX.
  • Trust basis (TrustBasis, k-trust-basis). The muted line after a trust-state chip that says how the status was established, rendered from Surface's claimBasisView. See Trust basis.
  • Loading (Skeleton, Spinner, Progress). Communicate useful state without fake precision. Never imply completion while work is pending.
  • Empty states (Empty). Explain purpose and the next action.
  • Errors. State the failure, its impact, recoverability, and the next action; reveal technical detail progressively.
  • Iconography. UI icons use simple geometry, consistent optical weight, thin-to-medium strokes, and excellent small-size legibility. Do not turn utility icons into contour art. Avoid humanoid robots, glowing brains, floating code, and cyber grids.

Do's and Don'ts#

Token rules:

  • Do style with --k-* tokens; don't hard-code hex values, font sizes, radii, or spacing.
  • Don't introduce the draft's --kontour-*, --space-*, or --radius-* names as CSS.
  • Don't assume a step number means the same size in the draft and the shipped scale (OPEN-1).
  • Don't use product accents as status, and don't use status tones as identity.
  • Don't hand-edit this file's front matter; regenerate it.

Agents MUST preserve:

  1. the contour and environment metaphor
  2. the corporate/product distinction
  3. Station's ownership of the S-river
  4. each product's unique phenomenon
  5. the semantic separation of product color and status color
  6. uncertainty → clarity
  7. progressive trust inspection
  8. flat and monochrome viability
  9. accessibility
  10. canonical asset authority

Agents MUST NOT:

  1. put Station's S-river in the company logo
  2. add a compass to every product
  3. make every product a recolored contour blob
  4. introduce generic AI motifs
  5. use gradients as a substitute for structure
  6. assume concept typography is final
  7. redraw canonical SVGs without an explicit identity task
  8. invent unresolved brand decisions, or resolve an OPEN item silently
  9. hide uncertainty for visual cleanliness
  10. reduce provenance to decorative metadata

Agents MAY extend spacing and components responsively when consistent with the tokens and existing patterns. Changes to master marks, the core palette, product semantics, typeface families, or trust-state semantics are design-system changes and need an explicit decision.

Marks & Identity#

Corporate mark#

The Kontour symbol is a contour field moving from uncertain → resolved: one area continuous and legible, another interrupted, sparse, dotted, or incomplete. Use roughly 4–6 nested paths from related geometry. Preserve deliberate spacing, small-size legibility, controlled organic variation, and a distinctive silhouette. Avoid perfect concentricity and decorative noise.

Never add Station's S-river, a compass rose, a literal mountain, a location pin, a checkmark, shield, lock, brain, neural nodes, circuitry, AI sparkles, a robot, a chat bubble, a generic hexagon, or an infinity loop to the corporate mark.

Decided (owner, 2026-10-05) — corporate symbol. The owner kept the proposed symbol, icons/kontour-symbol.svg, as shipped: three nested circular contours whose centres step down and to the left, the outer one continuous around the lower left and broken into two dashes and a dot at the upper right, on the product marks' 24-unit frame and stroke weight.

It departs from the direction above in three ways. They were listed in the proposal (#114) and are accepted with the symbol rather than open:

  • it has three nested contours, not 4–6; the dashes and dot are pieces of the outer contour, not further contours
  • every contour is a perfect circular arc, so there is no organic variation; only the offset centres keep it from being concentric
  • at 16px the contours close up and the broken section stops reading; it reads from 24px, which is why the usage rules prefer 24px or more for it

The direction above still guides other contour artwork; it does not reopen the symbol.

Station#

A river or path forming a subtle S through a contour environment; the S emerges from the landscape rather than appearing as a letter. Station-specific: never the corporate mark. Station communicates orientation, location, arrival, state, inspection, checkpoint, reference, and reliability. A compass or reference marker may stay Station-specific if simplified, subordinate, and semantically useful. Do not propagate it to every product. Release channels (stable, beta, dev, nightly) may use controlled accent variants that keep the geometry; the channel colors are OPEN.

Flow#

Progression, routing, transfer, orchestration, transformation: work moving through a system. Use contour-derived directional behavior. A subtle F may emerge naturally; never force a literal F. Flow must stay distinct from Station at app-icon size.

Surface#

A state, region, layer, or plane made visible: exposed plane, plateau, basin, bounded region, section, or revealed layer. See the state clearly. Surface is not another generic contour blob; the surface itself is the defining phenomenon.

Future products#

Start with: what phenomenon in the Kontour world represents what this product actually does? Territories such as ridge, field, basin, trace, range, layer, boundary, current, crossing, or vale are concepts, not reserved names. Before approving an identity, document its function, phenomenon, semantic mapping, differentiation, shared grammar, 24px behavior, and monochrome behavior.

Flat vs. dimensional#

Every core mark needs a flat version for navigation, favicons, docs, badges, CLI, and small UI. Dimensional or cartographic treatment (shallow elevation, subtle shadow, terrain or paper material, inset paths, restrained depth) is allowed for app icons, launch screens, marketing, product cards, and presentations. Avoid photorealism. If removing gradients, texture, shadow, embossing, and color destroys the identity, the mark is unfinished.

Contour artwork belongs in identity, hero moments, explanatory diagrams, meaningful empty states, and selective launch or loading moments, not behind every card.

Production assets#

The marks ship in icons/, each as an SVG file (@kontourai/ui/icons/<name>.svg) and as a component. Every shipped mark is in force; what the owner decided and what was adopted with it differ:

Marks Status
The 22 product icons Decided (owner, 2026-10-01; #75): promoted to production marks as drawn, having passed the gates below.
kontour-symbol.svg Decided (owner, 2026-10-05), as shipped. See Corporate mark.
kontour-wordmark.svg The word "Kontour": decided (owner, 2026-10-05). The cut: adopted with it, as proposed. See Wordmark.
kontour-lockup-horizontal.svg Adopted with the decided marks, as proposed: symbol, then wordmark, at the shipped gap. Not separately decided. Composed from the other two, never drawn separately.

The product marks are flat UI icons. They do not implement the contour-derived directions above for Station, Flow, and Surface, and redrawing toward those directions stays open. The owner decided (2026-10-05) that no such redraw is published until the owner has seen it rendered at real sizes (the contact sheet below, at 16, 24, and 32px). A stacked lockup and per-product lockups do not exist.

Components: ProductIcon and <k-product-icon> for product marks; BrandMark (KontourSymbol, KontourWordmark, KontourLockup) and <k-brand-mark> for the corporate marks. The path modules under react/src/ are the source; node scripts/check-marks.mjs --write regenerates the files. The wordmark is outlines, so drawing it needs no font; scripts/generate-wordmark.py --check re-derives it from the vendored Fraunces file. Raster exports derive from the SVGs; never use generated concept images as production logo sources, and never trace one.

Accessible name. The wordmark and the lockup are exposed to assistive tech as an image named "Kontour" by default, so a header that shows only the lockup still announces the company. Pass title to rename one, or decorative to hide it where visible text beside it already says the name. The symbol and the product marks have no default name: they are hidden unless given a title.

Gates#

Enforced for every mark by npm run check:marks and the browser suite. A mark that fails one is fixed or not shipped.

  • a valid, self-contained SVG with a viewBox: only shape elements, and only plain numeric geometry inside the frame; no scripts, raster embeds, external references, styles, or live text
  • currentColor as the only paint; no filters, gradients, masks, or opacity
  • at 16, 24, and 32px, rendered alone: enough ink that still holds 3:1 after antialiasing on the page and panel surfaces in light and dark; not more ink than a drawing has; ink and empty space both present, with ink in the middle of the frame
  • no two marks render alike
  • shape-count and file-size ceilings
  • for the square marks: renders standalone, square, at 16 and 32px, as a favicon does

What the gates do not establish. They measure ink: how much, how dark against the surface, and how it is distributed. They do not measure whether anyone can tell what a mark is. Compared with the requirements this section carried before:

  • "~24px recognition" is not automated. The ink measurements do not stand in for it; they catch blank, hairline, washed-out, frame-only, and duplicated marks. Recognition stays a human review requirement: before a mark is approved, a person looks at it at 16, 24, and 32px on the contact sheet (node scripts/render-marks-sheet.mjs <out.png>) and confirms it reads as what it is and is not mistaken for a neighbouring mark.
  • The app-icon test is not covered. No gate renders a mark as an app icon (masked, padded, on a filled tile); that needs its own review when an app icon is made.
  • The favicon gate proves the file renders alone at favicon sizes. It does not prove it reads in a real tab strip.

Usage#

Adopted with the decided corporate marks, as proposed in #114. These rules are in force, but they were not separately put to the owner, so changing one does not reverse an owner decision.

  • Color. One color, from the surrounding text color. Use a text or brand role that holds 3:1 on the surface behind it. Never color parts of a mark differently.
  • Clear space. Keep a margin of one quarter of the mark's rendered height on every side, free of text, other marks, and container edges.
  • Minimum size. 16px high for every mark. Prefer 24px or more for the symbol, the wordmark, and the lockup.
  • Which mark. The lockup where the company is introduced; the symbol where space is tight or the name is already present; the wordmark where a symbol would repeat. Product marks identify products and never stand in for the corporate mark.
  • Favicons. The square marks render as SVG favicons. A standalone SVG has no text color to inherit, so currentColor draws black: for a dark tab strip, ship a copy with the color set, generated from the canonical file.

Don't:

  • stretch, rotate, skew, outline, or crop a mark
  • add a gradient, shadow, glow, texture, or container shape to a flat mark
  • retype the wordmark as live text, in Fraunces or any other face
  • pair the symbol and wordmark in an arrangement other than the shipped lockup
  • place a mark on a photo or a surface where it falls under 3:1
  • redraw a mark outside an explicit identity task

Motion#

Shipped tokens: --k-ease and --k-dur (values in the front matter comment). Exact durations and easing beyond these are OPEN.

Preferred metaphors:

  • Resolve: broken contours become continuous.
  • Trace: a path progressively reveals.
  • Focus: secondary layers recede.
  • Traverse: work moves through a defined path.
  • Layer: deeper evidence becomes visible.

Avoid random particles, AI glows, constant ambient animation, gratuitous morphing, and motion that implies completion while work is pending. Respect prefers-reduced-motion.

Interaction Model#

Complex underneath. Clear on the surface.

Progressive inspection: Outcome → Status → Explanation → Evidence → Provenance → Raw execution. Outcome answers what happened. Status states what Kontour can establish. Explanation says why. Evidence supports it. Provenance shows origins and transformations. Raw execution exposes model, tool, and event detail.

These are contours of detail. Users move between them without losing context.

Trust UX#

Prefer evidence over assertion. Do not merely say Verified when the product can explain what was checked, against what, by what mechanism, and with what limitations. Prefer "12 cited records matched the extracted values" over "Fully trustworthy."

Where relevant, expose source, actor, model, tool, timestamp, transformation, validation step, human approval, artifact or result, version, and lineage.

Distinguish the trust states: unknown, proposed, assumed, verified, stale, disputed, superseded, rejected, and revoked. Never collapse these into one generic confidence state.

A claim is shown on two axes, and neither stands in for the other:

  • Status: what state the claim is in. The trust-state chip, one of Surface's claim statuses (see Trust states).
  • Basis: how that status was established. The trust-basis line after the chip: the mechanism, how much evidence backs it, and its caveats (see Trust basis).

Only evidence linked to the claim bears on it: evidence that supports or cites the claim, or counts against it. Most tool calls in an agent session are exploration, retries, or corrected mistakes; the execution trail is not evidence. A tool call or its result that is not linked to the claim never changes the status or the basis line, whether it succeeded, failed, or was retried. It stays inspectable at the raw-execution layer. A declared check that is linked to the claim is evidence, so its failure does count.

Trust states#

OPEN-12 — trust-state vocabulary. Decided (owner, 2026-09-27): Surface's vocabulary. The trust states are Surface's claim statuses (TRUST_STATUSES, the same set the open trust format derives), in Surface's order, and their default labels are Surface's display names. Kontour UI does not redefine them. An earlier draft list (known, inferred, estimated, uncertain, conflicting, failed, unavailable, not checked) described how a value was produced; it is not a set of component states. @kontourai/surface is an exact-pinned development dependency, used only by npm run check:surface-parity, which fails when the states, their order, or their default labels differ from Surface's; bumping it is how a Surface vocabulary change reaches this package. Nothing shipped imports Surface (npm run check:pack enforces it).

Shipped: tokens in tokens/tokens.css, the TrustState React primitive, the k-trust-state element, and, for renderers that build HTML strings, renderTrustStateHtml from @kontourai/ui/trust-state. All three render from one framework-free module, and the string renderer's output is tested against the element's markup. A string consumer includes the tokens and @kontourai/ui/trust-state.css, the chip rules alone, generated from react/styles.css. Each state has its own ink and fill in both modes, resolved the same in every product theme, and never reads a product accent, the action role, or the focus role (npm run check:tokens). npm run check:contrast rates every label on its fill and every border against the panel, in every theme and mode, and fails when two states' inks are too close to tell apart.

Meaning never rests on color. Every chip always shows a text label, an SVG glyph whose shape is unique to the state, and a line style that groups states:

State Default label Glyph Line Surface meaning
unknown No evidence empty circle dotted No evidence has been recorded.
proposed Pending review clock dashed Asserted, not yet reviewed or verified.
assumed Assumed wave dashed Treated as true without evidence.
verified Verified check mark solid Current evidence supports the claim under its policy.
stale Needs refresh refresh arrow dotted Was verified; the verification aged out or the subject changed.
disputed Disputed not equal double Producers or reviewers disagree.
superseded Superseded arrow to a bar dotted A newer claim replaces this one.
rejected Rejected cross solid Checked and found not to hold.
revoked Revoked circle with slash dotted The producer withdrew the claim.

Solid means a current, definite outcome; dashed means asserted but not established; dotted means no current support (nothing recorded, expired, replaced, or withdrawn); double means contested. Assumed is never styled like verified: it has no evidence behind it.

A product may pass its own label; an empty one falls back to the default, and an override keeps the default label as visually hidden text so assistive technology still hears the status. The detail slot is where evidence goes: prefer a chip with a detail such as "12 source records matched" to a bare chip. An input the component does not recognize renders as its own text with no state styling or glyph; it is never coerced into a state.

Trust basis#

Decided (owner, 2026-09-27; ui#87). The basis is a plain text line after the status chip, never a second chip:

[VERIFIED]  1 cited only · Extracted from a source · 2 entail the claim
[PENDING REVIEW]  Model-derived · 1 not evaluated · Extracted from a source
[NO EVIDENCE]  Basis not recorded

Surface owns the summary. claimBasisView(claim, evidence) from @kontourai/surface/display chooses the facets, their order, their words, which ones are caveats, and the wording of every missing state. TrustBasis and k-trust-basis render that view as given: they never add, drop, reorder, relabel, or truncate a facet, and they do not import Surface. Kontour UI keeps a structural copy of the TrustBasisView type; npm run check:surface-parity compares it with Surface's in both directions and checks that the views the tests and gallery render are what Surface produces for their inputs.

The rules, applied by Surface and rendered here:

  • At most three facets on the line, caveats first, and a caveat is never dropped. The caveats are Model-derived, not evaluated, cited only, contradicts the claim, and failed checks. When there are more than three caveats, the line shows every caveat and nothing else. After the caveats come the method, then support counts, then review.
  • Caveats get a dashed underline, never a color. --k-basis-caveat-decoration is a line style in the text's own color. The whole line is muted text (--k-text-muted), so it never outranks the status. There is no tone prop, and basis rules read no brand, action, focus, status-tone, or trust-state token (npm run check:tokens).
  • Never blank. A claim with nothing to summarize shows a labelled missing state: Basis not recorded, Basis restricted (not visible to this viewer), Basis unavailable (the read failed), or Basis not available (the host cannot or should not say which). Input the component cannot use renders Basis not available and warns in the console. Validation is all or nothing: one malformed facet or detail row makes the whole view unusable, because rendering the rest could silently drop a caveat.
  • Producer ratings stay in the inspector. A producer's own evidence-strength rating and a calibrated confidence appear only as inspector rows labelled as producer-supplied, never on the line.
  • Voice. Write "Model-derived", never "AI thinks" or "AI inferred". "Verified" belongs to the status only; basis copy avoids "Confirmed", "Trusted", and "Certain".

Two densities. Inline is the line alone. Inspector shows the same line and adds a definition list below it with one labelled row per detail Surface reports (How, Support, Results, Derived, Review, Producer rating, Calibrated confidence, Sources). The inspector keeps the line (decided, 2026-09-27) because the rows carry no caveat marking: without the line, the caveats would lose their underline and their caveats-first position. Both densities render a visually hidden "Basis:" prefix, and the visible · separators are hidden from assistive technology, so a screen reader hears "Basis: 1 cited only, Extracted from a source, 2 entail the claim". Each facet carries data-field, data-code, and data-caveat for hosts and tests.

Caveats are announced by their wording and their position, not by the underline. The underline is a visual cue only; the words themselves ("Model-derived", "1 not evaluated", "1 cited only", "1 contradicts the claim") already say that they limit the status, and caveats always come first. Kontour UI does not add its own spoken copy such as "caveat:", since that would be basis wording outside Surface. If an explicit announcement is wanted, Surface owns it, in the view's labels.

Human review is first-class provenance. Failures state what failed, the impact, recoverability, retained partial work, and the next action.

AI & Agent Activity#

Do not anthropomorphize routine system behavior. Prefer "Model returned…", "Tool call failed…", "Kontour verified…", and "3 sources support this value…" over "The AI thought…".

Distinguish planned, running, completed, failed, skipped, retried, human-approved, and superseded events. Raw traces are not the default explanation: summarize first, inspect downward.

Data Visualization#

Charts prioritize comparison and interpretation over decoration, and every chart answers a user question. Never use product identity colors as an implicit status scale. Represent uncertainty and completeness with multiple cues (labels, line style, shape, opacity, pattern, and color). A series that carries a trust state reuses that state's ink (--k-trust-<state>) and the line style its --k-trust-<state>-line token names. That token is a CSS border-style keyword, not a stroke value, so a chart translates it:

Line keyword SVG stroke
solid no dash array
dashed a dash pattern, such as stroke-dasharray: 4 3
dotted round caps on zero-length dashes, relative to the stroke width: a dash of 0 and a gap of about twice the width (stroke-dasharray: 0 3 for a 1.5px stroke), with stroke-linecap: round, so each dot is as wide as the stroke
double no stroke equivalent: draw two parallel strokes, or use the state's glyph as the marker

Broken/continuous contour logic is allowed only when semantically appropriate. Provide textual equivalents where needed.

Voice & Writing#

Kontour sounds grounded, clear, precise, human, and optimistic. Avoid hype, fear, grand claims, excessive jargon, vague intelligence claims, and unsupported certainty. Specificity builds credibility.

  • Prefer "See how this result was produced" over "Unlock unprecedented AI transparency."
  • Prefer "Verified against 12 source records" over "AI you can finally trust."

Accessibility#

Accessibility is part of trust. Require keyboard access, visible focus (--k-focus-ring; buttons draw it as an outline offset onto the surrounding surface, so it never depends on the button's fill), semantic markup, accessible names, sufficient contrast (npm run check:contrast enforces WCAG AA on the shipped pairs), reduced-motion support, no critical color-only encoding, usable zoom and reflow, appropriate target sizes, screen-reader-readable status changes where needed, meaningful table headers, and textual equivalents for charts.

Design QA Checklist#

Before calling a UI or brand task complete, verify:

  • Does it make the user's primary outcome clear?
  • Can a user tell what Kontour knows from what it infers?
  • Is evidence accessible at the appropriate depth?
  • Is provenance preserved?
  • Are errors and uncertainty explicit?
  • Does product identity stay distinct from status semantics?
  • Does Station keep its S-river ownership?
  • Are contours meaningful rather than decorative noise?
  • Are canonical assets reused rather than approximated?
  • Does it work in light and dark where required?
  • Is important meaning available without color?
  • Is keyboard and focus behavior correct?
  • Is the hierarchy usable on narrow screens?
  • Are technical values copyable where useful?
  • Is the copy specific rather than hype-driven?
  • Does it use --k-* tokens only, and did it resolve no OPEN item by accident?
  • Did the implementation introduce a new design-system rule accidentally?
  • Does the result feel calm, precise, and inspectable?

Canonical Direction#

Intentional, not up for casual change: the Kontour/contour naming relationship; environmental product architecture; the uncertainty → clarity metaphor; the corporate contour-field concept; Station's S-river; product-specific environmental phenomena; a restrained corporate palette; distinct product accents; "Trust the work AI does."; separation of corporate and product iconography; the progressive "contours of detail" interaction model; evidence and provenance as first-class trust UX.

Open Decisions#

Do not silently resolve these. When one is decided, update this file and the canonical assets and tokens together, in the same change. A decided row stays in the register as the record of the decision.

Draft vs shipped tokens#

ID Item Status Shipped Draft direction Tracking
OPEN-1 Spacing scale Decided (owner, 2026-10-01): shipped scale is canonical --k-space-5 = 24px, --k-space-6 = 32px not adopted: 20px / 24px at steps 5 / 6; steps up to 96px #74
OPEN-2 Radius scale Decided (owner, 2026-10-01): shipped radii are canonical --k-radius-sm = 9px, --k-radius-md = 14px, --k-radius-control = 4px, --k-radius-overlay = 10px not adopted: 6 / 10 / 16 / 24px #74
OPEN-3 Type scale Decided (owner, 2026-10-01): shipped scale is canonical --k-text-xs = 11px to --k-text-2xl = clamp(26px, 3.4vw, 38px) not adopted: ten named levels, 12px to 64px #74
OPEN-4 Typefaces Decided (owner, 2026-10-01): keep the shipped families Fraunces / Hanken Grotesk / IBM Plex Mono humanist or restrained neo-grotesk, family undecided; closed by this decision —
OPEN-5 Default mode Decided (owner, 2026-10-01): dark is the default dark default, [data-theme="light"] opt-in; following the OS preference is the consumer's script (see Modes) not adopted: light Stone/Ink default —
OPEN-6 Palette and product accents Decided (owner, 2026-10-01): shipped values are the palette per-theme --k-brand; .theme-station ships with a contrast-adjusted dark brand not adopted: Ink, Stone, the draft's Flow and Surface accents #74
OPEN-7 Status model Decided (owner, 2026-10-01): five tones plus the nine trust states; a new tone needs a named consumer five tones; the nine trust states (Surface's statuses) as separate tokens and a primitive not adopted: semantic status tokens beyond the five tones #88
OPEN-8 Primary action and focus Resolved by #79 --k-action and --k-focus roles, independent of the brand slot accent is not automatically the action color #72
OPEN-9 Flow endorsement form and product tiering OPEN Station and Surface: "<Product> by Kontour AI" (decided) Flow: "Flow" or "Flow Agents"; tiering of other products —
OPEN-10 Console and Survey themes Decided with OPEN-6 (2026-10-01): shipped values are canonical shipped not covered by the draft —
OPEN-11 Marketing-site palette relationship Decided (owner, 2026-10-01): the marketing site keeps its own palette, as a documented exception for now — — —
OPEN-12 Trust-state vocabulary Resolved (owner, 2026-09-27) Surface's claim statuses and display names, checked against @kontourai/surface — #73

Brand decisions still open#

Contour spacing and line weights; contour-derived Station, Flow, and Surface vectors (the shipped product marks are flat UI icons, #75; none is published before the owner sees it at real sizes); stacked and per-product lockups; app-icon treatments; complete typography tuning; environment and release-channel colors; exact motion durations and easing; final breakpoints; final component-library mappings.

Final Principle#

The visual identity and the product experience express the same idea:

Make invisible structure visible.

Kontour is not trustworthy because it looks secure. It earns trust by making AI work understandable, attributable, inspectable, and appropriately uncertain.

The contour is not decoration. It is the model for the product.

Source: DESIGN.md