In a bare React Native project, what must you do so a bundled .ttf file can be used through the Text fontFamily style?
answer
- fonts are native resources, not JS
- Android: assets/fonts, file name is the family
- iOS: target resource plus UIAppFonts
- rebuild, a Metro reload is not enough
- unknown name falls back silently
basics
~10 sCopy the font into each native project: android/app/src/main/assets/fonts on Android, and the iOS app target plus a UIAppFonts entry in Info.plist. Then rebuild the native app and reference it by name in fontFamily.
solid answer
~40 sA custom font is a native resource, so a Metro reload never picks it up. On Android I copy the `.ttf` or `.otf` into `android/app/src/main/assets/fonts/`; React Native's font manager looks there by file name, so `BrandSans-Regular.ttf` is used as `fontFamily: 'BrandSans-Regular'`. On iOS I add the file to the app target so it is copied into the bundle, and list it under `UIAppFonts` in `Info.plist`; iOS then knows it by the family or PostScript name stored inside the file. Then I rebuild both apps. If a name is wrong nothing throws: the text silently renders in the system font. In an Expo project the `expo-font` config plugin makes the same native edits at prebuild.
code
xml · 7 lines<key>UIAppFonts</key>
<array>
<string>BrandSans-Regular.ttf</string>
<string>BrandSans-Medium.ttf</string>
<string>BrandSans-SemiBold.ttf</string>
<string>BrandSans-Bold.ttf</string>
</array>go deeper
Recall the two native steps: Android assets/fonts, and on iOS a target resource plus a UIAppFonts entry. Then say you rebuild the app, because fonts are not JavaScript.
Explain why the names differ: Android's asset lookup uses the file name, iOS uses names stored in the font. Renaming files to their PostScript names makes one string work everywhere.
Show how you keep this from regressing: automate the linking, verify the built Info.plist and assets folder, and treat a silent system-font fallback as a test failure, not a cosmetic nit.
Frame fonts as native build inputs: decide whether the team links them by hand, through a config plugin, or through a tool, and who owns the check that every platform build actually contains them.
## Why a custom font is a native resource In React Native, a `<Text>` is drawn by a native text view, and the typeface it uses is looked up by the platform's font system, not by JavaScript. The `fontFamily` style is only a string that React Native passes to that lookup. The font file therefore has to be inside the installed app, in the Android APK's assets or the iOS app bundle, before the string can resolve to anything. Two consequences are worth saying out loud in an interview: - **A Metro reload is not enough.** Fast Refresh swaps JavaScript; it never repackages native resources. Adding or renaming a font file means rebuilding and reinstalling the native app. - **A missing font fails silently.** React Native does not throw when a family is unknown. iOS draws with the system font at the requested size and weight; Android tries its asset lookup and then falls back to a system typeface. The screen simply looks slightly wrong, which is why typos survive code review. ## Android: the assets/fonts folder React Native's Android font manager, `ReactFontManager`, resolves a `fontFamily` string in this order: 1. A family registered from native code with `ReactFontManager.getInstance().addCustomFont(...)`, typically an XML font family under `res/font`. 2. A file in the APK's `assets/fonts/` folder whose base name equals the string, trying `.ttf` and then `.otf`. 3. Otherwise, a system typeface created from the name. So the bare-project step is to copy `BrandSans-Regular.ttf` into `android/app/src/main/assets/fonts/`. The **file name is the family name**: `fontFamily: 'BrandSans-Regular'`. The asset lookup also understands the suffixes `_bold`, `_italic` and `_bold_italic` for the four classic variants of one family, which matters as soon as you combine a custom family with `fontWeight`. ## iOS: bundle resource plus UIAppFonts iOS needs two separate things, and forgetting either one gives the same silent fallback: 1. The font file must be a **member of the app target**, so Xcode copies it into the bundle (it shows up in the target's Copy Bundle Resources phase). 2. The file name must be listed in `Info.plist` under the **`UIAppFonts`** key, which Xcode displays as "Fonts provided by application". iOS registers the listed files when the app launches. After that, iOS knows the font by the names stored **inside** the file: its family name (for example `BrandSans`) and each face's PostScript name (for example `BrandSans-Regular`). React Native accepts either. It first treats the string as a family; if no family matches, it tries it as a font name and recovers the family from it. ## Naming it in fontFamily | | Android (assets/fonts) | iOS (bundle + UIAppFonts) | |---|---|---| | Where the name comes from | the file name, without extension | metadata inside the font file | | Example string | `'BrandSans-Regular'` for `BrandSans-Regular.ttf` | `'BrandSans'` or `'BrandSans-Regular'` | | Unknown name | a system typeface | the system font | The practical rule is to **rename each file to its PostScript name** before copying it; then one `fontFamily` string works on both platforms. Note also that `fontFamily` takes exactly one name. Unlike CSS `font-family`, there is no comma-separated fallback list, so `'BrandSans, sans-serif'` is simply an unknown family. ## Formats worth choosing Ship static **TTF or OTF** files. React Native's Android asset lookup only tries those two extensions, and variable fonts do not have consistent support across platforms, so the usual advice is one static file per weight and style. If the design team hands over a variable font, extract the specific instances you need into separate files first. ## Automation, Expo and verification Hand-editing two native projects is error-prone, so teams usually automate it. In an Expo project the `expo-font` config plugin performs the same edits during prebuild: on iOS it adds the files to the target and to `UIAppFonts`; on Android it copies `.ttf` and `.otf` files into `assets/fonts`, or into `res/font` with a generated XML family when you describe weights. Community asset-linking tools do the copying for bare apps. Whichever route you take, check the result rather than trusting it: - open the built app's `Info.plist` and confirm every `UIAppFonts` entry matches a real file name; - confirm the files sit in `android/app/src/main/assets/fonts` in the Android project; - render a test string next to one in the system font on both platforms; a silent fallback is the usual symptom of a typo or a missed step; - rebuild after every font change, and do not debug a missing font with a JavaScript reload.
- Why does a newly added font not appear after reloading Metro or saving a file with Fast Refresh on?Fast Refresh and a Metro reload replace only the JavaScript bundle. React Native's native font lookup reads the APK's `assets/fonts` and the iOS bundle's registered `UIAppFonts`, which are packaged when the native app is built. A new or renamed font file therefore needs a native rebuild and reinstall before `fontFamily` can resolve it.
- What does React Native do at runtime when fontFamily names a font the app does not contain?Nothing visible to JavaScript: there is no red box or thrown error. On iOS React Native uses the system font at the requested size and weight. On Android the font manager tries `fonts/<name>.ttf` and `.otf` in the assets, then falls back to a system typeface. The text renders, just in the wrong face.
saying these in an interview costs you the question
- Dropping the font into a JavaScript assets folder and reloading Metro is enough.
- fontFamily accepts a CSS-style fallback list such as 'BrandSans, sans-serif'.
- A misspelled fontFamily throws a red-box error so you will notice it.
- On iOS the file only needs to be in the project folder, no Info.plist entry.
- On Android the file name does not matter because the family is read from inside the file.