Fonts

#Default fonts

Every app starts with a UI font and a monospace font from the theme:

RoleFamilySize
UI text.SystemUIFont16px
Code / monospacemacOS: Menlo, Windows: Consolas, Linux: DejaVu Sans Mono13px

The editor paints its code in mono_font_family at mono_font_size. See Editor for details.

Both defaults are checked against the installed fonts when the theme is applied. A missing monospace default is swapped for an installed alternative, and when .SystemUIFont resolves to one of GPUI’s fallback families rather than the system font itself (Linux desktops without the family GPUI maps it to), the theme names that family directly so text lookups stay cached. A family you set yourself is used as-is.

#System fonts

Desktop apps can use any font installed on the OS by name — no bundling, no config. GPUI resolves the family live against the system collection (CoreText on macOS, DirectWrite on Windows, fontconfig on Linux).

div().font_family("Segoe UI")

Editor::new(&editor).font_family("JetBrains Mono")

Common examples per platform:

  • macOS: SF Pro, Helvetica, Arial, Times New Roman, Menlo, Monaco
  • Windows: Segoe UI, Arial, Consolas, Courier New
  • Linux: Noto Sans, DejaVu Sans, Liberation Sans, DejaVu Sans Mono

If the name does not match an installed font, GPUI falls back silently — so verify the exact family name on each target platform.

#Changing fonts via Theme

Set the app-wide fonts through Theme::update, which syncs the base layer and refreshes every window:

Theme::update(cx, |theme| {
    theme.font_family = "Inter".into();
    theme.mono_font_family = "JetBrains Mono".into();
    theme.font_size = px(18.);
});

font_size doubles as the application zoom control — Root calls window.set_rem_size(cx.theme().font_size), so rem-based spacing scales with it. See Coding Guides for details.

#Per-element override

Any element accepts a font override without touching the theme:

div()
    .font_family("JetBrains Mono")
    .text_size(px(15.))
    .font_weight(FontWeight::BOLD)

These are ordinary Styled methods, so they compose with the rest of the style chain.

#Bundling custom fonts

Fonts that are not installed on the user’s system must be bundled and registered with the text system before the first frame:

cx.text_system()
    .add_fonts(vec![Cow::Borrowed(
        include_bytes!("../fonts/MyFont-Regular.ttf").as_slice(),
    )])
    .expect("Failed to load fonts");

Then reference them by family name as usual:

Theme::update(cx, |theme| theme.font_family = "MyFont".into());

The gallery’s web build bundles Inter, JetBrains Mono, Noto Sans SC and IBM Plex Sans this way — see crates/story-web/src/lib.rs.

#Theme JSON config

Font families and sizes can also come from a theme file:

{
    "font.family": "Inter",
    "font.size": 16,
    "mono_font.family": "JetBrains Mono",
    "mono_font.size": 13
}

Load it with ThemeRegistry:

ThemeRegistry::watch_dir(PathBuf::from("./themes"), cx, move |cx| {
    if let Some(theme) = ThemeRegistry::global(cx).themes().get(&theme_name).cloned() {
        Theme::update(cx, |current| current.apply_config(&theme));
    }
});

See Theme for the full config reference.

#WebAssembly note

Browsers expose no system fonts to WASM apps. The story-web gallery (which runs at gpui-kit.com/gallery/) must bundle every family it uses and re-assert them after Theme::change, or the text system panics. Desktop apps skip this entirely.

Text the bundled fonts cannot draw can still come from the browser. The web platform renders emoji through Canvas 2D with the visitor’s local fonts, so an application does not have to bundle an emoji font. The policy is chosen when the platform is constructed and cannot change afterwards:

CanvasFontFallbackBrowser draws
Emoji (default)Emoji, including skin tones, flags, keycaps and ZWJ sequences
EmojiAndCjkEmoji plus horizontal Han, kana and modern Hangul text
DisabledNothing; only bundled fonts are used

gpui_kit::application() and gpui_kit::platform::single_threaded_web() keep the default. To widen it, build the platform yourself:

use gpui_kit::web::{CanvasFontFallback, WebBackendPreference, WebPlatform};

let platform = Rc::new(WebPlatform::new_with_backend_and_font_fallback(
    false,
    WebBackendPreference::Auto,
    CanvasFontFallback::EmojiAndCjk,
));
let http_client = Arc::new(platform.fetch_http_client());
let app = Application::with_platform(platform).with_http_client(http_client);

Bundled fonts stay preferred wherever they have the glyph. The fallback draws each grapheme on its own, so CJK text rendered this way favors readability over exact spacing and font features, and its appearance depends on the fonts installed on the visitor’s machine. The gallery opts into EmojiAndCjk: its bundled fonts hold only the glyphs its own stories use, and anything a visitor types into an input would otherwise render as tofu.