Design Language · Version 3.0

Design Language.

Every surface — the app, the website, a Nachtrag PDF, a slide in a Vergabegespräch, a film — is built from the rules on this page.

The mark, 277.28 × 320 — sized by its height
Version
3.0 · 27.09.2026
Typefaces
Plus Jakarta Sans · Geist Mono
Licence
SIL Open Font License 1.1
Accessibility
WCAG 2.2 AA · light + dark

01Foundation

Derived,
not decorated.

German construction runs on documents that have to be exactly right. This system's job is to make BauLynq look like the software that gets them right.

Three principles

Derived, not decorated

Chart colours come from a colour-blindness simulation. Spacing comes from a 4 pt unit. Type sizes come from one ratio. Nothing is here because it looked nice.

Hairlines and air

Structure comes from 1 px rules and space, not heavy boxes or fills. A page should read like a well-set technical drawing.

One red

Oxblood, once per view — the primary action, money, a decision, a blockage. Spread it and it stops meaning anything.

The numbers are the product

A Leistungsverzeichnis is thousands of rows of money. Money is set in Plus Jakarta with tabular figures and no cents in a list — cents only where a document reconciles, like an invoice total. Identifiers are the only thing set in Geist Mono. Status is a tone from the ring, always with its word.

Nr.BezeichnungZeitraumNettoStatus
AR-11Abschlagsrechnung01.06.–30.06.2026317.854 €In Prüfung
AR-10Abschlagsrechnung01.05.–31.05.2026289.410 €Freigegeben
NT-07Nachtrag—17.760 €Überfällig
AR-09Abschlagsrechnung01.04.–30.04.2026244.176 €Bezahlt

03Typography

Two faces,
one job each.

Plus Jakarta Sans carries the voice and every figure. Geist Mono carries identifiers — and nothing else. Both SIL OFL: free to use, embed and ship.

Which face carries what

Aa 4.284.900 €

Plus Jakarta Sans

Headlines, text, UI — and money and quantities: tabular-nums wherever figures stack, no decimals in a list, a Kennzahlkarte or a headline.

AR-2026-005
01.02.0030

Geist Mono

Identifiers only: record numbers, OZ, routes, hashes. Never a price, never a label, never a kicker.

Kickers are Jakarta — 600, 11 px, 0.06 em, uppercase — not mono. Plus Jakarta was chosen from nineteen open-source families: near-monoline strokes, true-circle bowls, an x-height of 0.726 of the cap that holds at 10 pt in an LV table, weights 200–800 and tabular figures. Space Grotesk is retired.

The scale

display-1800 · -0.045em · 0.94
Ein Projekt.
display-2800 · -0.04em · 0.98
Ein Datensatz.
h1700 · -0.032em · 1.06
Von der Ausschreibung bis zur Übergabe
h2700 · -0.026em · 1.12
Abrechnung auf einem Datensatz
h3600 · -0.02em · 1.2
Nachträge und Abzugsregeln
h4600 · -0.014em · 1.3
Leistungsverzeichnis
body-lg400 · -0.006em · 1.6
BauLynq verbindet Ausschreibung, Aufmaß, Prüfung und Abrechnung auf einem Datensatz.
body400 · 0 · 1.6
Von der Ausschreibung bis zur Übergabe geht nichts verloren. Jede Position trägt ihre Geschichte.
body-sm400 · 0 · 1.55
Gilt für alle Vergabeeinheiten nach VOB/B § 16.
caption500 · 0.005em · 1.45
Stand: 27.09.2026 · Bauabschnitt 2
kickerJakarta 600 · 0.06em · 11 px
Abrechnung · Quartier Nord
figureJakarta 650 · tabular
4.284.900 € · 62 %
idGeist Mono 500
AR-2026-005 · OZ 01.02.0030

German typesetting

Do

  • Quotes are „low-high" — „Ein Projekt."
  • Thousands a dot, decimal a comma: 1.234.567,89 € — and no cents where nothing reconciles: 1.234.568 €.
  • Non-breaking space before €, %, §.
  • Dates 27.09.2026, two-digit day and month in tables.
  • tnum anywhere numbers stack vertically.

