Skip to content

[Design Polish][Layout][Improvement] Tighten mobile gutters and card padding through spacing tokens - #100960

Merged
arosiclair merged 54 commits into
mainfrom
georgia-spacing-system
Oct 7, 2026
Merged

arosiclair merged 54 commits into
mainfrom
georgia-spacing-system

Conversation

@grgia

@grgia grgia commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

Mobile gutters go from 20px to 12px, card padding goes from 20px to 24px, and stacked cards sit 12px apart. Desktop doesn't change.

Rather than sweep ~40 files swapping ph5 for ph6, I moved the values into src/styles/layoutSpacing.ts and a useLayoutSpacing() hook, migrated the cards, lists and tables onto it, and then flipped the numbers. The first six commits should be pixel-identical to main; the later ones are the actual design change plus the bits design found in the builds (Rules cards, Spend filter pills, Domain button rows, the expense report table). There's a lint rule so the old shouldUseNarrowLayout ? styles.ph5 : styles.ph8 pattern can't creep back (it tells you which style to use instead), and STYLING.md now covers the tokens.

Measurements are from the Figma frames on the tracking issue. Two things I inferred rather than measured: the 12px gap between stacked Section cards (only drawn on Home), and sections inside the RHP picking up the mobile gutter. Both are one number to change if design wants otherwise.

Not touched: sections outside the central pane on desktop (still 20px), tab bars and pages inside the RHP on desktop (still 20px, only the page-level tab bars on Spend, Workspaces, Inbox, Rules and Workflows moved), and table row padding (already 16px on mobile).

Fixed Issues

$ #100819
PROPOSAL:

Tests

  1. On a phone or a narrow window, open Home. Cards are 12px from the edge, content inside them is 24px in, and there's 12px between cards.
  2. Spend > Expenses: the list, the search box, the filter pills and the tab pills all start 12px from the edge.
  3. Workspaces: tabs and table both at 12px.
  4. Any workspace > Categories: the Add category / Settings buttons and the table are at 12px, the description text is still at 20px.
  5. Workspace Overview, Fields, Reports and Rules: same 12 / 24 / 12 as Home.
  6. Account > Wallet and Subscription: same again, and payment method rows still stretch to the card edge on hover.
  7. Open an expense report: the table and the Group by row sit 12px from the edge.
  8. Widen the window past 1024px and go back through 1 to 7. Nothing should have moved vs main.
  • Verify that no errors appear in the JS console

Offline tests

None, styling only.

QA Steps

Same as Tests.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Pending. Draft opened for local and design review first.

cardPadding and pageGutter become narrow/wide token maps in
src/styles/layoutSpacing.ts. useLayoutSpacing resolves them once per
component from useResponsiveLayout and returns ready styles, so call
sites stop switching on the layout flag. The home content container and
widget header read the tokens. Values are unchanged.
Replaces shouldUseNarrowLayout ? styles.ph5 : styles.ph8 (and the p5/p8
and mhn5/mhn8 forms) in Home widgets, Section, Wallet rows and workspace
sections with the resolved styles. No value changes.
no-layout-spacing-conditional flags styles.ph*/p*/pl*/pr*/m*/mh*/mhn*
picked by a shouldUseNarrowLayout or screen-width ternary. Vertical
spacing is left alone. Remaining sites outside cards are grandfathered
in the seatbelt baseline.
For the three known pairs (ph5/ph8, p5/p8, mhn5/mhn8) the message says
which useLayoutSpacing() style to use instead of pointing at the file.
cardPaddingBottom, cardPaddingLeft and cardMarginHorizontal cover the
four sites that otherwise needed an inline style object, which STYLING.md
does not allow.
STYLING.md gains a tokens section, the coding-standards skill gains UI-4,
and CLAUDE.md points at both so generated code reaches for the variant
and useLayoutSpacing() before the linter has to.
…nput

The bare styles.mh5 on list rows was the mobile page gutter and the
desktop row margin at once. pageGutterMargin keeps both values as they
are today and lets the column width hook read the same number.
Implements the design polish spec for mobile: cards and tables sit 12px
from the screen edge and card content is inset 24px. Wide layouts are
unchanged.
cardGap joins the layout spacing tokens (12 narrow, 20 wide). The Home
widget stack reads it; wide columns are unchanged. Measured from the
design polish Figma frames.
Section and WorkspaceEmptyStateSection read cardGapBottom from
useLayoutSpacing, matching the Home widget stack. Wide stays at 20.
@grgia grgia added the Design label Sep 11, 2026
@melvin-bot
melvin-bot Bot requested a review from shawnborton September 11, 2026 12:24
@codecov

