Skip to content

[fab] Let an extended Fab grow to fit a wrapping label - #49529

Open
teoarjun wants to merge 3 commits into
mui:masterfrom
teoarjun:fix/fab-extended-wrapping-label
Open

teoarjun wants to merge 3 commits into
mui:masterfrom
teoarjun:fix/fab-extended-wrapping-label

Conversation

@teoarjun

Copy link
Copy Markdown

The extended variant sets a fixed height per size and no vertical padding, so once a label wraps the text is laid out taller than the button and renders outside it. Measured in Chromium, a two-line label sits 0.5px outside a large extended Fab, 4.5px outside a medium one and 7.5px outside a small one; at three lines it is 12.8px, 16.8px and 19.8px.

This keeps the same numbers but as a minimum rather than a fixed height, and adds vertical padding so the button grows to fit. A one-line label still measures 48, 40 and 34px, so nothing moves for the common case and the docs demos, which all use short labels, should render identically. The root still sets height: 56 for the circular variants, so the extended variant keeps an explicit height: 'auto'.

This covers the Fab height item from the Wave 1 follow-ups in #49477.

The test is guarded with skipIf(isJsdom()) because jsdom does no layout. It passes in the browser scope and fails on master with expected 48 to be above 48. I have not run the visual regression suite

The extended variant pinned a fixed height per size and used no vertical
padding, so a label that wrapped was laid out taller than the button and
rendered outside it. In Chromium a two-line label sits 0.5px, 4.5px and
7.5px outside the large, medium and small buttons; a three-line label
12.8px, 16.8px and 19.8px.

Swapping the fixed height for the same value as a minimum, plus vertical
padding, lets the button grow instead. A one-line label still measures 48,
40 and 34px, so nothing moves for the common case.
@code-infra-dashboard

code-infra-dashboard Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Deploy preview

https://deploy-preview-49529--material-ui.netlify.app/
QR code for https://deploy-preview-49529--material-ui.netlify.app/

Bundle size

Bundle Parsed size Gzip size
@mui/material 🔺+19B(0.00%) 🔺+15B(+0.01%)
@mui/lab 0B(0.00%) 0B(0.00%)
@mui/private-theming 0B(0.00%) 0B(0.00%)
@mui/system 0B(0.00%) 0B(0.00%)
@mui/utils 0B(0.00%) 0B(0.00%)

Details of bundle changes

Performance

No regressions in 32 benchmarks · ran 4m 06s · details


Check out the code infra dashboard for more information about this PR.

@teoarjun
teoarjun marked this pull request as ready for review October 22, 2026 11:45
@ZeeshanTamboli ZeeshanTamboli added type: bug It doesn't behave as expected. component: Fab The React component. labels Oct 26, 2026

@ZeeshanTamboli ZeeshanTamboli 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 this! The fix works: a wrapped label now stays inside the button, and with the default theme a one-line label still measures 48, 40 and 34px.

However, this is a breaking change, and the description's "nothing moves for the common case" doesn't hold. I measured master and this branch in Chromium and Firefox, with identical results in both:

Case master this PR
Large, single line 48 48
Medium / small, single line 40 / 34 40 / 34
Large, inside a flex row 100px tall 48 100
Small, flex row next to a 56px sibling 34 56
Large, inside a grid row 80px tall 48 80
Large with sx={{ height: 30 }} 30 48
Small with sx={{ height: 28 }} 28 34
Small, theme typography.fontSize: 16 34 36
Small with a 35px child (large icon) 34 43

Details are in the inline comments. In short:

  • Regression: with height: 'auto', a flex or grid parent now stretches the Fab to match its tallest sibling. height: 'fit-content' avoids this.
  • Breaking change: the new minHeight overrides any smaller height set through sx, styleOverrides or a theme variant. Custom sizes added through module augmentation inherit minHeight: 48. As far as I can tell, no CSS lets the button grow with its content and still keep a smaller explicit height. So this either needs a breaking-change note in the changelog and migration guide, or should wait for the next major.

The visual regression suite won't catch either one, because both extended Fab demos use block containers.

Comment thread packages/mui-material/src/Fab/Fab.js Outdated
padding: '8px 16px',
width: 'auto',
minHeight: 'auto',
height: 'auto',

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.

