Foundations
Visual foundations
Shape, colour, icons and brand assets define the visual language
of the Malkai mid-office. Canonical values live in tokens/tokens.css;
this page shows how those values appear in the interface.
Shape, borders & elevation
Editorial print, not glossy SaaS: radii small, borders hairline, one low shadow. A line is a thickness and a rhythm before it is a colour — the whole layer is drawn with three steps of one ink and two styles.
Corner radius
Borders — three steps, two styles
Every border in the workspace is 1px and comes from the hairline family, three steps deep:
--mk-hairline (.14) the quiet line, --mk-hairline-mid the frame,
--mk-hairline-strong (.32) the drawn edge of an instrument. Solid is the default; dashed is a state.
--mk-border / --mk-border-strong are deprecated aliases — write the line out.
The dash is ONE rhythm (07-16): a 1px CSS dash
draws 3px on / 2px off, and a gradient-drawn rail (.mk-rail) matches it exactly —
never 2px, never another period. Its ink is --mk-hairline-dash (.5): a 3/2 dash lays
ink on 60% of its length, so at the solid strong's .32 it read as a solid ~.19 — the ink
overshoots to land at the strong's weight (08-02). A dashed border is a state mark,
never an affordance (C15): what it bounds says what is missing; the action lives elsewhere.
Neither neighbour holds a frame: the quiet hairline dissolves as an outline (08-02), and strong
solid is an instrument's edge — a card, widget or table framed in either stood out of the family.
Elevation
Two steps and deliberately no third. Both are the brand ink at a low opacity — nothing casts a grey shadow.
Colors
The pigments, grouped by family — every colour value the workspace holds, and the tokens that point at each one. A pigment is often shared: the same gold is the accent, the build dot and the brand’s dark camel. Where a colour is used belongs to the section about that thing — buttons with buttons, rules with borders. Values are read from tokens.css, so this cannot drift from the code. Both palettes live here, each shown on its own ground — a pigment is only true against the surface it was chosen for — and the theme you are reading in comes first.
The brand gold
The camel family and the pale gold that carries a badge. One pigment often wears several names — --mk-camel-dark is also the accent and the build dot.
--mk-camel-light--mk-camel-mid--mk-camel-dark--mk-dot-build--mk-accent--mk-camel-deep--mk-accent-deep--mk-badge-bg--mk-badge-shellBlack and white
Three values, and the middle one is the interesting one. The brand book defines its «black» as #1D1F1D — a warm Pantone near-black, the right answer for print, where a true zero is neither reachable nor wanted. A screen is not paper, and the book is a reference rather than the source of our decisions, so pure black exists here too (29 Jul). --mk-ink stays the voice of the brand and the default for text; --mk-black is the true zero for the places that need one.
--mk-black--mk-ink--mk-text--mk-surfaceThe greys — ink at a distance, not pigments of their own
A design system usually keeps a grey ramp. This one does not, and the reason is worth stating: almost every grey here is the brand ink held at an opacity, not a colour that was chosen. That is deliberate — a flat grey is only correct on the ground it was picked against. #656765 reads right on white and wrong on the cream page, wrong again on a sunken band and on a state tint. The ink at 68% is right on all four, and it follows the dark theme without a second value. Two true grey pigments do exist, and they are here because they are colours, not distances: the closed-phase dot and the brand’s warm neutral. Below is the ladder those opacities actually produce on white; its 100% ink anchor is already shown once in Black and white above.
--mk-text-secondary--mk-icon-ink--mk-text-label--mk-text-muted--mk-dot-exit--mk-hairline-strong--mk-warm-grey--mk-hairlineBrand neutrals and the surface ramp
The brand’s beige, and the interface neutrals between it and white: the ground a page runs on, and the well inside a card.
--mk-beige--mk-accent-contrast--mk-beige-dark--mk-surface-sunken--mk-page--mk-surface-raisedSystem colours
What the interface says went right, went wrong, or is still running. Each has a fill and, where the fill could not carry words at 4.5:1, a darker ink beside it.
--mk-success--mk-success-text--mk-dot-win--mk-warning--mk-dot-wait--mk-warning-text--mk-error--mk-error-textSystem tints
The plate each state sits on.
--mk-success-tint--mk-warning-tint--mk-error-tintPhase and lifecycle
Where an enquiry stands. Two pigments of their own; the other three reuse the system and brand colours above, which is why a build dot and the accent are the same gold.
--mk-dot-lead--mk-dot-exitTelling stays apart
The builder’s date picker marks every other stay of the journey in the months it shows, so a property that appears twice in a route reads as two stays. Qualitative, not sequential. Flagged: this is the one set here that does not sound like Malkai — saturated blue, magenta and indigo against a workspace of camel and sage. Naming them fixed the plumbing; redrawing them is its own look.
--mk-stay-1--mk-stay-2--mk-stay-3--mk-stay-4Elsewhere
The brand’s muted neutral, and the ink that darkens a photograph — cooler and deeper than --mk-ink, so an image darkens without going warm. The neutral was --mk-blue-grey #BFC7C4 until 29 Jul, when its green channel was found sitting above the other two — an olive cast that read as a different family from everything around it. It now carries --mk-beige’s own warm ratio at the same lightness, and the name went with the colour. Still unused: the brand book carries it and the README calls it a rare background accent, but no screen states it — not a prototype, not the component layer.
--mk-warm-grey--mk-scrim-inkDerived — not pigments
These hold no colour of their own: each is one of the pigments above at an opacity, or two of them mixed. Why not simply pick a colour? Because a flat value is only correct on the ground it was chosen against. This workspace stacks four grounds — the cream page, the white card, the sunken band and a state tint — and a divider, a hover or a muted word has to look right on all of them. A hex would need one token per ground; the recipe needs one, and it lands correctly on every surface, including the ones designed later. It also carries the dark theme for free: mix from semantic tokens and the value remaps itself, so nothing here needs a second definition. Before 29 Jul all of them were mixed at the place they were used — 131 times, in 67 different recipes, so a hover was 4% in one component and 8% in its neighbour.
--mk-text-mutedrgba(29, 31, 29, 0.68--mk-text-labelrgba(29, 31, 29, 0.78--mk-text-secondarytext 68%, var(--mk-text-muted--mk-icon-inkrgba(29, 31, 29, 0.8--mk-hairlinergba(29, 31, 29, 0.14--mk-hairline-midhairline-strong 55%, var(--mk-hairline--mk-hairline-strongrgba(29, 31, 29, 0.32--mk-hairline-accentaccent 50%, var(--mk-hairline-strong--mk-wash-fainttext 4%, transparent--mk-washtext 6%, transparent--mk-wash-strongtext 8%, transparent--mk-wash-presstext 12%, transparent--mk-accent-washaccent 8%, transparent--mk-accent-wash-strongaccent 14%, transparent--mk-surface-bandsurface-sunken 35%, var(--mk-surface--mk-surface-band-strongsurface-sunken 55%, var(--mk-surface--mk-success-washsuccess-text 30%, transparent--mk-warning-washwarning-text 30%, transparent--mk-error-washerror 8%, transparent--mk-error-edgeerror 40%, transparent--mk-scrimrgba(29, 31, 29, 0.32Elevation
Not colours, but they are made of one: both shadows are the brand ink at a low opacity, so nothing in the interface ever casts a grey shadow.
--mk-shadow-card0 1px 3px rgba(29, 31, 29, 0.08)--mk-shadow-pop0 8px 32px rgba(29, 31, 29, 0.24)Rules, borders and dashed edges live in Shape, borders & elevation — they are a thickness and a rhythm before they are a colour.
The dark theme
Every value the [data-theme="dark"] block overrides, shown on its own ground. The chips read the tokens live, so the panel cannot drift from tokens/tokens.css; the hex captions and the contrast figures are generated from the same block. Measured against the dark card, #151515, except where a row names another ground.
The ladder of grounds
Neutral greys, R=G=B, stepping down to a true-black page. The ink had run out of headroom, so contrast is bought by dropping the grounds under it. Neighbouring steps stay 1.06–1.16:1 apart, which is what keeps a band tellable from the one beneath it.
--mk-page--mk-surface-sunken--mk-surface--mk-surface-raised--mk-badge-shellThe ink ladder
One voice at full ink and four registers under it. Body sits at the ceiling because every step below it proved invisible on a real page; under body come meta (a timestamp, a count), the caps label, and absence.
--mk-text--mk-text-muted--mk-text-meta--mk-text-label--mk-text-quiet--mk-icon-inkLines and veils
Neutral white at three opacities, matched to the neutral grounds, plus the veil behind a sheet.
--mk-hairline--mk-hairline-strong--mk-hairline-dash--mk-scrimThe accent, and what rides on it
One hue at 47.9°, walked in lightness: the ink that carries text, the saturated ink a quiet act's label takes, the focus ring, then the fill and the two faces under it. Nothing here is a different gold, which is the whole point — before 08-19 these values ranged over four hues and the focus ring was still wearing the light theme's brand camel. Saturation was walked three times that day (42% → 58% → 63%) with no visible change, so the applied value is two notches on from where it started; concept-rounds/dark-gold-ladder.html lays out G0 to G4 with the whole family in use, and the next move should be a pick from those columns.
--mk-accent--mk-accent-ink-strong--mk-camel-mid--mk-accent-fill--mk-accent-fill-hover--mk-accent-busy--mk-accent-deep--mk-accent-contrastThe same accent, doing its job
A swatch and a label are not the same colour to the eye, and the label is the one that ships — so the family is also shown here as the real components reading the real tokens. This strip is the reference's own check: if a gold looks right in the chips above and wrong here, here is right.
A confirmed stay at four nights, and the agent still has to open the guest's profile before the deposit can be taken.
The functional palette
Lifted off the light values: on a dark ground those fills measured 2.7:1 and the text variants failed outright. Each text value clears 4.5:1 on the card, on the raised surface and on its own 14% tint.
--mk-success--mk-warning--mk-error--mk-success-text--mk-warning-text--mk-error-textPhase dots
Three of the five needed lifting to clear the 3:1 non-text floor on a dark badge. The base names point at the lifted variants, so no page has to opt in.
--mk-dot-lead-dark--mk-dot-build-dark--mk-dot-win-darkIcons
One library, one hand. Every glyph is drawn on a 16 grid and painted in
currentColor — so an icon takes the ink of whatever it
sits beside. Line icons use --mk-icon-stroke with butt
terminals and mitre joins; semantic markers and disclosure triangles are deliberately
filled. The stroke does not scale with the box: a 12px mark and a 24px mark both put down
the same hairline, which is how the brand draws its own UI.
This grid is generated from the registry at load, so it cannot drift from the product
the way the hand-drawn Feather set that stood here until 29 July had.
Ask for one by name — <svg data-mk-icon="route"></svg>
in markup, mkIcon('route', 'ico') in rendered strings. Never inline the path:
a copy cannot travel, and the chevron proved it — it was the most-set mark in the
workspace and lived as fourteen inline drawings until it entered the library.
In use across the workspace
Measured on the builder, the enquiry record, the queue and the itineraries list on 29 July — every icon those four pages actually render.
Drawn, not yet placed
In the library and ready to ask for. They wait on the surfaces that have not been built.
The filled glyphs. star marks the person
a trip answers to — a marker is read at a glance, and an outline star reads as a
rating control you could click. chevron and its legacy caret
alias are the same solid triangle on every disclosure and attached menu. The triangle
owns its fill in the registry, so a page cannot accidentally turn it back into a hairline.
Brand mark
The Malkai logo in its three shipped forms, extracted as vector from the brand
guidelines (THE MALKAI brand guidelines pp. 3, 7, 12). Brand rule: never stretch,
recolour, rotate or alter the proportions, and always keep clear space around the mark.
Files live in assets/.
malkai-logo-full.svg · covers, documents, formal headers.malkai-logo.svg · UI mastheads, running headers.malkai-icon.svg · app & favicon mark, standalone accent.Reproduce in solid brand black on light, or reversed on dark imagery — never in the camel accents or brand red. The three SVGs are cropped to the ink bounding box; pad with layout space, not baked-in margins.