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 declares two typefaces as its defaults: Plus Jakarta Sans for all UI text and Fira Code for code. These are the values of --nb-font-family-sans and --nb-font-family-mono. They are not bundled or loaded automatically: until you load them (or override the two variables), text falls back to the system stack. See Loading the fonts below.
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.
Fira Code — monospace
Used exclusively for code, JSON viewers, technical values, and keyboard shortcuts. Its ligatures are enabled by default.
Loading the fonts
NubiscoUI references --nb-font-family-sans and --nb-font-family-mono everywhere, but it does not ship or auto-load any font files. This keeps the bundle free of fonts you may not want: consumers who already serve their own typefaces pay zero cost. There are two ways to get real fonts on the page.
Option A: override the family variables (bring your own)
If your app already loads fonts (its own @font-face, a bundler plugin, an @fontsource/* package, etc.), just point the two variables at them on :root. You load the files; NubiscoUI uses them.
/* main.css or App.vue <style>: load Inter + JetBrains Mono yourself, then: */
:root {
--nb-font-family-sans: 'Inter', system-ui, sans-serif;
--nb-font-family-mono: 'JetBrains Mono', ui-monospace, monospace;
}Every type set resolves its family through these two variables. body, label, code, heading, and display all re-point automatically the moment the variables change. There is nothing else to override.
Option B: use the bundled defaults via the fonts plugin
NubiscoUI exports a Vite plugin that self-hosts the default Plus Jakarta Sans + Fira Code (via Fontsource, no third-party CDN, RGPD-friendly). Install the source packages and add the plugin:
pnpm add -D @fontsource/plus-jakarta-sans @fontsource/fira-code// vite.config.ts
import { defineConfig } from 'vite'
import { fonts } from '@nubisco/ui/plugins/fonts'
export default defineConfig({
plugins: [fonts()],
})Then add the matching CSS import to your application entry, once. unplugin-fonts does not inject it automatically:
// src/main.ts (or your equivalent entry)
import 'unfonts.css'TypeScript users may need to declare the module in their env.d.ts:
declare module 'unfonts.css'Called with no arguments, fonts() loads the library defaults: Plus Jakarta Sans (weights 400/500/600/700) and Fira Code (400/600), normal styles, font-display: swap. Because these match the declared --nb-font-family-* defaults, you do not need to touch any CSS variable.
To load different families or weights, pass options (they fully replace the defaults). Any provider unplugin-fonts supports works, including custom for your own self-hosted woff2 files:
fonts({
fontsource: {
families: [
{ name: 'Inter', weights: [400, 600], styles: ['normal'] },
{ name: 'JetBrains Mono', weights: [400], styles: ['normal'] },
],
},
})The name passed to each family must match the value used in --nb-font-family-sans / --nb-font-family-mono exactly, including the quoting around multi-word names like 'JetBrains Mono'. Fontsource emits the @font-face under that exact family name, so if the override does not match, the browser falls through to the next fallback in the stack.
Then set --nb-font-family-sans / --nb-font-family-mono (Option A) so NubiscoUI uses the family names you just loaded.
Not on Vite? The plugin is a convenience, not a requirement. Load the fonts however your toolchain prefers (hand-written
@font-face, your own bundler plugin, etc.), then use Option A to wire them into NubiscoUI.
Preloading critical weight
Fontsource sets font-display: swap, so text never blocks render. For first-paint LCP screens (login, dashboard landing), preloading the single weight that body-md uses removes the flash-of-fallback-text:
<!-- path is the hashed woff2 your bundler emits for the 400 weight -->
<link
rel="preload"
href="/path/to/your-body-weight.woff2"
as="font"
type="font/woff2"
crossorigin
/>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.
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:
| Prefix | Example | Source 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 |