Regression: the Fab now stretches inside flex rows and grid cells.

Flex and grid parents stretch a child by default, but only when its height is auto. The fixed height: 48 on master prevented that. Now an extended Fab in a <Stack direction="row">, a display: flex row or a grid cell grows to match its tallest sibling:

  • flex row 100px tall: 48 → 100
  • small Fab next to a 56px sibling: 34 → 56
  • grid row 80px tall: 48 → 80

fit-content isn't auto, so the parent doesn't stretch it, and it still resolves to the content height, so the button still grows. On top of this branch it gives 48 in both the flex and grid cases and 65 for the wrapped label. All browsers in our browserslist support it (Chrome 117, Firefox 121, Safari 17).

Suggested change
height: 'auto',
// `fit-content` rather than `auto`: grows with a wrapping label, but
// isn't stretched by a flex or grid parent.
height: 'fit-content',

height: 'auto',
minWidth: 48,
height: 48,
minHeight: 48,

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.

Breaking change: a smaller height customization no longer applies.

On master the extended variant set minHeight: 'auto', so a smaller height from sx, styleOverrides.extended or a theme variant took effect. Now this minHeight wins, and so do the 34 and 40 below for the small and medium sizes:

  • <Fab variant="extended" sx={{ height: 30 }}>: 30 → 48
  • <Fab variant="extended" size="small" sx={{ height: 28 }}>: 28 → 34

Custom sizes added through module augmentation are hit hardest. No built-in variant matches them, so they inherit minHeight: 48 and padding: '8px 16px'. A custom xsmall size with height: 28 renders at 48.

Users can fix it by also setting minHeight, but nothing warns them, so their layouts just change. I don't see a CSS approach that grows with the content and still keeps a smaller explicit height. So I think this has to be called out as a breaking change, with a changelog entry and a migration note, or held for the next major. Please also update the PR description, which currently says nothing moves.

Comment thread packages/mui-material/src/Fab/Fab.js Outdated
style: {
width: 'auto',
padding: '0 8px',
padding: '4px 8px',

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.

The small size leaves 26px for content (34 − 2 × 4px), and the default label line is 24.5px, so there's only 1.5px of headroom. Anything slightly taller now changes the default height:

  • createTheme({ typography: { fontSize: 16 } }): 34 → 36
  • a 35px child, such as a fontSize="large" icon: 34 → 43. A default 40px CircularProgress or Avatar would behave the same way.

You could argue this is the fix working as intended, since that content used to overflow. But it's another visible change for apps with a custom theme. To keep it smaller, 2px vertical padding leaves 30px of room, which fits 16px type. The cost is tighter spacing around a wrapped label:

Suggested change
padding: '4px 8px',
padding: '2px 8px',


// JSDOM does no layout, so the overflow this guards against is only visible
// in a real browser.
it.skipIf(isJsdom())('keeps a wrapping label inside the extended button', () => {

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.

This only covers the large size, and no test checks that a one-line label keeps its height. Could you add:

  • a check that a one-line label measures 48, 40 and 34px for the large, medium and small sizes
  • a check that a flex parent doesn't stretch the Fab, so the regression flagged in Fab.js can't come back. This one fails on the current branch, which measures 100px:
it.skipIf(isJsdom())('is not stretched by a flex parent', () => {
  render(
    <div style={{ display: 'flex', height: 100 }}>
      <Fab variant="extended">Add</Fab>
    </div>,
  );
  expect(screen.getByRole('button').getBoundingClientRect().height).to.equal(48);
});

@ZeeshanTamboli ZeeshanTamboli added the breaking change Introduces changes that are not backward compatible. label Oct 26, 2026
height: auto let a flex or grid parent stretch the button: in a flex row or a
grid with a 160px track it grew from 48 to 160. fit-content keeps it sized to
the label while still letting it grow when the label wraps.

Also drops the small size's vertical padding to 2px, which left only 1.5px of
headroom under its 34px minimum, and adds tests for the one-line height of
each size and for the flex-parent case.

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

breaking change Introduces changes that are not backward compatible. component: Fab The React component. type: bug It doesn't behave as expected.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants