Fonts

Define font families with createFont and load custom fonts on native.

Fonts are the one token category with structure. Each family carries its own size, line height, weight, and letter spacing scales.

Create one with createFont and pass it to createTamagui under fonts:

tamagui.config.ts

import { createFont, createTamagui, isWeb } from 'tamagui'
const interFont = createFont({
family: isWeb ? 'Inter, Helvetica, Arial, sans-serif' : 'Inter',
size: {
1: 12,
2: 14,
3: 15,
// ...
},
lineHeight: {
1: 17,
2: 22,
3: 25,
// ...
},
weight: {
4: '300',
6: '600',
},
letterSpacing: {
4: 0,
8: -1,
},
// android needs the weight mapped to the font file's family name
// find it with: otfinfo --family Inter.ttf
face: {
400: { normal: 'Inter', italic: 'Inter-Italic' },
500: { normal: 'InterBold', italic: 'InterBold-Italic' },
},
})
export const config = createTamagui({
fonts: {
heading: interFont,
body: interFont,
},
// ...
})

Keys can be any string. The tamagui components expect number keys 1 through 10 on size, so headings and sized text resolve automatically.

createFont fills gaps for you. When lineHeight, weight, or letterSpacing define fewer keys than size, each missing key carries the previous value forward, and keys before the first one you define take that first value:

const systemFont = createFont({
family: isWeb ? 'Helvetica, Arial, sans-serif' : 'System',
size: { 1: 12, 2: 14, 3: 15 },
lineHeight: { 2: 22 }, // 1 becomes 22
weight: { 1: '300', 3: '600' }, // 2 becomes 300
letterSpacing: { 1: 0, 2: -1 }, // 3 becomes -1
})

Set settings.defaultFont to the key of the family that text should use when no fontFamily is given. See Settings.

Face

On Android, React Native picks a font file by name and ignores fontWeight. The face map tells Tamagui which file name to use for each weight and style. Without it, every weight renders with the same file. iOS and web ignore face.

Loading fonts on native

React Native has to load custom font files before it can use them. Tamagui does not handle loading. With Expo, use useFonts from expo-font and gate rendering on the result. The Expo guide walks through both @tamagui/font-inter and @expo-google-fonts. Without Expo, follow the React Native docs for linking font assets.

The name you load must match the name used in family and in face:

App.tsx

import { useFonts } from 'expo-font'
function App() {
const [loaded] = useFonts({
Inter: require('@tamagui/font-inter/otf/Inter-Medium.otf'),
InterBold: require('@tamagui/font-inter/otf/Inter-Bold.otf'),
})
if (!loaded) {
return null
}
return <MyApp />
}

Using font tokens

Text components read a family with fontFamily. Style props such as fontSize and lineHeight accept a key from that family’s scale. SizableText, Paragraph, and the headings take a size prop that picks fontSize, lineHeight, weight, and letterSpacing from the family together:

<Text fontFamily="body" fontSize="3" lineHeight="3">
Hello world
</Text>
<SizableText fontFamily="heading" size="3">
Hello world
</SizableText>

Per-language fonts

A font name with a _ suffix is a language variant of the base family. Define body_cn next to body and load both font files:

import { createFont, createTamagui } from 'tamagui'
export default createTamagui({
fonts: {
body: createFont({
family: 'Inter, Helvetica, Arial, sans-serif',
// ...
}),
body_cn: createFont({
family: 'Inter-CN',
// ...
}),
},
})

FontLanguage then switches a family for a whole subtree. Every component reading fontFamily="body" inside it picks up the variant, with no prop changes on the text itself:

import { FontLanguage, html } from 'tamagui'
export default () => (
<FontLanguage body="cn">
<html.span fontFamily="body">你好</html.span>
</FontLanguage>
)

The suffix name is yours to choose. The reserved value default resets a family to its base font:

<FontLanguage body="cn">
<html.span fontFamily="body">你好</html.span>
<FontLanguage body="default">
<html.span fontFamily="body">Hello</html.span>
</FontLanguage>
</FontLanguage>

On web, FontLanguage swaps a CSS variable, so changing language does not re-render the text.