feat: replace Geist with Inter as the default typeface - #1489
Conversation
Swaps the default font family from Geist to Inter across design tokens, the Tailwind and twrnc presets, and both Storybook apps. The six Inter cuts map one-to-one onto the Geist cuts they replace (regular/medium/semibold, each with an italic), so no font weight, size, line height, or letter spacing token changes. Semantic bold stays at 600. Font binaries are built from the Inter 18pt static family. Their internal name tables are normalized from the upstream `Inter18pt-Regular` style PostScript names to `Inter-Regular`, matching the convention the previous Geist files used, because the twrnc preset and iOS both resolve fonts by PostScript name. React Native now loads .ttf directly rather than .otf; Metro's default assetExts already covers it. BREAKING CHANGE: `--font-family-default` and `fontFamilies.default` are now Inter, and the twrnc font names changed from `Geist-*` to `Inter-*`. Consumers must bundle the Inter font files. See MIGRATION.md. Co-authored-by: Cursor <cursoragent@cursor.com>
📖 Storybook LinksCompare the preview for this pull request with the latest Storybooks from the 🔀 Pull Request Preview
🌳 Main Branch (Latest)
|
|
@metamaskbot publish-preview |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
|
@georgewrmarshall Not sure if we have a strong preference between TTF/OTF — there are disadvantages + advantages to both. |
The Inter binaries kept upstream's family grouping in name ID 1, so each cut's family name differed from the PostScript name the twrnc preset passes as `fontFamily`. The upright and italic cuts also shared a family (`Inter SemiBold`), leaving iOS two candidate faces for one family lookup. Family (1) and full name (4) are now the PostScript name, and subfamily (2) is `Regular`, making each cut a standalone single-style family. Name IDs 16/17 are dropped because Apple platforms report name ID 16 as the family when it is present, which would otherwise keep the family `Inter`. Only the `name` and `head` tables change; glyphs, cmap, units per em, italic angle, and vertical metrics are byte-identical, so there is no reflow beyond the Geist-to-Inter swap. The italic cuts keep their italic angle and flags, and `Text` selects italics by family rather than `fontStyle`, so nothing relies on style linking. MIGRATION.md guidance is updated, since its fontTools snippet rewrote only the PostScript name and would reproduce the mismatch. Co-authored-by: Cursor <cursoragent@cursor.com>
📖 Storybook LinksCompare the preview for this pull request with the latest Storybooks from the 🔀 Pull Request Preview
🌳 Main Branch (Latest)
|
|
@metamaskbot publish-preview |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
georgewrmarshall
left a comment
There was a problem hiding this comment.
Reviewed the Geist → Inter swap with extra attention on React Native PostScript names versus metamask-mobile#35982 (preview/inter-postscript-fix, commit c4f94ca3 preview packages).
Lookup names are aligned. I dumped the shipped TTF name tables in apps/storybook-react-native/fonts/Inter and compared them to Mobile's app/fonts/Inter-*.ttf, plus Mobile's getFontFamily() and fontStyles.
| Cut | MMDS name ID 1 / 6 | twrnc fontFamily |
Mobile getFontFamily / fontStyles |
|---|---|---|---|
| Regular | Inter-Regular |
default-regular |
Inter-Regular |
| Regular italic | Inter-RegularItalic |
default-regular-italic |
Inter-${suffix}Italic → Inter-RegularItalic |
| Medium | Inter-Medium |
default-medium |
Inter-Medium |
| Medium italic | Inter-MediumItalic |
default-medium-italic |
Inter-MediumItalic |
| Bold (600) | Inter-SemiBold |
default-bold |
weight 600/700/bold → Inter-SemiBold; fontStyles.bold same |
| Bold italic | Inter-SemiBoldItalic |
default-bold-italic |
Inter-SemiBoldItalic |
The second commit's standalone-family rewrite is doing the important work: name ID 1 equals name ID 6, name ID 2 is Regular on the MMDS files, and typographic family IDs 16/17 are gone. RN Text still selects italics via font-default-*-italic (family), not fontStyle, which matches that model. Web @font-face correctly keeps a single 'Inter' family plus weight/style, which is the right split from RN.
The binaries are not the same files as Mobile's, and a few name-table details still diverge. That is worth tightening so Storybook iOS and the app are validating the same recipe:
- MMDS still has name ID 25 (
Interon uprights,InterItalicon italics) plus STAT name IDs 256+. Mobile dropped 25 (and the 256+ names). Mobile's rewrite exists specifically so nothing can regroup faces under anInter/Inter 18ptumbrella. - Italic name ID 2 is
Regularhere andItalicon Mobile. RN lookup should not care, but the two rewrite scripts should converge before these files are treated as shared assets. - Unique name ID 3 is
Inter-Regular; MMDShere vs4.001;RSMS;Inter-Regularon Mobile. Harmless for resolution.
Token/docs side looks complete: CSS/JS/Figma fontFamilies.default, Storybook loaders, and MIGRATION notes. Leftover Geist in historical changelog/migration sections is expected. Breaking-change docs in MIGRATION.md (tokens 10.x → 11.0.0) are the right place given changelog generation on release branches.
I did not run Storybook iOS/Android myself. Worth confirming italic + semibold on a native client after dropping ID 25, since a wrong family still fails closed to the system font.
Replace the RN Inter binaries with the MetaMask Mobile PostScript-fixed files so iOS lookup matches the app, and tell consumers to copy those Storybook assets instead of rewriting name tables. Co-authored-by: Cursor <cursoragent@cursor.com>
📖 Storybook LinksCompare the preview for this pull request with the latest Storybooks from the 🔀 Pull Request Preview
🌳 Main Branch (Latest)
|
|
@metamaskbot publish-preview |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
There was a problem hiding this comment.
LGTM! 🚀
Follow-up review against Tailwind, twrnc, Metro, and the Expo fonts guide (https://docs.expo.dev/develop/user-interface/fonts/). The loading model looks correct after the Mobile TTF swap.
Tailwind (web): --font-family-default is 'Inter' and Storybook @font-face rules use one family plus weight 400/500/600 and italic. That matches CSS font matching. No PostScript names on web.
twrnc + RN Text: font-default-* maps to Inter-Regular / Inter-RegularItalic / Inter-Medium / Inter-SemiBold. Text still selects italics by family (font-default-*-italic), not fontStyle. That is the right RN model and matches useFonts keys and filenames.
Metro: Expo's default assetExts already includes ttf. apps/storybook-react-native/metro.config.js only strips svg for the transformer. No Metro change is required for this swap (unlike woff, which would need an extra ext).
Expo: TTF is supported on Android, iOS, and web. Filenames match PostScript names (Inter-Regular.ttf → Inter-Regular), which is Expo's recommendation so Android (filename) and iOS (name table) agree. Storybook loads via useFonts + require(), which is the documented runtime path and works without the expo-font config plugin. Do not use @expo-google-fonts/inter here: those files are Inter_400Regular and would not match twrnc.
Consumers who embed with the expo-font plugin should pass the file-path array (per-face families), not the object form that registers one 'Inter' family plus fontWeight. The latter is in Expo's Inter example and would miss twrnc's fontFamily: 'Inter-SemiBold' lookups.
Expo prefers OTF when both exist; shipping TTF is still valid and matches Mobile. Not a blocker.
Tested storybook builds
Before
After
Tested in storybook on Android and iOS
After
Tested mobile build on android and iOS device
android-metamask.mp4
ScreenRecording_09-09-2026.13-36-51_1.MP4
| 'Inter-Medium': require('../fonts/Inter/Inter-Medium.ttf'), | ||
| 'Inter-MediumItalic': require('../fonts/Inter/Inter-MediumItalic.ttf'), | ||
| 'Inter-SemiBold': require('../fonts/Inter/Inter-SemiBold.ttf'), | ||
| 'Inter-SemiBoldItalic': require('../fonts/Inter/Inter-SemiBoldItalic.ttf'), |
There was a problem hiding this comment.
non-blocking: This matches Expo's useFonts pattern: keys are the fontFamily strings RN will request, files are .ttf (supported on Android, iOS, and web), and the filename equals the PostScript name so iOS and Android agree. No Metro assetExts change is needed; Expo already treats ttf as an asset.
| 'default-regular': 'Inter-Regular', | ||
| 'default-regular-italic': 'Inter-RegularItalic', | ||
| 'default-medium': 'Inter-Medium', | ||
| 'default-medium-italic': 'Inter-MediumItalic', | ||
| 'default-bold': 'Inter-SemiBold', | ||
| 'default-bold-italic': 'Inter-SemiBoldItalic', |
There was a problem hiding this comment.
non-blocking: These strings must stay per-face (Inter-SemiBold), not a single 'Inter' family plus fontWeight. Expo's config-plugin Inter example uses the latter (fontFamily: 'Inter', fontWeight: '700'). That would not resolve these twrnc classes.
| ```tsx | ||
| // After | ||
| useFonts({ | ||
| 'Inter-Regular': require('./fonts/Inter/Inter-Regular.ttf'), |
There was a problem hiding this comment.
non-blocking: The useFonts map is the right consumer recipe. If someone embeds with the expo-font config plugin instead, they should pass the six file paths (Android family = filename). They should not use @expo-google-fonts/inter (Inter_400Regular) or Expo's object-syntax Inter family, both of which would miss these keys.
📖 Storybook LinksCompare the preview for this pull request with the latest Storybooks from the 🔀 Pull Request Preview
🌳 Main Branch (Latest)
|
Testing @metamask-previews packages for: MetaMask/metamask-design-system#1489 Preview commit: 3fe3d5d8 Co-authored-by: Cursor <cursoragent@cursor.com>
## Release 66.0.0 This release replaces Geist with Inter as the default typeface. `--font-family-default` / `fontFamilies.default` are now Inter, and React Native twrnc default font names changed from `Geist-*` to `Inter-*`. Consumers must bundle the Inter font files. Accent (MM Sans) and hero (MM Poly) are unchanged. Includes [#1489](#1489) (Inter default typeface) and [#1500](#1500) (Telegram icon revert). ### 📦 Package Versions - `@metamask/design-tokens`: **11.0.0** - `@metamask/design-system-react`: **0.41.0** - `@metamask/design-system-react-native`: **0.45.0** - `@metamask/design-system-tailwind-preset`: **0.13.0** - `@metamask/design-system-twrnc-preset`: **0.11.0** `@metamask/design-system-shared` is **not** published in this release (JSDoc-only typeface note; Telegram artwork is vendored in the platform packages). ### 🎨 Design Tokens (11.0.0) #### Changed - **BREAKING:** Replaced Geist with Inter as the default typeface ([#1489](#1489)) - `--font-family-default` is now `'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif` - `fontFamilies.default` is now `'Inter'` - Size, weight, line height, and letter spacing tokens are unchanged - Migration: [design-tokens MIGRATION.md](./packages/design-tokens/MIGRATION.md#from-version-10x-to-1100) ### 🌐 React Web Updates (0.41.0) #### Changed - **BREAKING:** Default typeface is now Inter instead of Geist ([#1489](#1489)) - `FontFamily.Default`, `font-default`, and `Text` props are unchanged - Peers updated to `@metamask/design-tokens@^11.0.0` and `@metamask/design-system-tailwind-preset@^0.13.0` - Migration: [React MIGRATION.md](./packages/design-system-react/MIGRATION.md#from-version-0400-to-0410) - Restored the Telegram `Icon` artwork to the official logo ([#1500](#1500)) ### 📱 React Native Updates (0.45.0) #### Changed - **BREAKING:** Default typeface is now Inter instead of Geist ([#1489](#1489)) - twrnc default font names changed from `Geist-*` to `Inter-*` - Peers updated to `@metamask/design-tokens@^11.0.0` and `@metamask/design-system-twrnc-preset@^0.11.0` - Migration: [React Native MIGRATION.md](./packages/design-system-react-native/MIGRATION.md#from-version-0440-to-0450) - Restored the Telegram `Icon` artwork to the official logo ([#1500](#1500)) ###⚠️ Breaking Changes #### Default typeface: Geist to Inter (both platforms) **What Changed:** - Token values: `--font-family-default` / `fontFamilies.default` are Inter - React Native: `font-default-*` PostScript names are `Inter-*` - Consumers must ship Inter font files (six cuts: regular / medium / semibold, each with italic) **Migration (web):** ```css /* Before (tokens 10.x / react 0.40.0) */ @font-face { font-family: 'Geist'; font-style: normal; font-weight: 600; src: url('fonts/Geist/Geist-SemiBold.woff2') format('woff2'); } /* After (tokens 11.0.0 / react 0.41.0) */ @font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; src: url('fonts/Inter/Inter-SemiBold.woff2') format('woff2'); } ``` **Migration (React Native):** ```tsx import { useFonts } from 'expo-font'; // After (0.45.0) useFonts({ 'Inter-Regular': require('./fonts/Inter/Inter-Regular.ttf'), 'Inter-RegularItalic': require('./fonts/Inter/Inter-RegularItalic.ttf'), 'Inter-Medium': require('./fonts/Inter/Inter-Medium.ttf'), 'Inter-MediumItalic': require('./fonts/Inter/Inter-MediumItalic.ttf'), 'Inter-SemiBold': require('./fonts/Inter/Inter-SemiBold.ttf'), 'Inter-SemiBoldItalic': require('./fonts/Inter/Inter-SemiBoldItalic.ttf'), }); ``` **Impact:** - Web: swap `@font-face` assets; no component API changes - React Native: re-register fonts under the new names or text falls back to the system font - Expect minor reflow from Inter vs Geist metrics See migration guides: - [design-tokens](./packages/design-tokens/MIGRATION.md#from-version-10x-to-1100) - [React](./packages/design-system-react/MIGRATION.md#from-version-0400-to-0410) - [React Native](./packages/design-system-react-native/MIGRATION.md#from-version-0440-to-0450) ### ✅ Checklist - [x] Changelogs updated with human-readable descriptions - [x] Changelog validation passed (`yarn changelog:validate`) - [x] Version bumps follow semantic versioning - [x] design-tokens: major (`10.0.0 → 11.0.0`) — breaking default typeface (post-1.0) - [x] design-system-react: minor (`0.40.0 → 0.41.0`) — breaking default typeface (pre-1.0) - [x] design-system-react-native: minor (`0.44.0 → 0.45.0`) — breaking default typeface (pre-1.0) - [x] design-system-tailwind-preset: minor (`0.12.0 → 0.13.0`) — peer `@metamask/design-tokens@^11.0.0` (pre-1.0) - [x] design-system-twrnc-preset: minor (`0.10.0 → 0.11.0`) — Inter PostScript names (pre-1.0) - [x] design-system-shared: skipped — no consumer-facing publish in this release - [x] Breaking changes documented with migration guidance - [x] Migration guides updated with before/after examples (if breaking changes) - [x] PR references included in changelog entries ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) - [x] I've reviewed the [Release Workflow](./.cursor/rules/release-workflow.md) cursor rule - [x] All tests pass (`yarn build && yarn test && yarn lint`) - [x] Changelog validation passes (`yarn changelog:validate`) ## **Pre-merge reviewer checklist** - [ ] I've reviewed the [Reviewing Release PRs](./docs/reviewing-release-prs.md) guide - [ ] Package versions follow semantic versioning - [ ] Changelog entries are consumer-facing (not commit message regurgitation) - [ ] Breaking changes are documented in MIGRATION.md with examples - [ ] All unreleased changes are accounted for in changelogs Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. --> ## **Description** <!-- mms-check: type=text required=true --> Replaces Geist with Inter as MetaMask Mobile's default UI typeface and upgrades the Mobile design-system packages to the official [v66.0.0 release](https://github.com/MetaMask/metamask-design-system/releases/tag/v66.0.0). **Packages upgraded:** - `@metamask/design-system-react-native`: `^0.44.0` → `^0.45.0` - `@metamask/design-system-twrnc-preset`: `^0.10.0` → `^0.11.0` - `@metamask/design-tokens`: `^10.0.0` → `^11.0.0` **Breaking changes addressed:** - Replaced Geist Regular, Medium, and SemiBold (including italics) with the six official Inter `.ttf` assets from the release. The Mobile, Android, and upstream v66 font blobs match exactly. - Updated Expo, iOS, Android, `fontStyles`, `getFontFamily`, and hard-coded font references to use the `Inter-*` PostScript names expected by twrnc 0.11.0. - Updated design-system-rendered Snap snapshots from `Geist-*` to `Inter-*`. - MM Sans, MM Poly, and the MetaMask icon font are unchanged. No component API migration or new legacy-component deprecations are required by v66.0.0. A native rebuild is required; Metro reload alone cannot install the new font assets. ## **Changelog** <!-- mms-check: type=changelog required=true blocking=true --> CHANGELOG entry: Updated the app typeface from Geist to Inter ## **Related issues** <!-- mms-check: type=issue-link required=true --> Refs: MetaMask/metamask-design-system#1489 ## **Manual testing steps** <!-- mms-check: type=manual-testing required=true --> ```gherkin Feature: Inter typeface rollout Background: Given I have installed a native iOS or Android build of this branch And I am logged into MetaMask Mobile Scenario: wallet home uses Inter for body and heading text Given I am on the Wallet home screen When I inspect account names, balances, and tab labels Then text should render in Inter Regular, Medium, or SemiBold And no glyphs should fall back to the system font Scenario: italic text uses dedicated Inter faces Given I open a screen that renders italic body copy When the text is displayed Then it should use the corresponding Inter italic face And it should not use faux italic or the system font Scenario: design-system and app typography are aligned Given I open a Snap surface and a standard app surface When default text is rendered Then both surfaces should use Inter Scenario: brand display fonts remain unchanged Given I open a screen that uses MM Sans or MM Poly When the branded text is displayed Then it should continue using its existing accent or hero font ``` Automated verification: - `yarn lint:tsc` - Four targeted Snap test suites: 27 tests and 20 snapshots passed - All six `app/fonts/Inter-*.ttf` and Android copies match the v66.0.0 upstream Git blobs ## **Screenshots/Recordings** <!-- mms-check: type=screenshot required=true --> ### **Before** https://github.com/user-attachments/assets/b35bd23a-2226-413f-a692-67b65dbc6065 ### **After** https://github.com/user-attachments/assets/b7ccacbc-a9c2-4b42-b645-314e4653e5c0 ## **Pre-merge author checklist** <!-- mms-check: type=checklist required=true --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [x] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [x] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [x] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Visual/typography and asset linking changes with no auth, security, or payment logic; main risk is missing font files or stale native builds showing fallback fonts. > > **Overview** > Replaces the default UI typeface **Geist** with **Inter** (Regular / Medium / SemiBold and italics) across native bundling and app code. Expo `expo-font` entries, Android/iOS asset manifests, Xcode resources, and `UIAppFonts` now point at `Inter-*.ttf` instead of `Geist-*.otf`; **MM Sans**, **MM Poly**, and icon fonts are unchanged. > > Typography resolution is updated so shared helpers and call sites emit `Inter-*` families: `getFontFamily` in `Text.utils.ts`, `fontStyles` in `common.ts`, plus scattered styles (send amount, modals, Snap UI, Card verification, WebView/chart CSS). **@metamask/design-tokens** (^11), **design-system-react-native** (^0.45), and **twrnc-preset** (^0.11) are bumped to align with the design-system Inter rollout. Jest snapshots reflect the new `fontFamily` values. > > A native rebuild is required for new font files; Metro reload alone is insufficient. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 06cb5d9. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: georgewrmarshall <george.marshall@consensys.net>


Description
Replace Geist with Inter as the default typeface across design tokens, the Tailwind and twrnc presets, and both Storybook apps.
MetaMask is moving the product default font to Inter. This PR updates
--font-family-default/fontFamilies.default(and the matching Figma token) so MMDS matches that decision. The six Inter cuts map one-to-one onto the Geist cuts they replace (regular / medium / semibold, each with an italic), so font weight, size, line height, and letter spacing tokens are unchanged. Semantic bold stays at600.--font-family-accent(MM Sans) and--font-family-hero(MM Poly) are unchanged.Font binaries are built from the Inter 18pt static family. Internal name tables are normalized from upstream
Inter18pt-RegularPostScript names toInter-Regular, matching the previous Geist convention, because the twrnc preset and iOS both resolve fonts by PostScript name. React Native now loads.ttfdirectly rather than.otf; Metro's defaultassetExtsalready covers it.Breaking change:
--font-family-defaultandfontFamilies.defaultare now Inter, and twrnc font names changed fromGeist-*toInter-*. Consumers must bundle the Inter font files. Migration notes are in:packages/design-tokens/MIGRATION.mdpackages/design-system-react/MIGRATION.mdpackages/design-system-react-native/MIGRATION.mdRelated issues
Fixes: Mobile update: MetaMask/metamask-mobile#35748
Manual testing steps
yarn storybookand open Typography and Text stories. Confirm default body, medium, and bold (600) text render in Inter, including italic variants. Confirm accent (MM Sans) and hero (MM Poly) are unchanged.font-familyon defaultTextand confirm it is Inter, not Geist. Spot-check a few components that inherit default typography (Button, Banner, ListItem) for unexpected wrapping or overflow.yarn storybook:ios(and Android if available). Confirm default text uses Inter, not the system fallback. Confirm italic and semibold cuts load (wrong PostScript names silently fall back on iOS).Screenshots/Recordings
Before
ScreenRecording_09-08-2026.18-15-37_1.MP4
After
ScreenRecording_09-08-2026.18-09-36_1.MP4
Pre-merge author checklist
Pre-merge reviewer checklist
Made with Cursor