Design
One palette, one status vocabulary, four key tiers, and a defined fallback for every terminal. This page is the reference for all four.
Colour
The palette is warm amber on cool slate. It nods to GitLab's tanuki without using GitLab's brand orange.
Components name a role, never a hex value. Colour literals live in the theme package and nowhere else, so a token change reaches every screen at once.
Surfaces and borders
Text
Meaning
Contrast
Every token clears WCAG AA — 4.5:1 for text, 3:1 for interface elements — against every
surface, not only against the frame background. Text sits on four backgrounds, and the one
that decides the palette is the selected row: a timestamp there is the most-scanned cell in
the product. The ratio beside each swatch is that worst case, computed on every commit rather
than eyeballed on one monitor. The lowest is text.muted at 4.80:1, and that headroom is what
a small tweak spends.
Two tokens are exempt, and each says why. border.faint and border.default carry no state:
a row separator at 3:1 draws a grid louder than the rows, and an unfocused pane's edge means
nothing on its own. border.focus is what identifies focus, and it clears 3:1 several times
over.
The contrast theme re-derives the whole palette at AAA, 7:1 for body text, on near-black
surfaces — 7:1 for every hue is not reachable above a slate background.
Run labdash theme preview to see these numbers computed in your own terminal.
Light theme
The same token names, different values, verified by the same computation.
Capability tiers
labdash reads the terminal's capability once at startup and stores it on the theme, so no call site branches on colour depth.
At the 16-colour tier the selected row is reverse video rather than a background colour, because reverse video is the one background every terminal theme renders reliably.
NO_COLOR is honoured unconditionally, and no setting overrides it. --no-color does the same for a single run. At either, every state is still identifiable, because colour never carries a state on its own.
The status vocabulary
Every state is a triple: glyph, colour, word. Remove any one and the other two still carry it.
GLYPH ASCII WORD TOKEN ──────────────────────────────────────────────── ✔ + passed status.success ✖ x failed status.error ● > running status.running ◌ . pending status.pending ○ - created status.neutral ◌ . preparing status.pending ◍ ~ waiting status.pending ⏸ ! manual status.warning ◔ @ scheduled status.pending ◐ / canceling status.neutral ⊘ / canceled status.neutral » s skipped status.neutral ⊘ - no pipeline text.muted ✖ w failed (allowed) status.warning
Twelve of those rows are GitLab's pipeline states. Two are labdash's: a merge request with no pipeline at all, and a job that failed with allow_failure: true.
GitLab has thirteen states and this table has twelve rows, because the mapping is many-to-one where the difference is not something you can act on: WAITING_FOR_RESOURCE and WAITING_FOR_CALLBACK both read waiting.
A glyph repeats where the word separates two states. ◌ covers pending and preparing, ⊘ covers canceled and no pipeline. A word never repeats, which is what makes a state identifiable by its glyph and its word together, never by the glyph alone.
An allow-failure job that failed renders as a warning. Red is reserved for the failures that stop a pipeline, because a dashboard that shows red for something the pipeline was told to tolerate teaches you to stop reading red.
Merge blockers use the same triple. Each value of GitLab's detailedMergeStatus maps to a phrase, a token and a glyph: ready, conflicts, ci failed, ci running, needs 2, 3 threads open, needs rebase, changes requested, policy blocked. A value labdash does not recognise is title-cased and logged rather than dropped.
Icons
No patched font is required. The glyphs above come from ordinary Unicode blocks that a terminal font has carried for years, so there is nothing to install and nothing to detect.
Every glyph occupies one cell, and every ASCII fallback is one character, so a screen rendered either way has identical column boundaries. A glyph that is one cell in one mode and two in the other is a layout bug.
theme.icons takes auto, unicode or ascii. In ascii mode no byte above 0x7F reaches
the screen, borders are + - |, and the spinner is |/-\.
auto reads your locale. Three of the pipeline glyphs — ● running, ○ created, ◐
canceling — are classed as East Asian Ambiguous, as are several of the markers, meaning one
cell in most terminals and two in a terminal configured for CJK text. On a CJK locale auto
chooses ascii, because a glyph drawn two cells wide where one was measured shifts every
column after it. Set icons: unicode to keep the glyphs anyway.
labdash theme preview --icons=ascii renders the whole vocabulary the other way. Hold one
screenshot over the other: the columns line up exactly, and that is the property a test
asserts on every commit.
Type and borders
There is one font: yours. What labdash controls is weight, case and glyph choice.
Borders use ╭ ╮ ╰ ╯ ─ │; rounded reads softer without costing a cell. A focused pane changes colour and never geometry, because a border that thickens on focus shifts every column beside it.
Space
Vertical space costs more than horizontal: one blank row between preview sections, never two, and no blank rows inside a table. Chrome takes four rows, the filter bar takes one more while it is open, and the rest is data.
Responsive columns
The table decides its own layout at the width it is given.
title,blockersandpipelineare present at every width.- Every other column carries a priority and a minimum width, and drops in that order as the terminal narrows.
- Text is left-aligned, numbers and durations right-aligned, a single-glyph status centred.
- Truncation is always
…, never a hard clip, and a wide glyph never shifts a column.
The preview pane takes 34 to 40% of the width. auto puts it on the right at 120 columns or wider, and along the bottom below that.
Keys, in four tiers
L0 and L1 are complete alternatives to each other, not a core plus an add-on. Nobody is locked out for not knowing vim, and nobody who does has to reach for an arrow key. Every action is reachable from the keyboard; the mouse only ever adds.
Bindings are fixed. The coherence rules that make an unlearned key guessable are on the keyboard reference.
Motion
Terminals redraw whole cells, so motion is cheap to add and expensive to live with.
theme.reducedMotion: true replaces the spinner with a static marker and drops the gradient. State is still communicated: the task line still names what is running.
Copy
Terse and factual. The user is mid-task and something failed. Say what happened and what to do.
No exclamation marks. No emoji in prose, because glyphs are part of the status vocabulary and that is a different thing. Never blame the user, and never say "invalid" when "not recognised" is true.
Every message follows a formula:
GitLab's words, not ours: "merge request" never "pull request", "pipeline" never "build", "To-Do" capitalised as GitLab does. Someone who searches GitLab's documentation for a word they saw here finds it.
Identity
The wordmark is figlet's ANSI Shadow, generated once and committed as a text file, so there is no font dependency and no startup cost.
██╗ █████╗ ██████╗ ██████╗ █████╗ ███████╗██╗ ██╗ ██║ ██╔══██╗██╔══██╗██╔══██╗██╔══██╗██╔════╝██║ ██║ ██║ ███████║██████╔╝██║ ██║███████║███████╗███████║ ██║ ██╔══██║██╔══██╗██║ ██║██╔══██║╚════██║██╔══██║ ███████╗██║ ██║██████╔╝██████╔╝██║ ██║███████║██║ ██║ ╚══════╝╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
The two-tone split is the identity: lab recedes into text.muted, and dash takes a horizontal gradient from accent.primary to status.error, amber to coral. It degrades by tier:
_ _ _ _ | | __ _| |__ __| | __ _ ___| |__ | |/ _' | '_ \ / _' |/ _' / __| '_ \ | | (_| | |_) | (_| | (_| \__ \ | | | |_|\__,_|_.__/ \__,_|\__,_|___/_| |_|
The gradient lives on the splash and nowhere else. It never touches body text.
The mascot
GitLab's own logo is a tanuki, a Japanese raccoon dog, so the correct nod is 🦝 rather than a fox. labdash draws its own, and uses neither GitLab's logo, nor its brand orange, nor any wording that implies endorsement.
Every colour on these pages is one of the tokens above, and the terminal screens are rendered with the shipped palette.