codecov Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/components/BaseWidgetItem.tsx 100.00% <100.00%> (ø)
...omponents/MenuItem/presets/MenuItemSectionRoot.tsx 100.00% <100.00%> (ø)
...stReportView/MoneyRequestReportTransactionList.tsx 72.05% <100.00%> (+0.41%) ⬆️
src/components/Search/SearchBulkActionsButton.tsx 89.36% <100.00%> (+0.11%) ⬆️
...ponents/Search/SearchList/ListItem/GroupHeader.tsx 77.47% <100.00%> (ø)
...h/SearchList/ListItem/TransactionGroupListItem.tsx 76.81% <100.00%> (ø)
...Search/SearchPageHeader/SearchFiltersBarNarrow.tsx 88.23% <100.00%> (+0.73%) ⬆️
src/components/Section/index.tsx 100.00% <100.00%> (ø)
src/components/Table/TableFilterBar/index.tsx 96.66% <100.00%> (+0.11%) ⬆️
src/components/Table/TableHeader.tsx 94.62% <100.00%> (+0.05%) ⬆️
... and 79 more
... and 8 files with indirect coverage changes

@grgia

grgia commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

The Add / Settings button row above workspace tables sat at 20px while
the table below it moved to 12px. It now reads pageGutter, matching the
Categories and Workspaces mocks. Description text stays at 20px.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9688a4eeda

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread eslint-plugin-local-rules/no-layout-spacing-conditional.js Outdated
@shawnborton

Copy link
Copy Markdown
Contributor

Make some builds whenever you are ready, excited for this one!

TabSelectorBase reads pageGutter instead of a fixed 20px, and the
Workspaces header row reads pageGutterRight, so the tab bar lines up
with the 12px gutter on mobile. Wide stays at 20.
The rule now reports a narrow/wide ternary only when the pair spells the
card padding values, the 5 step narrow and the 8 step wide, in any inset
property, and understands wide-first flags like isLargeScreenWidth.
Unrelated pairs such as ph3/ph2 pass, so the seatbelt baseline needs no
entries for this rule.
@shawnborton

Copy link
Copy Markdown
Contributor

How is this one going?

# Conflicts:
#	.claude/skills/app-coding-standards/rules/ui-4-layout-spacing-tokens.md
#	config/eslint/eslint.seatbelt.tsv
#	src/components/Table/TableRow.tsx
#	src/components/Table/useDynamicColumnWidths.ts
#	src/pages/Search/SearchPageNarrow/index.tsx
#	src/pages/workspace/perDiem/WorkspacePerDiemPage.tsx
#	src/pages/workspace/rules/PolicyRulesPage.tsx
#	src/pages/workspace/tags/DynamicWorkspaceViewTagsPage.tsx
# Conflicts:
#	config/eslint/eslint.config.mjs
@grgia
grgia requested a review from huult September 16, 2026 08:40
@grgia
grgia marked this pull request as ready for review September 16, 2026 08:40
@grgia
grgia requested review from a team and Julesssss as code owners September 16, 2026 08:40
@grgia

grgia commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

@huult main is merged again and the test job should be green now, the fix landed on main last night. ready for a final look.

@huult

huult commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

@arosiclair all yours

Comment thread .claude/skills/app-coding-standards/rules/ui-4-layout-spacing-tokens.md Outdated
Comment thread .claude/skills/app-coding-standards/rules/ui-4-layout-spacing-tokens.md Outdated
Comment thread contributingGuides/STYLING.md Outdated
@grgia
grgia requested a review from arosiclair October 7, 2026 08:00
@grgia

grgia commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

I made @arosiclair's comments on the MD files. We chatted last night so we should be good to merge this cycle.

Fixing conflicts now 🙏 for no conflicts haha

@huult for rereview once merge hits

@arosiclair
arosiclair merged commit 5d25622 into main Oct 7, 2026
42 of 47 checks passed
@arosiclair
arosiclair deleted the georgia-spacing-system branch October 7, 2026 13:03
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🚧 arosiclair has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

OSBotify commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify OSBotify mentioned this pull request Oct 7, 2026
68 tasks done
@OSBotify

OSBotify commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/arosiclair in version: 9.5.5-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor

No help site update needed: this PR only changes mobile spacing and padding (gutters, card padding, card gaps), and no article in docs/articles describes layout spacing.


view run

@OSBotify

OSBotify commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/puneetlath in version: 9.5.5-2 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):


const tableRowPressableStyles = [
styles.mh5,
pageGutterMargin,

@Krishna2323 Krishna2323 Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grgia heads up, this looks like it stranded two section headers.

TableRow moved from styles.mh5 to pageGutterMargin, and the message says it keeps both values as they are today. That holds on wide, but pageGutter is {narrow: 12, wide: 20}, so on narrow the row went 20 to 12 while these two kept the hardcoded styles.mh5:

  • src/components/Tables/WorkspaceExpenseDefaultsTable/WorkspaceExpenseDefaultsTableRow.tsx:99
  • src/components/Tables/WorkspaceSpendRulesTable/WorkspaceSpendRulesTableRow.tsx:75

Both render the grey section band above a Table.Row, so on small screens the band is inset 8px further than the rows and its text no longer lines up with the row text. Workspace > Rules > Expense defaults and Card restrictions both show it. Wide is unaffected, which is probably why it slipped through.

Image

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants