Dark first, quiet until it matters.
This is the design system behind my homelab console. Dark is the default because that is what I prefer, but the system ships four accents, two themes and two backgrounds, and every combination is a real supported option. Everything below is live. Use the panel in the bottom right to switch, and watch the components and the swatches follow along.
Six rules, and one thing that is up to you
The rules below hold whichever look you pick. What you pick is your call. Indigo on ink dark is only the default. Teal on flat light is just as valid, and if that suits the thing you are building, use it. Sixteen combinations, all of them checked.
Neutrals come from the ramp
Never #000, never #fff, never untinted grey. On dark the ramp leans blue, on light it stays cool. Both feel like a place rather than an inverted settings panel.
One accent, your pick of four
Indigo, teal, ember or lime. Choose one at the root and let it own active states, primary buttons and focus rings. The rule is one accent per app, not one accent for everyone.
State twice over
Status is a tint you feel and a word you read. Never colour on its own, never a bare label. This is what keeps the palette swappable without losing meaning.
Depth from glass, or not at all
Things lift with blur and translucency instead of shadows, and only the top layer gets a real shadow. Flat mode drops the blooms and the system still works.
Hide the detail
A row is one line until you open it. Density is something you ask for, not something the page does to you.
Texture, barely
Five percent grain, soft colour blooms underneath, both optional. Atmosphere is fine. Noise is not.
Three families, strict jobs
Neutrals carry the interface. One accent carries attention. Semantic colours carry state. The pale versions are for pills and fills. The darker validated versions are for chart marks. They do not swap jobs.
Neutrals
Accent and state, pale steps for pills
Chart marks, checked for contrast
Palette check, computed rather than eyeballed
| Check | Result | Detail |
|---|---|---|
| Lightness band | PASS | all five sit inside OKLCH L 0.48 to 0.67 |
| Chroma floor | PASS | all five at or above 0.10 |
| Colour vision | WARN | rose and amber sit 7.5 apart under deuteranopia. Allowed, because every status also has a word and a dot |
| Normal vision floor | PASS | closest pair 15.2, floor is 15 |
| Contrast on surface | PASS | all five at 3:1 or better |
One family for every heading
Outfit sets all text and every heading. Heading sizes follow one rule: each level down divides by the square root of the golden ratio, so two levels make one factor of φ, and h6 is body size. IBM Plex Mono handles labels, hostnames, timestamps and anything a machine produced. Instrument Serif is kept for quotations and the drop cap that opens a long article. All three are woff2 files in fonts/.
The only thing that should glow is the thing that is wrong.
Two ways to show one
A is the default. B puts a soft gradient behind the number. Use at most one B on a screen. Numbers use tabular figures so columns of them line up.
One line each until you ask
Closed, a row is a name, what it is, a couple of facts and a status pill. Open, it adds the full detail, a note about what is wrong and the buttons that act on it. Click a row and see.
Services
31 / 33 up Memory in use 38.4 GBColour you feel before you read
Each column carries a faint wash of its state colour, so you know which lane you are looking at before reading a single word. The cards stay neutral glass so the wash comes through them.
Thin marks, labelled directly
One line with a dotted forecast on the end, comparison bars with the subject picked out, a segmented meter, and a stacked bar that splits one whole into its parts. Text always uses text colours. Colour belongs to the marks.
Requests per hour
Idle memory
Disk on jupiter
Source lines
The navigation goes off canvas, nothing else disappears
A phone gets the same pages, not a reduced set. Grids collapse on a fixed ladder, the app sidebar and this rail slide in from the left rather than vanishing, and every chart stays readable and inspectable with a finger.
1 · The ladder
| Width | What changes | Why |
|---|---|---|
| 1140 | .settings-cols single column, .subnav hides | the "on this page" list stops earning its width |
| 1100 | .mosaic spans halve | twelve columns stop reading as twelve |
| 980 | .cols-main, .panes and the marketing embed go single column | two columns of data stop fitting side by side |
| 880 | .side and .rail become drawers, .navtoggle and .brand-sm appear | the app shell loses its sidebar column |
| 720 | nav.top .links becomes a dropdown panel | the marketing links stop fitting beside the logo |
| 620 | table rows restack, comparison bars narrow, frame padding tightens | the last width where two columns of anything work |
minmax(0, 1fr), never 1fr. A bare 1fr is minmax(auto, 1fr) and cannot shrink below its content, which is how a chart ends up half off the screen.2 · The drawer, .navtoggle and data-nav-toggle
data-nav-breakpoint when the panel collapses somewhere other than 880, as the marketing pages do at 720.visibility: hidden, not display: none. Hidden keeps its links out of the tab order, which display would also do, but it still allows the panel to slide.3 · Charts under a finger
viewBox so that one user unit is one CSS pixel. Nothing is scaled unevenly, which is what keeps the dotted forecast dotted at 390px wide instead of thinning into blank space. Press and drag the chart above for the crosshair and the readout, and let go to dismiss it. The chart claims sideways drags and leaves vertical ones to the page, so scrolling over it still works.preserveAspectRatio="none" on a chart. It buys a chart that fills its box and costs you every stroke width, dash and radius in it.