1---
2title: Fonts
3description: Learn about using custom fonts, supported font formats for each platform and loading them.
4---
5
6import { YesIcon, NoIcon } from '~/ui/components/DocIcons';
7import { Terminal, SnackInline } from '~/ui/components/Snippet';
8import ImageSpotlight from '~/components/plugins/ImageSpotlight';
9import { BoxLink } from '~/ui/components/BoxLink';
10import { BookOpen02Icon } from '@expo/styleguide-icons';
11
12Both Android and iOS and most desktop operating systems come with their own set of platform fonts. However, if you want to inject some more brand personality into your app, a well-picked font can go a long way.
13
14As each operating system has its own set of platform fonts, if you want to produce an experience that is consistent for all users, you'll want to use your fonts in your project. This page covers the aspects of getting a custom font, loading it in your project and what are some of the best practices to use when the font is being loaded in your project.
15
16## Get a font
17
18The first thing you need is a font file. For a working example, we are going to use Inter Black from the free and open source [Inter font family](https://rsms.me/inter/) by Rasmus Anderson. A common convention in React Native apps is to put your fonts in an **./assets/fonts** directory. However, you can put them anywhere you like.
19
20### Supported font formats
21
22The two officially supported font formats that work consistently in the Expo SDK across Android, iOS and the web, are OTF and TTF. If your font is in another format, you will require to set up an [advanced configuration](#beyond-otf-and-ttf) for your project.
23
24If you have both OTF and TTF versions of a font, prefer OTF. OTF is a newer format and **.otf** files are often smaller than **.ttf** files. Sometimes OTF files render slightly better in certain contexts. In general, both formats are very similar and perfectly acceptable.
25
26### Beyond OTF and TTF
27
28If your font is in another format, you have to [customize the Metro bundler configuration](/guides/customizing-metro#adding-more-file-extensions-to-assetexts) to get anything other than OTF and TTF to work. In some cases, trying to render a font format that a platform doesn't support may cause your app to crash.
29
30For reference, the following table provides what formats work on which platforms:
31
32| Format | Web         | iOS         | Android     |
33| ------ | ----------- | ----------- | ----------- |
34| bdf    | <NoIcon />  | <NoIcon />  | <NoIcon />  |
35| dfont  | <NoIcon />  | <NoIcon />  | <YesIcon /> |
36| eot    | <YesIcon /> | <NoIcon />  | <NoIcon />  |
37| fon    | <NoIcon />  | <NoIcon />  | <NoIcon />  |
38| otf    | <YesIcon /> | <YesIcon /> | <YesIcon /> |
39| ps     | <NoIcon />  | <NoIcon />  | <NoIcon />  |
40| svg    | <YesIcon /> | <NoIcon />  | <NoIcon />  |
41| ttc    | <NoIcon />  | <NoIcon />  | <NoIcon />  |
42| ttf    | <YesIcon /> | <YesIcon /> | <YesIcon /> |
43| woff   | <YesIcon /> | <YesIcon /> | <NoIcon />  |
44| woff2  | <YesIcon /> | <YesIcon /> | <NoIcon />  |
45
46## Use a custom font
47
48After getting the font file, in your project, you need to install [`expo-font`](/versions/latest/sdk/font/#installation) package.
49
50### Import the font
51
52After the installation step, import the `useFonts` hook from `expo-font` package in your project. The hook keeps track of the loading state of the font. When an app is initialized, the hook loads the map of fonts as shown in the example below:
53
54```jsx App.js
55// Rest of the import statements
56import { useFonts } from 'expo-font';
57
58export default function App() {
59  const [fontsLoaded] = useFonts({
60    'Inter-Black': require('./assets/fonts/Inter-Black.otf'),
61  });
62}
63```
64
65Then, you can use the font on the `<Text>` by using `fontFamily` style prop.
66
67```jsx
68<Text style={{ fontFamily: 'Inter-Black', fontSize: 30 }}>Inter Black</Text>
69```
70
71Alternatively, you can use [`Font.loadAsync`](#use-fontloadasync-instead-of-the-usefonts-hook) to load the fonts in your app.
72
73### Minimal example
74
75Let's take a look at a minimal example that uses Inter font family. It uses [`useFonts` hook](/versions/latest/sdk/font/#usefonts) to import the font from **./assets/fonts** directory.
76
77<SnackInline label="Using custom fonts" dependencies={['expo-font', 'expo-splash-screen']} files={{ 'assets/fonts/Inter-Black.otf': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/44b1541a96341780b29112665c66ac67' }}>
78
79```jsx
80import { useCallback } from 'react';
81import { Text, View, StyleSheet } from 'react-native';
82/* @info Import useFonts hook from 'expo-font'. */ import { useFonts } from 'expo-font'; /* @end */
83/* @info Also, import SplashScreen so that when the fonts are not loaded, we can continue to show SplashScreen. */ import * as SplashScreen from 'expo-splash-screen'; /* @end */
84
85/* @info This prevents SplashScreen from auto hiding while the fonts are loaded. */
86SplashScreen.preventAutoHideAsync();
87/* @end */
88
89export default function App() {
90  const [fontsLoaded, fontError] = useFonts({
91    'Inter-Black': require('./assets/fonts/Inter-Black.otf'),
92  });
93
94  /* @info After the custom fonts have loaded, we can hide the splash screen and display the app screen. */
95  const onLayoutRootView = useCallback(async () => {
96    if (fontsLoaded || fontError) {
97      await SplashScreen.hideAsync();
98    }
99  }, [fontsLoaded, fontError]);
100  /* @end */
101
102  if (!fontsLoaded && !fontError) {
103    return null;
104  }
105
106  return (
107    <View style={styles.container} onLayout={onLayoutRootView}>
108      <Text style={{ fontFamily: 'Inter-Black', fontSize: 30 }}>Inter Black</Text>
109      <Text style={{ fontSize: 30 }}>Platform Default</Text>
110    </View>
111  );
112}
113
114/* @hide const styles = StyleSheet.create({ ... }); */
115const styles = StyleSheet.create({
116  container: {
117    flex: 1,
118    justifyContent: 'center',
119    alignItems: 'center',
120  },
121});
122/* @end */
123```
124
125</SnackInline>
126
127Inter Black is very bold and dark and pretty distinctive so you should be able to tell if you're able to get the example working right, or if something is wrong. If the platform default font looks a little different for you, that's fine; the platform default font can vary depending on the operating system and the device manufacturer (on Android).
128
129When you load it on your device, you'll see something like this:
130
131<ImageSpotlight
132  alt="Enter a name of your new organization."
133  src="/static/images/font-example-custom-font.jpg"
134  style={{ maxWidth: 305 }}
135/>
136
137To create a new project including this example, run in your terminal:
138
139<Terminal cmd={['$ npx create-expo-app --example with-custom-font']} />
140
141> The above example also uses [`expo-splash-screen`](/versions/latest/sdk/splash-screen/) package. For more information on that, see [Waiting for fonts to load](#wait-for-fonts-to-load) section.
142
143## Platform built-in fonts
144
145If you don't want to use a custom font, you can use the platform's default font by not specifying a font family. Each platform has a different set of fonts available by default, so there's no good way to specify one that will work everywhere without supplying your custom font.
146
147On the web, there are several generic font families that you can specify. Different browsers and operating systems are configured to use different fonts for each of these font family specifications. For example, Safari on an iPhone uses San Francisco as its default for `sans-serif` while Microsoft Edge on Windows uses Arial. Similarly, Chrome on Android uses Roboto, though OnePlus phones often use Slate, and so on.
148
149- `sans-serif`
150- `serif`
151- `monospace`
152- `fantasy`
153- `cursive`
154
155In general, your safest bets are just to use the system default which usually is an easy-to-read sans-serif font that the user of any system should be familiar with. However, don't be surprised when the system default font is changed to use another font that is not easy to read but at the same time, is supported on the platform or the device. In this case, use your custom font so you have precise control over what the user will see.
156
157## Use a Google Font
158
159Expo has first-class support for all fonts listed in [Google Fonts](https://fonts.google.com/). To use one of these, check out the [`expo-google-fonts`](https://github.com/expo/google-fonts) package. With these packages, you can quickly integrate any font or font variants.
160
161For example, to use Inter font you can install the [`@expo-google-fonts/inter`](https://www.npmjs.com/package/@expo-google-fonts/inter) package with the command below.
162
163<Terminal cmd={['$ npx expo install expo-font @expo-google-fonts/inter']} />
164
165Then, you can integrate it in your project by using the `useFonts` hook. You can directly use this hook from the Google Fonts package. Under the hood, the hook uses [`Font.loadAsync`](/versions/latest/sdk/font/#loadasyncfontfamilyorfontmap-source). You do not have to explicitly import the font file since that is done by the package itself.
166
167<SnackInline label="Using Google fonts" dependencies={['@expo-google-fonts/inter']}>
168
169```jsx
170import React from 'react';
171import { View, Text, StyleSheet } from 'react-native';
172import { useFonts, Inter_900Black } from '@expo-google-fonts/inter';
173
174export default function App() {
175  let [fontsLoaded, fontError] = useFonts({
176    Inter_900Black,
177  });
178
179  if (!fontsLoaded && !fontError) {
180    return null;
181  }
182
183  return (
184    <View style={styles.container}>
185      <Text style={{ fontFamily: 'Inter_900Black', fontSize: 40 }}>Inter Black</Text>
186    </View>
187  );
188}
189
190/* @hide const styles = StyleSheet.create({ ... }); */
191const styles = StyleSheet.create({
192  container: {
193    flex: 1,
194    justifyContent: 'center',
195    alignItems: 'center',
196  },
197});
198/* @end */
199```
200
201</SnackInline>
202
203## Wait for fonts to load
204
205Since your fonts won't be ready right away, it is generally a good practice to not render anything until the font is ready. Instead, you can continue to display the Splash Screen of your app until all fonts have loaded (or an error has been returned). It is done by using [`expo-splash-screen`](/versions/latest/sdk/splash-screen/) package. See the [minimal example](#minimal-example) section on how to use it.
206
207### Load fonts on the web
208
209Sometimes, particularly on the web -- people choose to render their content in a platform default font while their custom font is loading. Alternatively, to render the rest of their content, that doesn't depend on the custom font while the font is loading. These approaches are called FOUT and FOIT and you can read a lot more about them on the web.
210
211In general, these strategies are not recommended for native apps. If you include your fonts in your project, the
212fonts will always be delivered to the user by the time your code is running. The one exception to this is that you may prefer to do this on the web.
213
214## Additional information
215
216You probably don't need to know anything beyond this point to use custom fonts effectively in your app. If you are curious or your use case has not been addressed by the above information, please continue reading.
217
218### Load a remote font directly from the web
219
220In general, it's best and safest to load fonts from your local assets. If you submit to app stores, they will be bundled with the download and available immediately. You don't have to worry about CORS or other potential issues.
221
222However, if you to load a remote font file directly from the web rather than from your project's assets, you can do it by replacing the `require('./assets/fonts/MyFont.otf')` with the URL of your font. See the below example:
223
224<SnackInline label="Using a remote font" dependencies={['expo-font']}>
225
226```jsx
227import React from 'react';
228import { Text, View, StyleSheet } from 'react-native';
229import { useFonts } from 'expo-font';
230
231export default function App() {
232  const [fontsLoaded] = useFonts({
233    'Inter-SemiBoldItalic': 'https://rsms.me/inter/font-files/Inter-SemiBoldItalic.otf?v=3.12',
234  });
235
236  if (!fontsLoaded) {
237    return null;
238  }
239
240  return (
241    <View style={styles.container}>
242      <Text style={{ fontFamily: 'Inter-SemiBoldItalic', fontSize: 30 }}>Inter SemiBoldItalic</Text>
243      <Text style={{ fontSize: 30 }}>Platform Default</Text>
244    </View>
245  );
246}
247/* @hide const styles = StyleSheet.create({ ... }); */
248const styles = StyleSheet.create({
249  container: {
250    flex: 1,
251    justifyContent: 'center',
252    alignItems: 'center',
253  },
254});
255/* @end */
256```
257
258</SnackInline>
259
260> **warning** **If loading remote fonts, make sure they are being served from an origin with CORS properly configured**. If you don't do this, your remote font might not load properly on the web platform.
261
262### Use `Font.loadAsync` instead of the `useFonts` hook
263
264If you don't want to use the `useFonts` hook (for example, maybe you prefer class components), you can use `Font.loadAsync` directly. Under the hood, the hook uses `Font.loadAsync` from the [`expo-font`](/versions/latest/sdk/font/) library. You can use it directly if you prefer, or if you want to have more fine-grained control over when your fonts are loaded before rendering.
265
266<SnackInline label="Loading font async" dependencies={['expo-font']} files={{
267  'assets/fonts/Inter-Black.otf': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/44b1541a96341780b29112665c66ac67'
268}}>
269
270```jsx
271import React from 'react';
272import { Text, View, StyleSheet } from 'react-native';
273import * as Font from 'expo-font';
274
275let customFonts = {
276  'Inter-Black': require('./assets/fonts/Inter-Black.otf'),
277  'Inter-SemiBoldItalic': 'https://rsms.me/inter/font-files/Inter-SemiBoldItalic.otf?v=3.12',
278};
279
280export default class App extends React.Component {
281  state = {
282    fontsLoaded: false,
283  };
284
285  async _loadFontsAsync() {
286    await Font.loadAsync(customFonts);
287    this.setState({ fontsLoaded: true });
288  }
289
290  componentDidMount() {
291    this._loadFontsAsync();
292  }
293
294  render() {
295    if (!this.state.fontsLoaded) {
296      return null;
297    }
298
299    return (
300      <View style={styles.container}>
301        <Text style={{ fontFamily: 'Inter-Black', fontSize: 30 }}>Inter Black</Text>
302        <Text style={{ fontFamily: 'Inter-SemiBoldItalic', fontSize: 30 }}>
303          Inter SemiBoldItalic
304        </Text>
305        <Text style={{ fontSize: 30 }}>Platform Default</Text>
306      </View>
307    );
308  }
309}
310
311/* @hide const styles = StyleSheet.create({ ... }); */
312const styles = StyleSheet.create({
313  container: {
314    flex: 1,
315    justifyContent: 'center',
316    alignItems: 'center',
317  },
318});
319/* @end */
320```
321
322</SnackInline>
323
324## Next step
325
326<BoxLink
327  title="Color themes"
328  description="Learn more about supporting light and dark modes in your app."
329  href="/develop/user-interface/color-themes"
330  Icon={BookOpen02Icon}
331/>
332