Don't

  • Set long compounds in all caps — ABSCHLAGSRECHNUNG is unreadable.
  • Hyphenate a headline. Rewrite it shorter.
  • Use tabular figures for a big standalone number — it goes gappy.
  • Letterspace lowercase. Only the uppercase kicker gets tracking.
  • Set a price or a label in mono.

04Colour

Paper, two inks
and one red.

Three surfaces, two ink tiers and oxblood carry almost everything. Eight ramps, stepped in OKLCH so lightness moves evenly to the eye, sit behind them.

Papier, Blatt, Linie

The app stands on three surfaces: Papier is the canvas, Blatt the cards, rail and header, Linie every edge. They are cool greys on purpose — warm greys read as a second hue next to oxblood. Print, the website and the films stand on the brand paper #F7F7F5, the Beton 50 of this page.

PapierCanvas#F6F7F9
BlattCards, rail, header#FFFFFF
LinieEvery edge#E6E8EB

Two inks — and no third grey

AaInk #16181A · 16.6 : 1 — headings, body, figures.
AaInk 2 #53575B · 6.8 : 1 — secondary text, labels, kickers.
There is deliberately no third tier. #707478 is 4.39 : 1 on paper — under AA. Quietness comes from size and weight, never from more grey, and never from a text opacity below 70 %.

The status ring

Six tones, one meaning each. A tone appears as a 3 px tick at the left of a row and, where a column reports it, as a pill on its tint. The word always travels with it.

ToneMeansPillPrinted in
gruenFreigegeben, bezahltFreigegeben#006133
bernsteinIn Prüfung, Frist läuftIn Prüfung#985E00
rotÜberfällig, abgelehntÜberfällig#B83B36
betonEntwurf — no status yetEntwurf#53575B — the tick grey is too light to print
oxMoney, decision, blockageFreigabe offen#8E2222
inkA plain fact with no stateInfo#16181A
Oxblood means money, a decision or a blockage — never a border, never a page outline, never the default colour of a figure. And rot is not the brand red: #B83B36 sits two steps off #8E2222, so an error can never be mistaken for a primary action.

Ramps

Rot

The brand. German industrial red pulled toward Bordeaux so it reads as engineering, not alarm.

50#FFEDE9
100#FFDFD9
200#FFBEB5
300#FF978B
400#F77469
500#D8574D
600#B83B36
700#8E2222
800#6B1112
900#4B0809
950#310404

Beton

The neutral. The brand paper, the hairlines and both ink tiers.

50#F7F7F5
100#EFEFEC
200#DDE0E3
300#C5C9CD
400#A8ACB1
500#8C9095
600#707478
700#53575B
800#383B3F
900#16181A
950#07080A

Stahl

Steel blue. Data slot 4, and the focus ring — the only blue in the system.

50#E6F9FF
100#D0F0FF
200#A7DBFF
300#7AC4FF
400#51A9EF
500#318FD4
600#0972B2
700#00548C
800#003C69
900#002849
950#001930

Grün

Freigegeben, bezahlt, geprüft. Data slot 3.

50#E8FCEE
100#D3F5DE
200#ADE5BF
300#82D19E
400#5BB980
500#3D9E66
600#1B804C
700#006133
800#004722
900#003015
950#001E0B

Bernstein

In Prüfung, Frist läuft. Data slot 5.

50#FFF3DE
100#FFE7C2
200#F9CD8F
300#ECB056
400#D79311
500#BC7800
600#985E00
700#724400
800#543000
900#3B1F00
950#271100

Violett

Data slot 2 only. Never a status, never a UI colour.

50#F7F2FF
100#EDE5FF
200#D8CAFF
300#C2ADFF
400#A98FEE
500#8F75D3
600#7359B2
700#563E8B
800#3E2B68
900#2A1B49
950#1A1030

Türkis

Reserve, for module tints when a module needs its own.

50#E4FBFB
100#CCF5F4
200#9FE4E3
300#6CCFCF
400#37B7B7
500#009D9D
600#007F7F
700#005F5F
800#004546
900#002F30
950#001E1E

Gold

An ordinary ramp. The flag gold #F6C500 is not in it — that one is reserved for the flag rule.

50#FFF5DA
100#FCEABA
200#F0D280
300#E0B836
400#C99C00
500#AA8300
600#886700
700#664C00
800#4B3600
900#342300
950#221500
There are two golds. #AA8300 is gold 500, an ordinary ramp step. #F6C500 is not in any ramp: it is the Schwarz-Rot-Gold flag gold, and it appears in exactly one place — the flag rule.

Semantic tokens

Never reference a ramp step in a component — reference the semantic token, and both themes take care of themselves. Contrast is measured against each theme's canvas.

TokenLightDarkContrast L / DUse
--bl-bg#F7F7F5#07080A—Brand paper — web, print, film
--bl-surface#FFFFFF#121418—Sheets — cards, tables, charts
--bl-ink#16181A#F2F4F616.6 / 18.2Headings, body, figures
--bl-ink-2#53575B#9AA0A66.8 / 7.6Secondary text — the last tier
--bl-line#E2E3E0rgba(255,255,255,.08)—Hairlines. 1 px, always
--bl-accent#8E2222#D8574D8.2 / 5.1Oxblood — the primary action, money, decision
--bl-focus#286CBE#5FA4FC4.9 / 7.8Focus ring. Deliberately not red

05Data visualisation

Validated,
not picked.

Charts answer „wo stehen wir?". The palette was optimised, then validated against colour-blindness simulation.

Categorical — fixed order, never cycled

1 · Rot#A22626#D8574D
2 · Violett#A09BFF#5E54BA
3 · Grün#007A4B#19A572
4 · Stahl#60A6FF#1962B6
5 · Bernstein#9B6000#C57E00
6 · Magenta#DE75B4#A5417F
CheckLightDarkGate
Adjacent CVD ΔE20.720.8Target ≥ 8. Comfortably clear.
Normal-vision ΔE24.023.1Hard floor ≥ 15.
Lightness band, chroma floorpasspassAll six in band, all above the floor.
Contrast vs surface3 of 6 < 3:1all ≥ 3:1Slots 2, 4, 6 on white need labels or the table view.
Series cap. Bars, columns, lines and stacks use all six — only neighbours touch. Scatter, bubble, choropleth and small multiples use two. Past two, facet or fold into „Sonstige".

In practice

Abrechnungsstand je Gewerk

Abgerechnet und offen, in Tausend Euro

05001.0001.5002.0001.600Rohbau1.150Ausbau1.210TGA620Fassade430Außenanlagen
AbgerechnetOffen

Kumulierte Abrechnung

Februar bis Juni 2026, in Tausend Euro

05001.0001.500FebMärAprMaiJun1.380

Sequential and diverging

Sequential — one hue, light to dark. Magnitude only.

Diverging — Stahl to Rot, neutral grey middle. Polarity only.

Marks

Do

  • Bars ≤ 24 px thick, 4 px rounded at the data end, square at the baseline.
  • 2 px lines; markers ≥ 8 px with a 2 px surface-colour ring.
  • A 2 px surface gap between stacked segments — never a stroke.
  • Hairline solid gridlines. Label the endpoint or the extreme, not every point.
  • A legend whenever there are two or more series.

Don't

  • Two y-axes. Ever. Split into two charts or index to a base.
  • Colour bars by their value — bar length already says that.
  • Use a status colour as „Serie 4".
  • Colour label text with the series colour. Marks carry colour; text wears ink.
  • Dashed gridlines, 3-D, gradients, or a border around the plot.

06Layout & space

Hairlines
and air.

One 4 pt unit, a 1200 px content maximum, two radii, and one surface per page.

Space

Base unit 4 px. Section padding 96 / 56 px, content maximum 1200 px, gutter clamp(20px, 4vw, 48px).

4
8
12
16
20
24
32
40
48
64
80
96
128
160

Radii

10 px on every control, 12 px on every card — the app's two radii. Pills for chips and status; 18 px only for the website's large sheets. Print uses none.

10 · controls
12 · cards
18 · web sheets
pill · chips

One surface per page

A page is one Blatt on the Papier with a 1 px Linie edge. Sections inside it are separated by hairlines — never by boxes that each bring their own radius and padding, which reads as four unrelated widgets. Elevation is for what genuinely floats: menus, dialogs, a toast.

Aufmaß · Decke EGStand 27.09.2026
01.02.0030 Stahlbeton C30/3776 m³
01.02.0040 Bewehrung BSt 5008,4 t
01.03.0010 Schalung Decke312 m²

07Components

A small kit,
built from tokens.

If something is not here, build it from the tokens rather than inventing a value.

Buttons and modules

One primary per view, in oxblood — it is that view's red. A creation action is always a button, never a ghost link. 10 px radius. The focus ring is Stahl blue, never red.

InrXPlanXProcureXLedgerXReportsX

Module names: Jakarta, hairline outline, no fill. Capital X, no space, no hyphen.

Rows and status

AR-2026-005Abschlagsrechnung 5Quartier Nord · BA 215.372 €In Prüfung
AM-2026-033Aufmaß Decke EGAchse 1–876 m³Freigegeben
NT-07Nachtrag Zugangwartet auf Entscheidung17.760 €Freigabe offen
01.02.0030Stahlbeton C30/37Leistungsverzeichnis186 m³Entwurf

Status pills read the ring — tinted ground, tone-coloured word; the 3 px tick marks the row. The word is mandatory: a coloured row means nothing to a colour-blind Bauleiter without Überfällig beside it.

Kennzahlen

Auftragssumme4.284.900 €Stand 27.09.2026
Abgerechnet2.656.638 €62 % der Auftragssumme
Blockiert17.760 €Nachtrag NT-07 wartet

Kicker label, ink figure in Jakarta — tabular, no cents, no icon. Oxblood only on the figure that is money held up, never as the default figure colour. Figures appear; they never count up.

08Motion

Motion explains
a change.

Motion explains a relationship or a change of state. If it explains nothing, it is decoration, and it does not ship.

Four durations — try them

One scale, defined once as tokens, never a bare duration at a call site. Easings say what the thing is doing: --bl-ease-out arriving, --bl-ease-in leaving, --bl-ease-move travelling.

--bl-dur-tap 90 msPressed and hover — must read as instant.
Press and hold the button
--bl-dur-ui 160 msControl state, tint, tick, badge.
EntwurfFreigegeben
--bl-dur-enter 220 msSomething arrives. Exits mostly do not animate.
Nachtrag NT-07 · 17.760 €
--bl-dur-move 300 msSomething travels: sheet, drawer, reorder.

Rules

Do

  • Animate only transform and opacity — composited, never layout.
  • Animate entrances. Let exits happen at once; the backdrop is the exception.
  • Stagger on first paint only, capped at six rows.
  • Collapse every duration to 1 ms under prefers-reduced-motion — once, at the token layer.
  • Gate pointer effects by pointer: fine.

Don't

  • Animate height, width or top.
  • Count a figure up. Money and status appear.
  • Re-stagger a list on every refetch — that is noise.
  • Write a bare 0.22s at a call site: it silently opts out of the guard.
  • Bounce.

09Motion & film

One world.
One red thread.

The films are one continuous world a camera travels through — the paper, guides and type of this page — with every picture placed on the spoken word.

The frame, reduced

The camera creeps, words light on their spoken word, the thread draws station by station — and yields when the flag rule lands, so there is only ever one red. Captions keep their own band.

The rules

01One world, one camera

Scenes are places in one world, not slides. The camera glides on the spoken word, creeps 2–3 % over a hold and drifts 2–3 px, so nothing is ever pixel-still. Tilt at most 0.5°.

No fade to black: the next shot overlaps the last by 50–100 ms — a morph, a continuity cut, the UI moving the camera.

02Words light as they are said

Every word starts dim and lights on its timestamp. A title's last clause keeps the dim-to-lit contrast — the website's headline, in motion.

03The red thread

One line in oxblood (#D8574D on dark) links the stations of one LV position — LV, Aufmaß, Prüfung, Abnahme, Rechnung, XRechnung. It draws as the narration names each step, runs in its own lane and never crosses text or UI.

It is the only red in the frame. When the app's own red is on screen — a primary button, the Freigabe stamp — the thread yields.

04Gold once

#F6C500 appears only in the flag rule on the end card, its black band in ink so it survives on dark. The end card is the film's own world pulled back: mark top-left, the name at the far end, never a lockup.

05Sound under the picture

A synthesised kit, no stock audio: pencil for the Maßkette, stamp for the Freigabe, tick per validated position, chime for „XRechnung valide", paper, whoosh on every camera travel. Sound leads the cut by 0.1–0.2 s.

A quiet music bed, ducked under the voice (about –32 LUFS); the master is –14 LUFS.

06Captions keep their band

Phrases of two to five words, never bouncing word by word, timed from the word timestamps. A boxed pill that never sits on a figure, a button or a headline — marked content pushes it away. On by default on the website.

07The app stays the app

Screens are the shipped product in its light theme, with real German data and one hero record through the whole film. A counter never shows a value that was never true.

Eases by role

RoleCurveUse
entercubic-bezier(.23,1,.32,1)Arrivals. The brand --bl-ease is this curve.
exitcubic-bezier(.55,0,.9,.4)Leaving, at 0.65 × the enter duration.
movecubic-bezier(.77,0,.175,1)Panes, sheets, reorders.
cameracubic-bezier(.65,0,.35,1)Every camera glide — the brand --bl-ease-io.
swiftcubic-bezier(.2,0,0,1)Presses and toggles.
German precision, not bounce: springs at most 0.12, a pop only for a modal. Light plan paper is the default theme, and one timeline renders 16:9, 9:16, 1:1 and 4:5.

Watch the films

10Voice

Write like
a Bauleiter.

German, direct, technically literate. Short sentences, exact nouns, no adjective doing a number's job.

Tone

Do

  • „Von der Ausschreibung bis zur Übergabe geht nichts verloren."
  • Name the artefact: Leistungsverzeichnis, Aufmaß, Nachtrag, Abschlagsrechnung.
  • Cite the rule where it earns trust: VOB/B § 16.
  • Headlines of one or two clauses. Sub-copy of one sentence.
  • Use „Sie". Address a Bauleiter, not a persona.

Don't

  • „Revolutionäre KI-gestützte Cloud-Lösung."
  • Anglicisms where a German word exists — Abrechnung, not Billing.
  • Exclamation marks. Ever.
  • Claims without a figure behind them.
  • Emoji, in any surface.

Naming

BauLynq — one word, capital B and L, everywhere. Modules are InrX, PlanX, ProcureX, LedgerX, ReportsX — capital X, no space, no hyphen. Messages the app sends a person are written in German and English at the place they are raised; records stay German.

11Documents

Same tokens,
on paper.

Word, PowerPoint and Excel templates ship with this system, all in German, all built from the same tokens.

Templates

FileWhat it is
BauLynq-Briefbogen.docxDIN 5008 letterhead — mark, kicker, hairline rules, real page-number fields.
BauLynq-Bericht.docxProject report — bleeding near-black cover, live table of contents, KPI row, invoice table.
BauLynq-Vorlage.dotxThe report as a true Word template with the named style set.
BauLynq-Praesentation.pptxTen German slides — title, agenda, statement, pillars, KPI, table, native chart, suite, timeline, close.
BauLynq-Vorlage.potxPowerPoint template — BauLynq theme colours and fonts, eight named layouts.
BauLynq-Kostenverfolgung.xlsxCost-control workbook — 32-position LV, formula-driven KPIs, invoice ageing, two native charts.
BauLynq-Vorlage.xltxThe workbook stripped to headers, formulas and formatting.
Install the fonts first. Without them Office falls back to Aptos or Calibri — the layout holds, the voice does not. Both families are SIL OFL, so install them company-wide and send the files to clients.

12Downloads

Everything,
in one pack.

Every artefact in the system. The logo files and tokens are generated from one source each and regenerated whenever it changes.

Using the tokens

<link rel="stylesheet" href="/assets/css/baulynq-tokens.css">

.card {
  background: var(--bl-surface);
  border: 1px solid var(--bl-line);
  border-radius: 12px;
  color: var(--bl-ink);
}
.card .amount {
  font: 650 var(--bl-fs-data)/var(--bl-lh-data) var(--bl-sans);
  font-variant-numeric: tabular-nums;
}

Regenerate the CSS from the JSON rather than editing it: npm run tokens. Change a value in one place and everything downstream follows.