Skip to content

Add liquid glass bottom tab bar to iOS, Android and mWeb - #101339

Draft
sumo-slonik wants to merge 40 commits into
Expensify:mainfrom
software-mansion-labs:feat/liquid-glass-tab-bar
Draft

sumo-slonik wants to merge 40 commits into
Expensify:mainfrom
software-mansion-labs:feat/liquid-glass-tab-bar

Conversation

@sumo-slonik

@sumo-slonik sumo-slonik commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

Replaces the JS bottom tab bar on narrow layouts, changing its look on every platform:

  • iOS: native UITabBarController, rendered with liquid glass on iOS 26.
  • Android: native Material navigation bar in our colors.
  • Mobile web: JS bar redrawn as a flat floating capsule.

The native bars come from createNativeBottomTabNavigator (@react-navigation/bottom-tabs/unstable) on top of Tabs from react-native-screens. Both are experimental APIs, so upgrading either package may break this.

iOS 26 ignores most tab item colors, so icons are recolored with Skia and the status dot is drawn into the icon (native badges all take the selected tab's color). The unselected label color can't be changed.

Tab screens are no longer lazy, so a tab shows its skeleton as soon as it's picked.

Patches (details in each details.md):

  • react-native-screens+4.28.0+001+animate-hiding-the-native-tab-bar: animates hiding and showing the bar instead of a blink. The floating buttons fade with it.
  • react-native-screens+4.28.0+002+bold-selected-tab-label: bold label on the selected Android tab.
  • react-native-screens+4.28.0+003+no-android-tab-icon-tint: no icon tint on Android, so the avatar keeps its colors.
  • @react-navigation+bottom-tabs+7.16.2+002+active-indicator-color-precedence: fixes a bug that ignored tabBarActiveIndicatorColor on Android.
  • @react-navigation+bottom-tabs+7.16.2+003+hidden-tab-items: adds tabBarItemHidden, because native bars fit at most 5 of our 6 tabs (Insights or Account, depending on the beta).

@react-navigation/bottom-tabs is bumped to 7.16.2 and @react-navigation/native to 7.2.6, with their existing patches carried over.

The branch history keeps a reverted iOS color patch on purpose, to document a dead end.

Performance

Tab switching on iOS vs main (iPhone 17 simulator, iOS 27, dev build, 5 rounds). Average of median changes across all source tabs:

Tab First visit Later visits
Inbox -17% -41%
Spend -34% -46%

Android tab switching is also noticeably smoother with the native Material bar.

Fixed Issues

$ #101169
PROPOSAL: N/A

Tests

  1. Open the app on iOS 26 and confirm the bottom tab bar renders with the liquid glass material.
  2. Confirm unselected tab icons use the theme icon color and the selected tab is green.
  3. With a status indicator on more than one tab (for example an unread report plus a workspace or account status), confirm each dot keeps its own color no matter which tab is selected.
  4. Open a report from the Inbox tab and go back: the tab bar should travel with the screen instead of blinking, and the floating buttons should fade with it.
  5. Open a tab that has not been visited yet in this session and confirm its skeleton appears immediately rather than an empty screen.
  6. Repeat on Android, and on mobile web where the JS floating capsule is used instead of the native bar.
  • Verify that no errors appear in the JS console

Offline tests

Behavior is identical offline. None of these changes depend on network state, so the same steps as in the Tests section apply with the network connection turned off.

QA Steps

// TODO: These must be filled out, or the issue title must include "[No QA]."

  1. Open the app on iOS 26 and confirm the bottom tab bar renders with the liquid glass material.
  2. Confirm unselected tab icons use the theme icon color and the selected tab is green.
  3. With a status indicator on more than one tab (for example an unread report plus a workspace or account status), confirm each dot keeps its own color no matter which tab is selected.
  4. Open a report from the Inbox tab and go back: the tab bar should travel with the screen instead of blinking, and the floating buttons should fade with it.
  5. Open a tab that has not been visited yet in this session and confirm its skeleton appears immediately rather than an empty screen.
  6. Repeat on Android, and on mobile web where the JS floating capsule is used instead of the native bar.
  • 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

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

# Conflicts:
#	src/components/Navigation/NavigationTabBar/index.tsx
#	src/pages/Insights/InsightsPage.tsx
#	src/pages/home/HomePage.tsx
#	src/pages/inbox/sidebar/NavigationTabBarAvatar.tsx
Untint icons, label every tab, move visibility to screen options, tear down the color scheme coordinator.
The tab fragment is removed and never re-added on the pop. Also reset the color scheme coordinator.
The page skeleton stops at the first cached data and the list rendered a zero-sized view in the gap.
Android leaves the Material bar for the JS one; iOS paints its labels into the icon bitmaps.
# Conflicts:
#	src/pages/Insights/InsightsPage.tsx
#	src/pages/inbox/sidebar/SidebarLinks.tsx
Restore Android tab state, gate Insights on registered tabs, sum LHN inset.
Icons go to 20x20 with a tighter label gap; Android and mWeb blur the backdrop.
Adds a milky glass tint over a light blur.
@sumo-slonik sumo-slonik changed the title Add liquid glass bottom tabs to iOS Add liquid glass bottom tab bar to iOS, Android and mWeb Sep 24, 2026
@JmillsExpensify
JmillsExpensify self-requested a review September 24, 2026 10:53
… web

Drops the blur layer and expo-blur, and removes dead reveal and lazy-split code.
@sumo-slonik

Copy link
Copy Markdown
Contributor Author

I think iOS is already looking good. The content now takes up the full screan, and I also added the padding for the button selection. I think iOS looks good now. I’m checking Android now.

Screen.Recording.2026-10-01.at.14.01.37.mov

@shawnborton

Copy link
Copy Markdown
Contributor

Awesome, let us know when you want another set of test builds.

@sumo-slonik

Copy link
Copy Markdown
Contributor Author

I've checked Android and mWeb as well, and it seems to me that we can try building another ad hoc build.

Screen.Recording.2026-10-01.at.15.13.26.mov
Screen.Recording.2026-10-01.at.15.12.39.mov

@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

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

@shawnborton

Copy link
Copy Markdown
Contributor

Okay cool - one discussion I was having with @dubielzyk-expensify is I wonder if Android would look better if the tab ba used appBG as the background color. Then maybe we can just use a subtle shadow on the top of the bar instead of a border (or use .5px border), like so:
CleanShot 2026-10-01 at 4 14 43 PM@2x

@github-actions

This comment has been minimized.

@shawnborton

Copy link
Copy Markdown
Contributor

For mWeb, can we make the tabs appear to float on the content and not have a solid BG behind them?
CleanShot 2026-10-01 at 8 07 18 PM@2x

And curious what you think about my comments for Android

@dubielzyk-expensify

Copy link
Copy Markdown
Contributor

For mWeb, can we make the tabs appear to float on the content and not have a solid BG behind them?

Sounds good.

And curious what you think about my comments for Android

Go with what you want 👍

@shawnborton

Copy link
Copy Markdown
Contributor

Haha okay! @sumo-slonik can we at least try those Android changes and see how it looks?

@shawnborton shawnborton mentioned this pull request Oct 2, 2026
45 tasks done
@sumo-slonik

Copy link
Copy Markdown
Contributor Author

Haha okay! @sumo-slonik can we at least try those Android changes and see how it looks?

I'm on it

@sumo-slonik

Copy link
Copy Markdown
Contributor Author

@shawnborton I tried the Android changes: the tab bar now uses appBG as its background, with a subtle shadow along the top edge instead of the tonal edge.

I also noticed your mock doesn't have the pill behind the selected tab, so I put together two versions, one with the pill and one without it. Without the pill, the selected tab is marked only by the green icon and the bold label.

Without pill:

Screen.Recording.2026-10-02.at.11.18.19.mov

With pill:

Screen.Recording.2026-10-02.at.11.15.34.mov

Let me know what you think and which direction we want to go!

Copy link
Copy Markdown
Contributor

Hoping we can open this PR up for review today! I'd like to get it in app next week.

@shawnborton

Copy link
Copy Markdown
Contributor

That feels quite good to me 👍

I will run another set of test builds so @dubielzyk-expensify can test Android, but otherwise let's get this ready for review?

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

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

@sumo-slonik

Copy link
Copy Markdown
Contributor Author

@shawnborton I don't think another build will give us the answer yet, because we still haven't decided whether we want to go with the pill or without it. I'll push the code once we decide which direction we want to take.

@shawnborton

Copy link
Copy Markdown
Contributor

By pill, do you mean the light green BG behind the icon on Android?

@github-actions

This comment has been minimized.

@sumo-slonik

Copy link
Copy Markdown
Contributor Author

By pill, do you mean the light green BG behind the icon on Android?

Yep, exactly

@shawnborton

Copy link
Copy Markdown
Contributor

Got it, I think we want that so it feels more native material/Android. Will let Danny and Jon confirm though, but I say push that change.

Copy link
Copy Markdown
Contributor

I would go with that for now. Jon won't be back until next week.

@sumo-slonik

Copy link
Copy Markdown
Contributor Author

mWeb is adjusted as well:

Screen.Recording.2026-10-02.at.12.38.52.mov

Wouldn’t we want the color of the pill on mWeb to be the same as on Android?

@shawnborton

Copy link
Copy Markdown
Contributor

I don't think so, no. I think on mWeb we just want it to feel on brand and as close to the floating idea as possible. Again, let's wait until @dubielzyk-expensify chimes in as he might have stronger feelings about making this feel more native on Android.

@sumo-slonik

Copy link
Copy Markdown
Contributor Author

In that case, I’ve pushed the changes. We can kick off an ad hoc build and wait for @dubielzyk-expensify feedback.

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

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

@dannymcclain

Copy link
Copy Markdown
Contributor

I think I'm aligned with everything above. I'd reckon we want to keep the little icon pill background on Android because that's a standard pattern for the Material navigation bar (docs).

Wouldn’t we want the color of the pill on mWeb to be the same as on Android?
I don't think so, no. I think on mWeb we just want it to feel on brand and as close to the floating idea as possible.

Agree with this as well 👍

I'm also down with the background change to Android you suggested Shawn. I think it looks nice. Seems like typically Android uses a separate color for that bottom tab bar, but the colors and text of the tabs read better on appBG and there's still very clear separation between the tab bar and the content so IMO it's all good.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants