#140·Valdi

Web renderer treats system font tokens as literal families, causing serif fallback

Author: clholgatCreated Aug 5, 2026Updated Aug 5, 2026

Summary

On web, Valdi's abstract system font names are emitted as literal CSS font-family values. For example, font: system 18 becomes font-family: system, and font: system-bold 18 becomes font-family: system-bold. Browsers do not recognize either family, so visible text can fall back to a serif face; bold text also reports normal weight.

The same TSX renders with the expected platform sans-serif font on native targets.

Minimal reproduction

typescript
import { Style } from 'valdi_core/src/Style';
import { systemBoldFont, systemFont } from 'valdi_core/src/SystemFont';
import { Label } from 'valdi_tsx/src/NativeTemplateElements';

const regular = new Style<Label>({ font: systemFont(18) });
const bold = new Style<Label>({ font: systemBoldFont(18) });

<label style={regular} value="Regular system text" />;
<label style={bold} value="Bold system text" />;

In Chromium, getComputedStyle() reports system and system-bold as the font families. Depending on installed fonts and browser fallback behavior, the rendered face is serif. The system-bold example also remains weight 400 because bold is encoded in the unresolved family name rather than CSS font weight.

Cause

web_renderer/src/styles/ValdiWebStyles.ts splits the Valdi font string and assigns the first token directly to fontFamily. Attributed text and text-field paths perform similar literal assignment. The default web label may start with sans-serif, but an explicit Valdi system font overwrites that safe default.

Polyglot/custom web views are a related boundary: browser controls created inside them do not necessarily inherit the normal Valdi label defaults. They need either inheritance from a resolved container font or an explicit system stack.

Expected behavior

  • system resolves to the browser's platform sans-serif stack.
  • system-bold resolves to the same stack with bold weight.
  • Normal labels, attributed text, text fields, and text views resolve these names consistently.
  • Custom views can inherit the resolved font where practical; framework widgets that create their own text controls should use the same mapping.

Proposed fix

Add one shared web font resolver used by every renderer path:

typescript
const WEB_SYSTEM_FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';

function resolveWebFont(name: string): { family: string; weight?: string } {
  if (name === 'system') return { family: WEB_SYSTEM_FONT };
  if (name === 'system-bold') return { family: WEB_SYSTEM_FONT, weight: 'bold' };
  return { family: name };
}

Use it in ValdiWebStyles, attributed-text rendering, WebValdiTextField, and WebValdiTextView. Add browser tests asserting both computed family and weight. This keeps the abstract Valdi font contract intact and avoids application-level global CSS, which would not cover native targets or isolated custom-view internals.

Compatibility impact

This changes web rendering only for the reserved Valdi names system and system-bold, bringing web behavior in line with native behavior. Explicit custom font families remain unchanged.