Pular para o conteúdo

Typography

themes specs/themes/typography.kmd

Typography system for Koder UIs — font stacks, type scale, weights, leading/tracking tokens, and the 12 type roles every widget binds against. Material parity (`/styles/typography/fonts` + scale). Implementation: Inter (Latin) + JetBrains Mono (code) self-hosted per `#015`; per-preset overrides from `ui-style.kmd`.

Quando esta spec se aplica

Triggers primários

Todos os triggers

Corpo da especificação

Spec — Typography

Facet Visual do Koder Design. Material parity: https://m3.material.io/styles/typography/fonts.

Two font families

RoleFamilySourceWeights shipped
Sans (body, UI)Interself-hosted (assets/fonts/inter-latin-400.woff2)400 (regular), 500 (medium), 600 (semibold), 700 (bold)
Mono (code, monospaced)JetBrains Monoself-hosted400, 500, 700

Both ship with font-display: swap per #015 so the system fallback shows immediately; webfont swaps in without FOIT.

System fallback stack (browser/OS):

font-family: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, Consolas, monospace;

Per-preset overrides via ui-style.kmd (e.g., terminal_classic uses JetBrains Mono for everything; gnome uses Cantarell first).

R1 — Type scale (15 roles)

RoleSize (px)Line heightWeightTracking
display-large5764400-0.25
display-medium45524000
display-small36444000
headline-large32406000
headline-medium28366000
headline-small24326000
title-large22286000
title-medium16246000.15
title-small14206000.10
body-large16244000.50
body-medium14204000.25
body-small12164000.40
label-large14205000.10
label-medium12165000.50
label-small11165000.50

Aligned with Material 3 type scale. Pixel sizes are dp (density- independent); browsers map 1:1 to CSS px below high-DPI.

R2 — Role binding contract

Every text node binds to a role, not a raw size:

// ❌
Text("Welcome", style: TextStyle(fontSize: 28, fontWeight: FontWeight.w600))

// ✅
Text("Welcome", style: KoderType.of(context).headlineMedium)

The theme exposes KoderType (planned in koder_kit) with 15 getters matching the role names above. Widget code never references px sizes.

R3 — Role usage guide

RoleWhere
display-*Hero text, marketing landings, splash screens. Never in app UI.
headline-*Page titles, section openers, modal titles
title-largeCard titles, dialog titles, list-group headers
title-mediumApp bar title, button labels (extended FAB, primary CTA)
title-smallSettings tile title, table column header
body-largeLong-form reading text (article body, doc page body)
body-mediumDefault UI body, paragraph text, dialog body, list-item text
body-smallCaptions, footnotes, secondary text
label-largeStandard button label, primary tab
label-mediumChip label, badge text
label-smallTime stamp, micro-label, status indicator

R4 — Per-class scale step (responsive)

Per window-size-classes.kmd, type adjusts per class:

ClassDisplayHeadlineBody
Compact-1 step-1 stepbase
Mediumbasebasebase
Expanded+1 stepbasebase
Large+1 step+1 stepbase

"step" = next role up/down in the scale (e.g., headline-large -1 becomes headline-medium).

R5 — Color binding

Type color comes from color-roles.kmd:

Default usageRole
Body texttext
Secondary texttext-muted
Disabled texttext-subtle
Linkaccent
Visited linkaccent darkened by tone-shift (no separate role)
Code (inline)text + surface-variant background
Code (block)text on bg-inset

Never use error red for non-error body text.

R6 — Multilingual / i18n considerations

Inter ships only Latin subset. For other scripts:

  • pt-BR: covered by Latin
  • East Asian (CJK): falls back to system "Noto Sans CJK" via font-family chain
  • Arabic / RTL: falls back to system Arabic; layout flips via dir="rtl" per i18n/contract.kmd
  • Devanagari, etc.: system fallback

When shipping a non-Latin locale officially, vendor the corresponding Noto Sans subset (~50 KB each) alongside Inter.

R7 — Accessibility

  • Minimum body text size: 14 px (body-medium)
  • Minimum touch-target text size: 12 px with line-height ≥ 1.4
  • Line length: target 45-75 characters per line for body text
  • Contrast: per color-roles.kmd R4 (AAA on text/bg)
  • prefers-reduced-motion does NOT affect type
  • Bold/italic combine with role, not replace: body-medium-bold is a STATE on body-medium, not a separate role

R8 — Code typography

Inline <code>:

  • Font: JetBrains Mono
  • Size: matches surrounding body role
  • Background: surface-variant token
  • Padding: 2px 6px
  • Border-radius: radius-sm (4-6px depending on preset)

Block <pre><code>:

  • Font: JetBrains Mono
  • Size: 14 px (smaller than body to fit more lines)
  • Background: bg-inset token
  • Padding: 16-20 px depending on preset
  • Border-radius: radius-md
  • Overflow: scroll horizontal; never wrap

Syntax highlighting tokens come from themes/color-schemes.kmd Vocabulário de syntax (syntax_keyword, syntax_string, etc.).

R9 — Emphasized scale (Material 3 Expressive)

Material 3 Expressive ratifies a parallel 15-role emphasized scale: same role names as R1, with heavier weight and subtle tracking/leading adjustments. The emphasized scale exists alongside R1 (it does NOT replace it) and is opted into per-text-node when visual hierarchy needs more punch than weight-bump-within-role can provide.

R9.1 — Emphasized tokens

RoleWeight Δ vs R1Tracking ΔUse
display-large-emphasized+200 (400→600)-0.50Hero pages, splash, marketing landings
display-medium-emphasized+200-0.25Hero secondary
display-small-emphasized+2000Hero tertiary
headline-large-emphasized+100 (600→700)0Critical page titles, alert headers
headline-medium-emphasized+1000Important section opener
headline-small-emphasized+1000Card hero title
title-large-emphasized+1000Modal/dialog of high-priority
title-medium-emphasized+1000.10Primary CTA, app-bar title (Expressive variants)
title-small-emphasized+1000.05Featured tile title
body-large-emphasized+100 (400→500)0.40Featured paragraph (article lede)
body-medium-emphasized+1000.20Strong inline (legal/key info)
body-small-emphasized+1000.30Featured caption
label-large-emphasized+100 (500→600)0.10Primary action button (filled, Expressive)
label-medium-emphasized+1000.40Selected chip, active tab
label-small-emphasized+1000.40Active state indicator

Weight deltas use variable-font axes when available (preferred); discrete weights when not.

R9.2 — Variable-font axes (preferred)

When the active font is variable (Inter VF, JetBrains Mono VF), emphasized prefers axes over weight family swap:

AxisBaseline (R1)Emphasized (R9.1)
wght (weight)per R1per R1 + delta
opsz (optical size)role size pxrole size px (unchanged)
GRAD (grade)0+50 (subtle darkening without ascender shift)
wdth (width)100100 (unchanged — avoid auto-condensing)

Variable axes preserve x-height and metrics, preventing layout shift when toggling baseline↔emphasized at runtime.

R9.3 — Decision tree: when to use emphasized

Is this the SINGLE most important text on the screen?
├── YES, and it's a hero context (landing, splash)
│       → display-*-emphasized
├── YES, and it's an app context (page title, primary CTA)
│       → headline/title/label *-emphasized
└── NO  → does the section need visual lift above body?
          ├── YES (featured article lede, key paragraph)
          │       → body-*-emphasized (use sparingly: max 1 paragraph per screen)
          └── NO → use R1 baseline

Anti-pattern: every other text emphasized. Emphasized loses meaning when overused. Rule of thumb: at most 1 emphasized role per visual unit (card, section, dialog).

R9.4 — Accessibility

Emphasized does NOT replace semantic HTML/ARIA. A body-large- emphasized is still <p>, not <h2>. Screen readers MUST receive the same announcement as the baseline counterpart.

Emphasized text MUST still meet AAA contrast per R5/color-roles.kmd R4. Heavier weight does not loosen contrast requirements.

R9.5 — Surface bindings

SurfaceAPI
FlutterKoderType.of(context).headlineMediumEmphasized
ComposeMaterialTheme.typography.headlineMediumEmphasized
SwiftUI.font(.koderHeadlineMediumEmphasized)
WebCSS class kds-headline-medium-emphasized + --kds-font-headline-medium-emphasized-* vars

Surfaces MUST expose all 15 emphasized roles or none — partial implementation is a contract violation.

R9.6 — Per-preset variation

PresetEmphasized behavior
material3 / material_expressiveDefaults
terminal_classic / brutalistEmphasized = baseline (no delta) — preset already maxes weight
minimalist_monoEmphasized weight delta halved (+50 instead of +100/+200)
cyberpunk_neonEmphasized adds slight letter-spacing reduction (-0.10) for tighter glow
  • themes/color-schemes.kmd — syntax + body color tokens
  • themes/color-roles.kmd — text color roles
  • themes/ui-style.kmd — per-preset font stack
  • i18n/contract.kmd — locale-aware font loading
  • foundations/elements.kmd — content family uses type tokens

Referências