Skip to content

[Due for payment 2026-09-03] Update Font Styles to Match Figma Library #37503

Closed

Description

@grgia

Overview

The original framing — re-type the App's font values to match the Figma library — is the wrong direction and re-drifts the moment Figma or the code changes again. The design-system norm at scale is the reverse: code is the source of truth, Figma mirrors it, and drift is stopped by enforcement, not by periodic re-syncing.

So the goal here is not "match the numbers once." It's to establish a typography token layer that is the single source of truth, named to match Figma's semantic styles, and make raw font sizing impossible to add. Automated Figma→code sync is explicitly out of scope — it's over-engineering for a ~12-value scale that changes rarely.

Problem

Type styles have drifted and tangled, with no mechanism to stop it:

  • fontSizeH1 and fontSizeH2 are both 19; textHeadline/textHeadlineH1 share a size with different line heights.
  • ~75 text* styles express ~12 real roles because color is baked into type styles (textNormalThemeText, textMicroSupporting, …), exploding combinatorially across color × size.
  • 205 raw fontSize: and 115 raw lineHeight: literals bypass the scale entirely. This — not Figma changing — is where drift actually comes from.

Scope (zero behavior change)

  1. Token file (src/styles/typography.ts) — two tiers: a primitive scale (sizes, line heights, weights, families) + semantic styles named to match Figma (body, bodyStrong, label, caption, headline, display). Type only — no color.
  2. Alias the existing text* styles to the new tokens, re-attaching color at the alias boundary so every current call-site renders byte-identically.
  3. <Text variant> prop keyed to the semantic styles; deprecate the free-form fontSize prop.
  4. Lint rule banning raw fontSize/lineHeight outside the token file (existing violations grandfathered via eslint-seatbelt).

Out of scope

  • No automated Figma→code sync pipeline.
  • No big-bang migration of call-sites. The lint rule + variant prop pull files onto the system as they're next edited; an optional incremental sweep (one surface per PR, with screenshots) can accelerate it later.

Example

Foundation — semantic styles compose the primitive scale, no color:

const textVariants = {
    body:     {...FontUtils.fontFamily.platform.EXP_NEUE,  fontSize: fontScale.body,  lineHeight: variables.fontSizeNormalHeight},
    headline: {...headlineFont, ...whiteSpace.preWrap,     fontSize: fontScale.title, lineHeight: variables.lineHeightSizeH1},
    // label, caption, bodyStrong, display…
} as const;

Alias — old names keep working, now sourced from the scale (color re-attached here):

// before
textNormalThemeText: {color: theme.text, fontSize: variables.fontSizeNormal},
textHeadlineH1: {...headlineFont, ...whiteSpace.preWrap, color: theme.heading, fontSize: variables.fontSizeXLarge, lineHeight: variables.lineHeightSizeH1},

// after — identical output
textNormalThemeText: {...textVariants.body, color: theme.text},
textHeadlineH1:      {...textVariants.headline, color: theme.heading},

Call-site — color and type decouple (FeedbackSurvey.tsx):

// before
<Text style={[styles.mt1, styles.textNormalThemeText]}>{description}</Text>
// after
<Text variant="body" color={theme.text} style={styles.mt1}>{description}</Text>

Lint rule — stops the next raw literal at the door:

42:9  error  Raw `fontSize: 17` is not allowed. Use a <Text variant> or a token
             from src/styles/typography.ts  expensify/no-raw-typography

Blast radius

  • Now: ~6–8 files (token file + alias edits in src/styles/index.ts + Text.tsx + FontUtils map + ESLint config/rule).
  • Eventually (passive, never at once): ~597 files reference styles.text* or raw font literals (~9.5% of src). Aliasing means none of them need to change; they heal one line at a time as the linter flags them.

Needs design input

Design to confirm the semantic scale and the Figma-name → token map before the token file lands — the one part that can't be derived from code.

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~01c1bcd6eca2caf1d6
  • Upwork Job ID: 1763210051994648576
  • Last Price Increase: 2024-02-29
Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

  1. added
    InternalRequires API changes or must be handled by Expensify staff
    BugSomething is broken. Auto assigns a BugZero manager.
    on Feb 29, 2024
  2. self-assigned this
    on Feb 29, 2024
  3. melvin-bot commented on Feb 29, 2024

    @melvin-bot
  4. melvin-bot commented on Feb 29, 2024

    @melvin-bot

    Triggered auto assignment to Contributor Plus for review of internal employee PR - @rushatgabhane (Internal)

  5. melvin-bot commented on Feb 29, 2024

    @melvin-bot

    Triggered auto assignment to @dannymcclain (Design), see these Stack Overflow questions for more details.

  6. melvin-bot commented on Feb 29, 2024

    @melvin-bot

    Triggered auto assignment to @Christinadobrzyn (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details.

  7. removed
    BugSomething is broken. Auto assigns a BugZero manager.
    on Feb 29, 2024
  8. 131 remaining items

  9. grgia commented on Sep 1, 2026

    @grgia
    ContributorAuthor

    Yes, small follow up needed. Both remaining fontSizeH1 call-sites (src/styles/index.ts:3471, MoneyReportView.tsx:135) are 19px, same as fontScale.h2, so swapping them and deleting fontSizeH1 is zero visual change. we should also widen the lint rule to catch variables.fontSize*. The unaliased text*` styles stay out of scope, some shift visuals so they need design sign-off first

    @aimane-chnaif

  10. melvin-bot commented on Sep 2, 2026

    @melvin-bot

    Triggered auto assignment to @mallenexpensify (Awaiting Payment)

  11. melvin-bot commented on Sep 2, 2026

    @melvin-bot

    Payment Summary

    Resolving PRs:

    BugZero Checklist (@mallenexpensify)

    • I have confirmed assignees, roles, and Upwork contracts look correct
    • I have paid out Upwork contracts / manual NewDot requests
    • [BugZero Assignee] I have created a GH issue for creating/updating the regression test once above steps have been agreed upon
  12. grgia commented on Sep 4, 2026

    @grgia
    ContributorAuthor

    PR is in prod with no regressions, payment is with @mallenexpensify. @aimane-chnaif any interest in the fontSizeH1 follow up PR?

  13. aimane-chnaif commented on Sep 4, 2026

    @aimane-chnaif
    Contributor

    any interest in the fontSizeH1 follow up PR?

    I can work on this with Melvin

  14. grgia commented on Sep 4, 2026

    @grgia
    ContributorAuthor

    @aimane-chnaif sure, I opened an issue and copied over the above #37503

  15. mallenexpensify commented on Sep 4, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor+: @aimane-chnaif due $250 via NewDot

  16. mallenexpensify commented on Sep 4, 2026

    @mallenexpensify
    Contributor

    I'm assuming we don't need a test case here.

  17. garrettmknight commented on Sep 14, 2026

    @garrettmknight
    Contributor

    $250 approved for @aimane-chnaif

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Awaiting PaymentAuto-added when associated PR is deployed to productionDailyKSv2DesignInternalRequires API changes or must be handled by Expensify staff

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions