Flux 2.0

Foundations

Typography

Lexend Light carries everything Kaptio says. Lexend Bold carries what matters. JetBrains Mono carries what a machine produced. Three jobs, two families, and no weight in between.

Two voices

A sans that speaks, and a mono that reports.

Flux 1.x had one family and two weights, which kept output consistent but left no way to distinguish a booking reference from a sentence. Everything looked like prose, including the things that were not.

2.0 promotes JetBrains Mono Variable from a code font to a documented second voice. It carries identifiers, metadata, structural labels and values — anything that is data rather than language. Look at the eyebrow above this page's title, or the column headers in any Flux table: that is the second voice doing its job.

Lexend

Two weights. Nothing between them, and nothing outside them.

Lexend Light 300 — the default voice

Lexend Bold 700 — headings and emphasis

html
<p style="font-family: var(--flux-font-sans); font-weight: 300; font-size: var(--flux-text-4xl); letter-spacing: var(--flux-tracking-tight); margin: 0">
  Lexend Light 300 — the default voice
</p>
<p style="font-family: var(--flux-font-sans); font-weight: 700; font-size: var(--flux-text-4xl); letter-spacing: var(--flux-tracking-tight); margin: var(--flux-space-4) 0 0">
  Lexend Bold 700 — headings and emphasis
</p>

Light is the default for everything, headings included. Bold is for headings that need to anchor a page, for emphasis inside a sentence, for labels, and for table headers.

There is no 400, 500 or 600. A request for a weight Flux does not load gets synthesised by the browser into something that is not Lexend, which is why the base layer forces every bold element back to 700.

css
/* From flux.css. This is enforced, not suggested. */
b, strong, h1, h2, h3, h4, h5, h6, th {
  font-weight: var(--flux-weight-bold);
}

JetBrains Mono

Three weights, because the second voice has its own hierarchy.

400 Regular — code, values, raw output

500 Medium — inline metadata, badges, keyboard hints

700 Bold — structural labels, small and letter-spaced

html
<p style="font-family: var(--flux-font-mono); font-weight: 400; font-size: var(--flux-text-lg); margin: 0">
  400 Regular — code, values, raw output
</p>
<p style="font-family: var(--flux-font-mono); font-weight: 500; font-size: var(--flux-text-lg); margin: var(--flux-space-2) 0 0">
  500 Medium — inline metadata, badges, keyboard hints
</p>
<p style="font-family: var(--flux-font-mono); font-weight: 700; font-size: var(--flux-text-xs); letter-spacing: var(--flux-tracking-widest); text-transform: uppercase; margin: var(--flux-space-4) 0 0">
  700 Bold — structural labels, small and letter-spaced
</p>
WeightTokenUse
400 Regular--flux-weight-mono-regularCode blocks, raw values, identifiers in body copy.
500 Medium--flux-weight-mono-mediumInline metadata, version numbers, keyboard hints, table figures.
700 Bold--flux-weight-mono-boldEyebrows, column headers, section labels. Always small, uppercase and letter-spaced.
Careful
The mono voice is for data, not for volume. Uppercase mono attext-lg or larger reads as a warning label. Keep the bold, letter-spaced form attext-xs.

Prototypes

Product prototypes set in Google Sans Flex and Manrope. Decks and this site stay on Lexend and JetBrains Mono.

html
<link rel="stylesheet" href="https://flux.kaptio.com/tokens/v2/flux.css" />
<link rel="stylesheet" href="https://flux.kaptio.com/tokens/v2/flux-components.css" />
<link rel="stylesheet" href="https://flux.kaptio.com/tokens/v2/flux-prototype.css" />

Sold-out departures

Review sold-out departures before they need a stop-sell.

Berths sold against capacity

79.7%

+3.2 pts vs last week

html
<div style="display: grid; gap: var(--flux-space-5)">
  <h2 style="margin: 0; font-size: var(--flux-text-2xl)">Sold-out departures</h2>
  <p class="flux-description" style="margin: 0">Review sold-out departures before they need a stop-sell.</p>
  <div>
    <p class="flux-meta" style="margin: 0">Berths sold against capacity</p>
    <p class="flux-kpi" style="margin: var(--flux-space-2) 0">79.7%</p>
    <p class="flux-kpi-change" style="margin: 0; color: var(--flux-text-success)">+3.2 pts vs last week</p>
  </div>
  <div class="flux-field" style="max-width: 20rem">
    <label class="flux-field__label" for="type-search">Search</label>
    <input class="flux-input" id="type-search" type="text" placeholder="Voyage, ship or order" />
  </div>
  <div>
    <button class="flux-button" type="button">Ask Runa for a brief</button>
  </div>
</div>
RoleFontSizeWeightApply with
HeadingsGoogle Sans FlexFrom the scale, as in LexendMediumh1–h6
Card headingGoogle Sans Flextext-base · 16pxMedium.flux-card__title
KPI valueGoogle Sans Flextext-4xl · 36pxBold, text-brand.flux-metric__value, .flux-kpi
Body and descriptionManropetext-base · 16pxRegularbody, .flux-description
TablesManropetext-sm · 14pxRegular.flux-table
Input and dropdownManropetext-sm · 14pxRegular.flux-input, .flux-select
LabelsManropetext-sm · 14pxMedium.flux-field__label
ButtonsManropetext-sm · 14pxSemi Bold.flux-button
KPI changeManropetext-sm · 14pxSemi Bold.flux-metric__change, .flux-kpi-change
Small and metaManropetext-xs · 12pxRegular.flux-meta

flux-prototype.css remaps the font and weight roles every component reads, so buttons, fields, tables and badges retype without new markup. Prototypes have no mono voice: identifiers, badges and column headers set in Manrope. Sizes do not change: a prototype uses the same scale and component sizes as the Lexend product. The px values are the equivalents at the 16px root; the root stays fluid.

Set weights with --flux-prototype-weight-regular, -medium,-semibold and -bold, never a literal number. A KPI change carries a sign or arrow as well as its status colour, so the direction does not rely on colour alone.

This site shows prototype regions inside Lexend pages: its root setsdata-flux-type="lexend", and each component specimen setsdata-flux-type="prototype". A prototype needs neither attribute.

Careful
Never load flux-prototype.css in a deck. Decks keep Lexend and JetBrains Mono.

The scale

TokenSizeTypical use
--flux-text-xs0.75remShort metadata, badges, help text
--flux-text-sm0.875remDense UI, table cells, secondary copy
--flux-text-base1remBody
--flux-text-lg1.125remLede, card titles
--flux-text-xl1.25remSubsection headings
--flux-text-2xl1.5remSection headings
--flux-text-3xl1.875remPage headings, dense layouts
--flux-text-4xl2.25remPage headings
--flux-text-5xl3remMarketing display
--flux-text-6xl3.75remSlide display

Sizes are in rem, and the root is fluid — see below. Atext-base paragraph is 16px on a laptop and 19px on a large display, without a media query.

Careful
text-xs is not a compact body size. Reserve it for badges, help text and short metadata. Controls, table cells and compact secondary copy start at text-sm; sentences intended to be read use text-base. If content does not fit, reflow or scroll it rather than shrinking the type.

The fluid root

Every rem in Flux scales. This is the one place the system reaches past tokens into a base rule.

css
html {
  font-size: clamp(16px, 0.875rem + 0.4vw, 19px);
}

Type, spacing and component heights all move together, because they are all expressed inrem. A dashboard on a 27-inch display gets a proportionally larger interface rather than the same 16px interface with more empty space around it.

The consequence to remember: never mix a px value into a layout built from Flux spacing. It will not scale with everything around it, and the gap will widen as the viewport does. The one sanctioned exception is--flux-hairline, which is 1px on purpose.

Measure and leading

TokenValueUse
--flux-measure-prose38remReading column. Roughly 70 characters.
--flux-measure-content62remMixed content: tables, cards, specimens.
--flux-measure-wide78remFull-bleed layouts and dashboards.
--flux-leading-tight1.15Display headings.
--flux-leading-snug1.3Headings and dense labels.
--flux-leading-normal1.55Interface default.
--flux-leading-relaxed1.7Body copy meant to be read.
Note
Lexend was drawn for reading proficiency, and it rewards air. Body copy atleading-relaxed inside measure-prose is the combination the rest of the system is tuned around.

Do and don’t

Do

  • Use Lexend 300 for everything, and 700 when something has to anchor.
  • Reach for the mono voice when the content is data rather than language.
  • Keep body copy inside measure-prose at leading-relaxed.
  • Use at least text-sm for controls and table cells, and text-base for readable sentences.
  • Use tracking-tight on headings at text-3xl and above.
  • Use tracking-tighter only on metric figures, through --flux-metric-value-tracking. It is too tight for words.

Don’t

  • Do not request a Lexend weight other than 300 or 700.
  • Do not set mono uppercase above text-xs.
  • Do not use text-xs for paragraphs, table cells, controls or primary values.
  • Do not shrink type to fit more content above the fold.
  • Do not mix px into a layout built from rem tokens.
  • Do not use another sans-serif family. Lexend everywhere, except prototypes, which load exactly two through flux-prototype.css.