fix(web): default flex: N to flexShrink 0 matching React Native behavior#3936
fix(web): default flex: N to flexShrink 0 matching React Native behavior#3936lightstrike wants to merge 4 commits into
Conversation
In expandStyle.ts, flex: N always expanded to flexShrink: 1, which contradicted React Native's default of 0 and caused flex children to collapse in column layouts in v2. flexShrink is now 0 by default and 1 only when styleCompat is 'legacy', matching the RN shorthand. Adds unit tests for all three styleCompat variants and a kitchen-sink Playwright test.
…exShrink change oxfmt reformatted the long createTamagui calls in getStylesAtomic.web.test.tsx. Updated babel.web snapshots where flex: N now correctly emits _fs-0 instead of _fs-1.
Update webpack static compiler snapshots to expect _fs-0 after the flexShrink default change. Fix FlexShrink.test.tsx to use the correct usecase name 'FlexShrinkCase' (matching the filename).
Tamagui v2 does not auto-convert testID to data-testid on web. Add explicit data-testid attributes so getByTestId works in Playwright tests.
|
This was semi-intentional, but perhaps wrong. We wanted to align better to both RN and web, but when possible, prefer web. Especially for core styling primitives. v1 did have the philosophy of always aligning to native though and perhaps this is the only case where we no longer do. It is nice to be web-aligned, web is much bigger demo, and eg tailwind shorthands align now automatically, but it does come at the cost of RN weirdness. IMO if it's documented clearly it should be fine. We could have a third styleCompat |
natew
left a comment
There was a problem hiding this comment.
Thanks for the thorough PR with tests. After digging into this, I have concerns about the rationale and the breaking change scope.
The "React Native behavior" claim is inaccurate
React Native's Yoga layout engine, when given flex: 1 (positive value), actually expands to flexGrow: 1, flexShrink: 1, flexBasis: 0 — not flexShrink: 0. The flexShrink: 0 seen in RN behavior comes from the View base styles (which react-native-web explicitly sets as flexShrink: 0 on its View component), not from the flex shorthand expansion.
Evidence from node_modules/react-native-web/dist/cjs/exports/StyleSheet/compiler/createReactDOMStyle.js: the upstream react-native-web does NOT expand positive flex: N into individual longhands at all — it passes flex: value straight to the DOM. Only flex: -1 gets the special expansion to {flexGrow: 0, flexShrink: 1, flexBasis: auto}.
The real issue vs the proposed fix
The actual problem is that Tamagui v2 renders plain <div> elements without the react-native-web View base style (flexShrink: 0), so explicit flexShrink: 1 from the shorthand expansion can cause shrinking where none was expected. However, the fix conflates two things: the View default behavior (which should be set in the View base style) with the flex shorthand expansion semantics.
Breaking change concern
The snapshot diffs (_fs-1 → _fs-0) show this affects every component using the flex prop globally. Any user relying on flex children shrinking in a row layout (e.g., text that should truncate) would silently get wrong behavior. This is a significant breaking change for all existing web-only users.
natew's own comment notes this was semi-intentional and frames it as a tradeoff — "web is much bigger demo, and tailwind shorthands align now automatically." The existing behavior aligns with the CSS shorthand spec (flex: 1 = flex-grow: 1; flex-shrink: 1; flex-basis: 0).
Suggested direction
If the goal is to prevent unexpected collapsing in column layouts (like the test case demonstrates), a more accurate fix would be to add flexShrink: 0 to the View base/default styles — matching what react-native-web actually does — rather than changing the flex shorthand expansion globally. That would give RN-compatible defaults without overriding explicitly set flex shorthand semantics.
I'd hold on approving until natew weighs in with a clear direction, given his comment indicates uncertainty and this is a meaningful behavior change.
Summary
expandStyle.tswas always expandingflex: NtoflexShrink: 1, contradicting React Native's default offlexShrink: 0<div>elements (not via react-native-web), so this explicitflexShrink: 1caused flex children to collapse below their content height in column layouts — a regression from v1flexBasispattern:flexShrinkis now0by default and1only whenstyleCompat: 'legacy'(preserving CSS shorthand semantics for explicit opt-ins)flex: -1special case is unchanged (alwaysflexShrink: 1, flexGrow: 0, flexBasis: auto)Files changed
code/core/web/src/helpers/expandStyle.tsflexShrinkconditional onstyleCompatcode/core/core-test/getStylesAtomic.web.test.tsxstyleCompatvariants +flex: -1code/kitchen-sink/src/usecases/FlexShrinkCase.tsxcode/kitchen-sink/tests/FlexShrink.test.tsxflex-shrink: 0Test plan
cd code/core/core-test && TAMAGUI_TARGET=web npx vitest --run ... getStylesAtomic.web.test.tsx— all 10 tests passcd code/kitchen-sink && npx playwright test tests/FlexShrink.test.tsxhttp://localhost:9000/?test=FlexShrink— both children fill the container (~200px each)bun run test:web— no regressions