This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Interface

The foundations applied to screens — components, code surfaces, dark mode, icons, and forms.

Interface guidance applies the foundations to working software. Where a component appears here, its measurements are normative — a 40px input is 40px, not “about 40px”.

1 - Components

The component set as live specimens — buttons, inputs, cards, tables, navigation, feedback, overlays, and data display.

Every specimen on this page is live markup styled by the brand tokens, ported from brand/html/projectious-kitchen-sink.html. They follow your colour mode — switch the theme and they change with the documentation.

Measurements listed here are normative: a 40px input is 40px.

Buttons

Five variants, three sizes. Plus Jakarta Sans 600, 6px radius, 200ms transitions.

Variants — medium (40px)
Sizes — sm 32px · md 40px · lg 48px
On a midnight surface
VariantFillBorderTextContrastUse
Primarymidnight-9nonewhite12.75:1Default action
Accentaccent-solid #cc4528nonewhite4.72:1The single most important action
Outlinetransparent1.5px orange-9orange-115.13:1Secondary action
Ghosttransparent1px borderslate-9Tertiary, toolbars
Danger#a8261cnonewhite7.10:1Destructive action
Do

Use exactly one accent button per view. Give every button a verb — “Deploy”, “Save changes”.

Don't

Place two accent buttons side by side, or use a danger button for a reversible action.

Inputs

Default height 40px (sm 32, lg 48). Source Sans 3 at 13px, slate-7 border, and a 2px focus ring at midnight 15% alpha.

Text, textarea, select
States — default, focus, error, disabled
Default slate-7 border
Focus midnight-9 + 2px ring
Error Agent identifier is incomplete.
Disabled slate-4 bg, cursor not-allowed
Checkbox, radio, toggle, slider
On Off

Full rules — labels, validation, focus — are on Forms.

Cards

Radius 9px, padding 24px, 1px slate-4 border, shadow-1 at rest. Nested controls step down one radius.

Basic, with image, with actions
Validate & deploy
Runs policy checks before promoting to staging.
Image / diagram
Architecture
Nodes, flows, and policy gates.
Agent: auditor
Idle for 2 hours.

Tables

Header cells use the overline style. Striping uses the subtle background step, never a border-only rule. Wide tables scroll inside their own container.

Data table with sort indicator
PipelinePolicyAgentsStatus
validate-deploystrict2Healthy
nightly-auditadvisory1Degraded
release-trainstrict4Blocked

Table with search and filters

A toolbar above the table carries the search field and filter chips. Active filters are shown as removable chips so the current view is always legible — never leave a filter applied with no visible indication.

Search, filter chips, pagination footer
PipelinePolicyOwnerStatus
release-trainstrictplatformBlocked
nightly-auditstrictsecurityFailed

Multi-level headers

Grouped columns use a two-row header. The group row is centred over its span and separated by a vertical rule — the only place the system uses one.

Two-row header with column groups
PipelineThis weekLast week
RunsPassp95RunsPassp95
validate-deploy41298.1%4m12s38897.4%4m40s
nightly-audit771.4%18m03s785.7%17m22s
release-train1492.9%9m51s1291.7%10m08s

Grouped rows and totals

Row groups act as sub-headings inside the table. A totals row is separated by a 2px rule — heavier than the body rules, so it reads as a summary rather than another record.

Row groups, indented children, totals
WorkloadRunsCost
Platform
validate-deploy412€48.10
release-train14€12.60
Security
nightly-audit7€21.40
secret-scan96€9.05
Total529€91.15

Wide tables

A wide table scrolls inside its own container, never the page, and pins its first column so the row identity stays visible while scrolling.

Horizontal scroll with a sticky first column
PipelinePolicyOwnerRunsPassp50p95RegionStatus
validate-deploystrictplatform41298.1%2m01s4m12seu-centralHealthy
nightly-auditstrictsecurity771.4%11m40s18m03seu-westDegraded
Do

Right-align numeric columns and set them in IBM Plex Mono so digits line up. Show the active filter state and the result count together.

Don't

Add vertical rules between ordinary columns, let a wide table scroll the whole page, or apply a filter without a visible chip saying so.

Navbar — midnight in both modes, 3px accent rule
projectious.work Documentation Pipelines Agents
Breadcrumbs, tabs, pagination, sidebar
DocsInterfaceComponents
13px, secondary
OverviewRunsSettings
accent underline on active
current in midnight-9
Foundations
Logo
Interface
active: 600 weight

Alerts and feedback

Four semantic colours as a 4px left border on a tinted background. The hues are mode-specific — see Colour.

Alerts
Info
Pipeline requires at least one validation agent.
Success
All checks passed. Deployment ready.
Warning
Agent "monitor" has been idle for 2 hours.
Danger
Policy violation — deployment blocked.
Badges, progress, spinner
Default Urgent v0.1.0 Breaking

Modals and overlays

Modal radius 13px with shadow-3; scrim is midnight at 40% alpha. Focus is trapped while open and restored to the trigger on close; Esc always closes.

Dialog, dropdown, tooltip
Delete pipeline?
This removes the pipeline and its run history. This cannot be undone.
Re-run
Duplicate
Export logs
Last run 4 minutes ago12px, 3px radius

Data display

Avatar, stat card, tags
JD AS
1,284
Runs this week
▲ 12% vs last week
Composable Agent-first Humanistic
List and timeline
validate-deploy4m ago
nightly-audit2h ago
release-train1d ago
Policy check passed
09:14
Deploying to staging
09:16
Production
pending
Status indicators — always a dot plus a label
Healthy Degraded Failed Idle
Do

Pair every status dot with a text label, so meaning does not depend on colour perception.

Don't

Use a bare coloured dot, or rely on red/green alone to distinguish states.

Code and terminal

The code surface is always dark — see Code.

Terminal
$ tofu apply -auto-approve
Plan: 3 to add, 0 to change, 0 to destroy.
Policy check passed
Deploying to staging…

2 - Dark mode

Both modes are equally supported. The implementation rules that keep them in step.

Dark mode is not a variant of the brand — it is half of it. Both modes are designed, tested, and shipped together.

Implementation rules

  • Default follows prefers-color-scheme, with a manual override toggle persisted to localStorage.
  • CSS custom properties swap per theme. Components read tokens; they do not branch on the mode themselves.
  • Step 9 (solid accent) stays constant across modes. #E05232 is #E05232 everywhere.
  • No pure #000 or #fff as text. Use step 12 — #c5daf0 on dark, #142438 on light.
  • Images get a subtle overlay (black at low alpha) in dark mode, so a bright photograph does not punch a hole in a dark page.
  • Code blocks are always dark, regardless of mode. See Code.
The same card in both modes
Light mode
Body text is midnight-12; supporting copy is slate-11.
Deploy
Dark mode
Never pure white — midnight-12 from the dark scale.
Deploy

Surfaces

Elevation in dark mode is expressed by lightening the surface, not by deepening the shadow — a shadow against a near-black background is invisible.

LayerLightDark
App background#ffffffmidnight-dark-1 #0e1720
Subtle surfacemidnight-1midnight-dark-2 #131e2b
Raised surfacemidnight-2midnight-dark-3 #1a2b3e

Restating text on dark surfaces

The default heading colour is midnight-9, chosen so headings read correctly on light pages. That same value on a midnight surface is invisible — #1d3352 on #1d3352 is a contrast ratio of 1.00:1.

Any component that paints its own dark background must therefore restate its text colours. This applies to covers, dark boxes, footers, and overlays:

.surface--dark {
  --bs-body-color: #c5daf0;    // midnight-dark-12
  --bs-heading-color: #c5daf0;
  --bs-secondary-color: #97a8b8; // slate-dark-11, still AA
}
Do

Test every surface in both modes. Restate text colours on any component that sets its own dark background.

Don't

Ship a component that only works in one mode, or use pure black or white as a text colour in either.

3 - Code

The always-dark code surface and the syntax theme, with measured contrast for every token.

Code blocks are always dark

Code blocks stay dark regardless of colour mode. A code surface that flips with the theme forces the syntax palette to be designed twice and makes screenshots inconsistent between users. The surface is midnight-2 from the dark scale (#131e2b) in both light and dark mode.

The block you are reading is rendered by that rule:

// Agent pipeline definition
const pipeline = createPipeline({
  name: "validate-deploy",
  policy: "strict",
  agents: ["auditor", "deployer"],
});

Syntax theme

Because the surface is always dark, every syntax value is read from the dark scale. Contrast is measured against #131e2b.

Ten roles, not twenty-two tokens

Editors do not describe code with six token types. The Language Server Protocol defines 22 semantic token types and 10 modifiers, and TextMate grammars — the model behind VS Code, Sublime Text, and most highlighters — define 11 root scopes with a deep sub-scope tree under each.

A theme should not answer that with twenty-two colours. Past roughly nine, hue stops being a signal: everything is coloured, so nothing is marked. The two scope vocabularies are therefore grouped into ten roles, and the modifiers are carried by weight and slant rather than by more hue.

Every role is an existing brand or terminal value. The expansion introduced no new colour — the terminal palette had already added the two hues, cyan and magenta, that a syntax theme needs and the three interface scales do not have.

RoleOn surfaceLSP semantic tokenTextMate scope
Plain and variables
#c5daf0 midnight-dark-12
The default. Anything the reader does not need to pick out.
11.74:1variable · parameter · property · enumMembervariable · variable.parameter · variable.other
.n .nv .nx .py .vc .vg .vi
Keywords and modifiers
#8aacc8 midnight-dark-11
Also structural keys — a YAML key is the keyword of its line.
7.06:1keyword · modifierkeyword.control · storage.modifier · storage.type
.k .kc .kd .kn .kp .kr .nt .na
Types and classes
#74c0c9 terminal cyan (bright)
The brand has no cyan; the terminal palette does, and this is the second place the system needed one.
8.11:1type · class · struct · interface · enum · typeParameter · namespaceentity.name.type · entity.name.class · support.class
.kt .nc .nn .ne .bp
Functions and methods
#e0a92a terminal yellow (bright)
Callables read as callable at a glance — the single most useful distinction beyond the original six.
7.91:1function · methodentity.name.function · support.function
.nf .fm
Decorators and macros
#d491b4 terminal magenta (bright)
Code that runs at a different time from the code around it.
6.82:1macro · decorator · evententity.name.tag · meta.decorator · support.macro
.nd .ni .nl
Strings
#ea7558 orange-dark-10
Interpolation delimiters take the operator colour, so the expression inside stays readable as code.
5.76:1stringstring.quoted · string.interpolated · string.regexp
.s .s1 .s2 .sa .sb .sc .sd .se .sh .si .sr .ss .sx .dl
Numbers and constants
#f09878 orange-dark-11
Literal values, including true/false/nil.
7.58:1number · regexpconstant.numeric · constant.language · constant.character
.m .mb .mf .mh .mi .mo .il .no
Operators and punctuation
#97a8b8 slate-dark-11
Present but recessive — structure you read past, not at.
6.90:1operatorkeyword.operator · punctuation
.o .ow .p
Comments
#72889d code-comment
Italic. The only role with no scale step of its own.
4.59:1commentcomment.line · comment.block · comment.block.documentation
.c .ch .cm .cp .c1 .cs
Invalid and deprecated
#f08b80 danger-dark
Deprecated is struck through as well as coloured — the state does not depend on hue.
6.98:1(modifier) deprecatedinvalid.illegal · invalid.deprecated
.err

Modifiers are not colours

LSP modifiers combine with any token type: ten modifiers against ten roles is a hundred states. Hue cannot carry that, so it does not try.

LSP modifierTreatmentWhy
declaration · definitionWeight 500Where a name is introduced, distinguished from where it is used.
deprecatedLine-throughA state, not a category — it must survive greyscale.
documentationItalic, comment colourDoc comments are comments; they are not a separate hue.
readonly · static · abstract · async · defaultLibraryNo distinct colourTen modifiers times ten roles is a hundred combinations. Colour cannot carry that, and a theme that tries becomes unreadable.

Why comments have a dedicated token

Comments are the one syntax role with no scale step available to it. Steps 8–10 are border and solid-surface roles and are not held to text thresholds; step 11 is already spoken for by operators.

So code-comment (#72889d, 4.59:1) exists as a dedicated syntax token — the dimmest value that clears AA while staying visibly below operators. It is not a scale step and should not be treated as one.

Do

Group scopes into roles, and let a language’s grammar map onto them. Keep the role count under ten, and check every value against the code surface.

Don't

Give each LSP token type its own hue, or use the accent as a syntax colour — it marks the primary action, and a code block is not one.

Inline code

Inline code does not take the dark block treatment — it follows the surrounding surface. On light surfaces it sits on midnight-2 (light scale) with orange-11 text; in dark mode both values shift to their dark-scale counterparts. It uses IBM Plex Mono at 13px with a 3px radius.

Terminal output

Terminal blocks use the same dark surface. Prompts take the comment colour, output takes the operator colour, so a transcript stays readable without becoming a second syntax theme.

$ hugo --gc --minify
Start building sites …
Total in 842 ms

4 - Icons

Lucide as the icon library, and how icons are sized and coloured.

Library

The system uses Lucide — an actively maintained fork of Feather with over 1,400 icons.

PropertyValue
LicenceISC (functionally equivalent to MIT)
Commercial usePermitted
AttributionNot required in product UI; retain the notice in source
ModificationPermitted

ISC is one of the most permissive open-source licences available, which is why it was chosen over icon sets with attribution or share-alike terms. See the legal assessment for the clearance detail.

Sizes on the 4px grid — 16 / 20 / 24 / 32px
16
20 — default
24
32
accent — primary action
Icon with a label, and an icon button
Policy enforced

Usage

  • Stroke width 2, matching Lucide’s default. Do not thin or thicken strokes to “match” a layout.
  • Size on the 4px grid: 16, 20, 24, 32px. 20px is the default for inline UI.
  • Colour follows text. An icon beside a label takes the label’s colour. Standalone icons carrying meaning take --color-secondary; an icon marking the primary action takes the accent.
  • Icons are not decoration. Every icon must carry meaning the label does not already carry, or be marked aria-hidden="true".
Do

Pair icons with text labels wherever the meaning is not universally understood. Give standalone icon buttons an accessible name.

Don't

Mix icon sets, recolour icons to arbitrary hues, or use an icon as the only indicator of state.

5 - Forms

Input sizing, labels, focus, error, and disabled states.

Sizing

SizeHeight
sm32px
md40px — the default
lg48px

All inputs are set in Source Sans 3 at 13px.

Sizes — sm 32 · md 40 · lg 48
Small
Medium
Large

States

StateTreatment
Defaultslate-7 border
Focusmidnight-9 border + 2px focus ring, midnight at 15% alpha, 2px offset
Errororange-9 border + orange-1 background
Disabledslate-4 background, slate-8 text, cursor: not-allowed
Every state
Default
Focus
ErrorDescribe the problem in words.
Disabled

Labels

Labels use the overline style and sit above the field. Floating labels are not used anywhere in the system — they hide the label at exactly the moment the user is filling the field, and they break at long label lengths.

.field__label {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.3;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-secondary);
}

Validation

  • Error text sits below the field, in orange-11 on light surfaces.
  • Never rely on border colour alone to signal an error — pair it with text.
  • Validate on blur, not on every keystroke; re-validate on submit.
Do

Keep labels visible at all times. Give every field an associated <label> and describe errors in words.

Don't

Use placeholder text as a label, signal errors with colour alone, or disable the submit button without explaining what is missing.