Fonts

Load a font and point five tokens at it. Every component in the library follows.

Changing the font is two steps: load the faces, then define five tokens. There is no provider to configure and no prop to thread — every component already writes the classes those tokens attach to.

1. Load the faces

Any loader works. With Expo:

npx expo install expo-font @expo-google-fonts/inter
app/_layout.tsx
import { useFonts } from 'expo-font';
import {
  Inter_400Regular,
  Inter_500Medium,
  Inter_600SemiBold,
  Inter_700Bold,
} from '@expo-google-fonts/inter';
import { PanelUIProvider } from 'panelui-native';

export default function Layout() {
  const [loaded] = useFonts({
    Inter_400Regular,
    Inter_500Medium,
    Inter_600SemiBold,
    Inter_700Bold,
  });

  if (!loaded) return null;

  return <PanelUIProvider>{/* … */}</PanelUIProvider>;
}

The keys you pass to useFonts are the names the platform will know the faces by. Those exact strings go into the tokens below.

2. Point the tokens at them

global.css
@import 'tailwindcss';
@import 'uniwind';
@import 'panelui-native/theme.css';

@source './node_modules/panelui-native/src';

@theme {
  --font-normal: Inter_400Regular;
  --font-medium: Inter_500Medium;
  --font-semibold: Inter_600SemiBold;
  --font-bold: Inter_700Bold;
}

Restart the dev server, and that is the whole change. Every heading, label, button, field and table cell in the library is now set in Inter.

TokenThe class it takes overWhere it lands
--font-normalfont-normalBody text, descriptions, fields — most of the library
--font-mediumfont-mediumLabels, table headers, the number in a NumberInput
--font-semiboldfont-semiboldHeadings, button labels, dialog titles
--font-boldfont-boldTypography h1, and anything asking for font-bold
--font-monofont-monoCodeBlock, inline code, file paths

You do not have to define all five. Leave --font-bold out and font-bold keeps meaning weight 700 in whatever the system font is — useful if your family has no bold face.

Why the weight becomes the family

Setting --font-medium does not make font-medium mean "Inter at weight 500". It makes it mean font-family: Inter_500Medium, and the font-weight: 500 it used to set goes away entirely.

That is deliberate, and it is the shape native platforms actually want. On the web a browser picks a face out of one family by weight. On iOS and Android a weight is a separately registered faceInter_500Medium and Inter_700Bold are two different fonts as far as the platform is concerned, and asking for fontWeight: 700 on a family registered as a single face gets you a synthesised smear on one platform and nothing at all on the other.

So the token carries the face, and the class that used to name a weight now names the file that is that weight. Nothing in your code changes: font-semibold still means "the bold-ish one".

This is also why the library ships no --font-* values of its own for the four weights. If it declared them, font-semibold would resolve to a font family on every install — including for apps that never wanted a custom font, which would silently lose every bold string in the UI. Undefined tokens are what keep the weights working by default.

Monospace

--font-mono is the one weight-less token, and unlike the others the library does give it a value: Menlo on iOS and monospace on Android.

It has to. Tailwind's default --font-mono is a stack of eight families separated by commas, and React Native takes fontFamily as a single name — the whole list arrives at the platform as one family that does not exist, and code quietly renders in the system font. Override it with one real family name, never a stack:

@theme {
  --font-mono: JetBrainsMono_400Regular;
}

One font for one theme

The tokens are ordinary theme variables, so they take a @variant block like any other. A brand theme can bring its own typeface while the default theme keeps the system font:

global.css
@layer theme {
  :root {
    @variant grass {
      --font-normal: Nunito_400Regular;
      --font-semibold: Nunito_600SemiBold;
    }
    @variant grass-dark {
      --font-normal: Nunito_400Regular;
      --font-semibold: Nunito_600SemiBold;
    }
  }
}

Uniwind requires every theme to define the same variables, so a token scoped this way has to appear in all six blocks — see Theming.

Setting a font on one component

The tokens are global by design. For a single element, put the family on it directly:

<Typography type="h1" style={{ fontFamily: 'PlayfairDisplay_700Bold' }}>
  Reading list
</Typography>

style wins over className, so this works even where a weight class is already in play. See Styling.

Troubleshooting

Nothing changed. The dev server has to be restarted, not reloaded — a running one rewrites its generated CSS from the theme it started with. Stop it and run npx expo start -c.

The text is there but the font is the old one. The token value has to match the name the platform registered, which is the key you passed to useFonts — not the file name, not the family name from the font's metadata. Inter_600SemiBold, not Inter and not Inter-SemiBold.ttf.

Bold disappeared. Expected, if you defined --font-normal but not --font-bold: font-bold now resolves to a family, finds none, and falls back. Define all the weights you use, or none.

One component still uses the system font. Something is drawing text without a weight class. Every component in the library carries one; if you find one that does not, that is a bug worth reporting.

Next

On this page