fix(sheets): make the sheet edge visible on Android - #757
Merged
Conversation
The game sheet had no visible boundary on Android, so in light mode it
blended into the board behind it and only the handle gave it away.
`<BottomSheet style={...}>` is forwarded to `BottomSheetBody`, a
transparent container rather than the sheet surface. Android derives an
`elevation` shadow from the view's background outline, so the
`elevation: 8` in the old `sheetShadow` style never drew anything; only
the iOS `shadow*` props on that same view took effect. Moving
`elevation` onto the surface is not an option either, since it reorders
siblings on the Z axis and would paint the background over the sheet's
own content.
Draw the surface in a shared `backgroundComponent` instead, and separate
it with `boxShadow` (paint-only, no Z reordering, supported on both
platforms under the New Architecture) plus a hairline border. The border
is what guarantees the fix: outset `boxShadow` is gated on API 28 and
silently no-ops below it, and light mode has no color contrast to fall
back on, since `sheetBackground` and `background` are both `#F2F2F7` and
`TileBoard` paints that color directly behind the collapsed sheet.
All four sheets now share the one surface.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Merged
wyne
added a commit
that referenced
this pull request
Sep 10, 2026
Merging this PR bumps `package.json` to the version below, tags the merge commit, and publishes the GitHub release. `app.config.js` reads its `version` from `package.json`, so the shipped app version follows automatically. --- ## [3.1.2](v3.1.1...v3.1.2) (2026-09-10) ### Bug Fixes * **edit-player:** give the screen a top margin and a full-height scroll area ([#747](#747)) ([d916b92](d916b92)) * **fab:** restore SwiftUI floating action button ([#753](#753)) ([698bfbe](698bfbe)) * **fab:** stop an outside tap on the open menu reaching the list ([#746](#746)) ([4cc7a35](4cc7a35)) * **game-list:** tidy the row's hierarchy, spacing and dates ([#750](#750)) ([d07ce4e](d07ce4e)) * **list:** refine game list hierarchy ([#752](#752)) ([5417264](5417264)) * **list:** repair game list separators and revert the android options button ([#756](#756)) ([7d73a70](7d73a70)) * polish android interactions ([#755](#755)) ([d061fe4](d061fe4)) * **settings:** label the analytics state in the version alert ([#745](#745)) ([29a16c2](29a16c2)) * **sheets:** make the sheet edge visible on Android ([#757](#757)) ([cee99ce](cee99ce)) * **ui:** one section label style across the app ([#748](#748)) ([0e9a7b8](0e9a7b8)) ### Infrastructure * drop --what-to-test so the iOS submission can be scheduled ([#743](#743)) ([a7a27bd](a7a27bd)) ### Miscellaneous * **deps:** bump expo to 57.0.21 and expo-glass-effect to 57.0.2 ([#751](#751)) ([bcaa6eb](bcaa6eb)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The game sheet had no visible boundary on Android. In light mode it blended completely into the board behind it — the handle was the only thing that gave it away.
Why the previous attempts couldn't work
Three things stack up, and fixing only one of them leaves the sheet invisible:
The
elevationwas landing on a transparent view.<BottomSheet style={...}>isn't applied to the sheet surface — the library forwards it toBottomSheetBody, a bareAnimated.Viewwith no background. Android derives an elevation shadow from the view's background outline, so no background means an empty outline andelevation: 8was a literal no-op. The iOSshadow*props on that same view did work, because iOS derives the shadow from layer contents instead.Moving
elevationonto the surface would have broken the sheet. It reorders siblings on the Z axis, so the background would paint over the handle and content.There was no color to fall back on.
sheetBackgroundandbackgroundare both#F2F2F7in light mode, andTileBoardpaintsbottomSheetHeight + 2of padding in exactly that color right behind the collapsed sheet. The shadow was doing 100% of the work of showing that a sheet existed.Changes
SheetBackground— a sharedbackgroundComponentthat draws the surface itself, so the shadow has a real shape to come from.boxShadowinstead ofelevation: paint-only, no Z reordering, supported on both platforms under the New Architecture.boxShadowis gated at API 28 on Android and silently does nothing below it, andminSdkis Expo's default 24.sheetBorder/sheetShadowtheme tokens; the deadsheetShadowStyleSheet blocks are gone fromGameSheetandChooseWinnersSheet. All four sheets now share the one surface.Validation
Verified on a Pixel 10 emulator (API 37) through Fast Refresh, stashing and unstashing the change to compare the same frame region:
npx tsc --noEmit,npx eslint src/(0 errors),npx jest(451 tests, 45 suites) all pass.iOS is unverified — no simulator was booted and a build is slow, so this is reasoned rather than tested. The shadow moved from the transparent container onto the actual rounded surface, which should read near-identically or slightly crisper. Worst case if
boxShadowdisappoints there is a flatter shadow, not an invisible sheet, since the border still holds.Follow-up worth considering separately
sheetBackground === backgroundin light mode is the root cause the shadow was papering over. Nudging the light sheet to something like#EAEAEFwould give real tonal separation on every platform. Left alone here because it changes the iOS look and the white sub-elements inside the sheet (the Edit button, the ChooseWinners player list) are tuned against the current value.🤖 Generated with Claude Code