Skip to content

Typography in Nubisco is built on three layers: a raw scale of discrete sizes, a set of semantic tokens for line-height and letter-spacing, and type sets — pre-configured roles that bundle everything together into a named identity like body-md or heading-03. Components always use type sets, never raw scale values directly.

Typefaces

Nubisco ships with two typefaces, both served via the font plugin.

Plus Jakarta Sans — sans-serif

The primary typeface for all UI text. It is a geometric sans-serif with strong legibility at small sizes and enough personality to work at display scale. Used for labels, body copy, headings, and display type.

The quick brown foxABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789Plus Jakarta Sans — Regular, Semibold, Bold — var(--nb-font-family-sans)

Fira Code — monospace

Used exclusively for code, JSON viewers, technical values, and keyboard shortcuts. Its ligatures are enabled by default.

const ui = nubisco()0123456789 => != !== === >= <= -> <-Fira Code — Regular — var(--nb-font-family-mono)

The scale

The font-size scale is a curated set of values from 8px to 76px. Values are named by their pixel equivalent and exposed as CSS custom properties on :root.

8Nubisco UI
10Nubisco UI
12Nubisco UI
14Nubisco UI
16Nubisco UI
20Nubisco UI
24Nubisco UI
32Nubisco UI
42Nubisco UI
54Nubisco UI
76Nubisco UI

How CSS variables are generated

All typographic values are derived from SCSS maps in src/styles/variables/_type.scss and emitted as CSS custom properties on :root by _theme.scss. Three categories:

PrefixExampleSource map
--nb-font-size-{N}--nb-font-size-14$fontSizes
--nb-font-weight-{name}--nb-font-weight-semibold$fontWeight
--nb-font-family-{name}--nb-font-family-mono$fontFamilies
--nb-line-height-{name}--nb-line-height-normal$lineHeights
--nb-letter-spacing-{name}--nb-letter-spacing-tight$letterSpacing
--nb-type-{set}-{prop}--nb-type-body-md-size$typeSets

Hierarchy through scale

Nubisco UI is primarily a product interface library — dashboards, forms, data-heavy views. At this density, the default reading size is body-md (14px), not 16px. Reserve body-lg for settings screens, onboarding flows, and other low-density contexts where users read more than they interact.

The hierarchy descends from the most prominent element on the page toward the least:

  1. Page title — one per view, heading-04 or higher
  2. Section heading — groups of content, heading-02 or heading-03
  3. Card/panel title — scoped context, heading-01
  4. Body — primary readable content, body-md
  5. Supporting text — descriptions, help text, captions, body-sm
  6. Labels — form labels, column headers, badges — label-md or label-lg
  7. Meta — timestamps, IDs, secondary counts — label-sm with --nb-c-text-muted

Line-height rationale

Line-height is the most common typographic mistake in UI systems. Nubisco uses tighter leading as sizes grow:

ContextLine-heightWhy
Display / Large headings1.0 – 1.1Visual mass; long ascenders/descenders create natural gap
Mid headings1.2 – 1.3Enough breathing room for multi-line titles
Body text1.5 – 1.625Comfortable for sustained reading
Labels / UI controls1.4Compact but not cramped in tight layouts
Code1.5Consistent gutter for scan-reading

Never set line-height equal to font-size — this produces lh = 1 regardless of font size and makes body text illegible.

Letter-spacing at large sizes

At display and heading sizes, the optical spacing between glyphs appears too loose due to how fonts are designed (they assume small text). Nubisco applies negative letter-spacing to headings and display type:

  • heading-02 and up: -0.01em to -0.025em
  • display-01 / display-02: -0.03em

Never apply negative letter-spacing to body or label text — it tightens to the point of illegibility at small sizes.

Semibold vs bold

  • Semibold (600) — headings at heading-01 through heading-03, all labels. Enough weight for hierarchy without heaviness in dense UIs.
  • Bold (700) — only for heading-04 and above, plus display. The visual jump to 700 is intentional at large sizes; at small sizes it reads as shouting.
  • Regular (400) — all body and code.
  • Light (300) — reserved for decorative large numbers or editorial use; avoid in core product UI.

Code typography

Always use code-* type sets for any monospaced content — not raw font-family: mono with an arbitrary size. This ensures Fira Code is paired with the right line-height for readability and the correct weight (ligatures only activate at regular).

Colour and text roles

The following semantic tokens are available for text colour:

TokenUse
--nb-c-textPrimary body text, headings
--nb-c-text-mutedSupporting text, captions, secondary info
--nb-c-text-subtlePlaceholder text, disabled labels, ghost hints

Type sets are the primary way to apply typography. Each set bundles font-size, weight, line-height, letter-spacing, and font-family into a single named role. Apply them with the .type-{name} utility class or via CSS custom properties.

Labels

Used in UI controls — form field labels, column headers, badges, chips, tabs.

Label Small — 11pxlabel-sm · 11px · semibold · lh 1.4
Label Medium — 12pxlabel-md · 12px · semibold · lh 1.4
Label Large — 14pxlabel-lg · 14px · semibold · lh 1.4

Body

Used for readable content — paragraphs, descriptions, help text, notifications.

The quick brown fox jumps over the lazy dog. Nubisco UI is a design system built for product interfaces at scale.

body-sm · 12px · regular · lh 1.5

The quick brown fox jumps over the lazy dog. Nubisco UI is a design system built for product interfaces at scale.

body-md · 14px · regular · lh 1.5

The quick brown fox jumps over the lazy dog. Nubisco UI is a design system built for product interfaces at scale.

body-lg · 16px · regular · lh 1.625

Code

Used for monospaced content — code blocks, inline code, JSON viewers, keyboard shortcuts.

const answer = 42; // => true

code-sm · 11px · regular · lh 1.5 · Fira Code

const answer = 42; // => true

code-md · 13px · regular · lh 1.5 · Fira Code

const answer = 42; // => true

code-lg · 16px · regular · lh 1.5 · Fira Code

Headings

Used for page titles, section headings, card headers, and dialog titles. Headings 01–06 map directly to h6h1 respectively.

Heading 01 — section title

heading-01 · 16px · semibold · lh 1.3 · ls 0 → h6

Heading 02 — card title

heading-02 · 20px · semibold · lh 1.25 · ls −0.01em → h5

Heading 03 — page section

heading-03 · 24px · semibold · lh 1.2 · ls −0.015em → h4

Heading 04 — page title

heading-04 · 32px · bold · lh 1.15 · ls −0.02em → h3

Heading 05

heading-05 · 42px · bold · lh 1.1 · ls −0.02em → h2

Heading 06

heading-06 · 54px · bold · lh 1.05 · ls −0.025em → h1

Display

Used for hero sections, onboarding screens, marketing surfaces, and large statistical callouts.

Display 01

display-01 · 60px · bold · lh 1.0 · ls −0.03em

Display 02

display-02 · 76px · bold · lh 1.0 · ls −0.03em

CSS custom properties

Every type set generates four CSS variables on :root:

css
--nb-type-{name}-size
--nb-type-{name}-weight
--nb-type-{name}-line-height
--nb-type-{name}-letter-spacing
--nb-type-{name}-family

Full reference:

SetSizeWeightLine heightLetter spacing
label-sm--nb-font-size-11--nb-font-weight-semibold1.40
label-md--nb-font-size-12--nb-font-weight-semibold1.40
label-lg--nb-font-size-14--nb-font-weight-semibold1.40
body-sm--nb-font-size-12--nb-font-weight-regular1.50
body-md--nb-font-size-14--nb-font-weight-regular1.50
body-lg--nb-font-size-16--nb-font-weight-regular1.6250
code-sm--nb-font-size-11--nb-font-weight-regular1.50
code-md--nb-font-size-13--nb-font-weight-regular1.50
code-lg--nb-font-size-16--nb-font-weight-regular1.50
heading-01--nb-font-size-16--nb-font-weight-semibold1.30
heading-02--nb-font-size-20--nb-font-weight-semibold1.25-0.01em
heading-03--nb-font-size-24--nb-font-weight-semibold1.2-0.015em
heading-04--nb-font-size-32--nb-font-weight-bold1.15-0.02em
heading-05--nb-font-size-42--nb-font-weight-bold1.1-0.02em
heading-06--nb-font-size-54--nb-font-weight-bold1.05-0.025em
display-01--nb-font-size-60--nb-font-weight-bold1.0-0.03em
display-02--nb-font-size-76--nb-font-weight-bold1.0-0.03em

Utility classes

Apply a complete type set in one class:

html
<p class="type-body-md">Readable paragraph text</p>
<span class="type-label-md">Form label</span>
<h2 class="type-heading-03">Section title</h2>
<code class="type-code-md">const x = 1</code>

h1h6 elements are already wired to their corresponding type sets by default (h1heading-06, h6heading-01). You only need .type-heading-* when applying heading styling to a non-heading element.

SCSS usage in components

Inside component <style> blocks, reference type set variables directly:

scss
.my-component__title {
  font-size: var(--nb-type-heading-02-size);
  font-weight: var(--nb-type-heading-02-weight);
  line-height: var(--nb-type-heading-02-line-height);
  letter-spacing: var(--nb-type-heading-02-letter-spacing);
}

.my-component__description {
  font-size: var(--nb-type-body-md-size);
  font-weight: var(--nb-type-body-md-weight);
  line-height: var(--nb-type-body-md-line-height);
}

Line-height and letter-spacing scale utilities

Independent scale values are also available when you need to override a single property:

css
/* Line heights */
--nb-line-height-none: 1 --nb-line-height-tight: 1.1 --nb-line-height-snug: 1.25
  --nb-line-height-normal: 1.5 --nb-line-height-relaxed: 1.625
  --nb-line-height-loose: 1.75 /* Letter spacing */
  --nb-letter-spacing-tighter: -0.05em --nb-letter-spacing-tight: -0.02em
  --nb-letter-spacing-normal: 0em --nb-letter-spacing-wide: 0.02em
  --nb-letter-spacing-wider: 0.05em;

Adding a new type set

Edit $typeSets in src/styles/variables/_type.scss:

scss
$typeSets: (
  // ...existing sets...
  caption: (
      size: 11,
      weight: regular,
      line-height: 1.4,
      letter-spacing: 0.01em,
    )
) !default;

This automatically generates --nb-type-caption-* variables and a .type-caption utility class.