Skip to content

fix(web): default flex: N to flexShrink 0 matching React Native behavior#3936

Open
lightstrike wants to merge 4 commits into
tamagui:mainfrom
lightstrikelabs:fix/expand-style-flex-shrink
Open

fix(web): default flex: N to flexShrink 0 matching React Native behavior#3936
lightstrike wants to merge 4 commits into
tamagui:mainfrom
lightstrikelabs:fix/expand-style-flex-shrink

Conversation

@lightstrike

Copy link
Copy Markdown
Collaborator

Summary

  • expandStyle.ts was always expanding flex: N to flexShrink: 1, contradicting React Native's default of flexShrink: 0
  • In v2, components render as plain <div> elements (not via react-native-web), so this explicit flexShrink: 1 caused flex children to collapse below their content height in column layouts — a regression from v1
  • Fix mirrors the existing flexBasis pattern: flexShrink is now 0 by default and 1 only when styleCompat: 'legacy' (preserving CSS shorthand semantics for explicit opt-ins)
  • The flex: -1 special case is unchanged (always flexShrink: 1, flexGrow: 0, flexBasis: auto)

Files changed

File Change
code/core/web/src/helpers/expandStyle.ts 1-line fix: flexShrink conditional on styleCompat
code/core/core-test/getStylesAtomic.web.test.tsx Unit tests for all three styleCompat variants + flex: -1
code/kitchen-sink/src/usecases/FlexShrinkCase.tsx New integration test usecase
code/kitchen-sink/tests/FlexShrink.test.tsx Playwright test verifying height > 100px and flex-shrink: 0

Test plan

  • cd code/core/core-test && TAMAGUI_TARGET=web npx vitest --run ... getStylesAtomic.web.test.tsx — all 10 tests pass
  • cd code/kitchen-sink && npx playwright test tests/FlexShrink.test.tsx
  • Open http://localhost:9000/?test=FlexShrink — both children fill the container (~200px each)
  • Run full bun run test:web — no regressions

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.
@natew

natew commented Mar 9, 2026

Copy link
Copy Markdown
Member

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 'native' perhaps but I don't like it. I could be wrong here too, it'd be nice if RN eventually aligned as they did have a few RFC's stating that was the plan, but if they never plan to change to match web that would be a definite point to consider against.

@natew natew left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

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.

2 participants