From f82af797579eef51a0cde505cbfc87030468f172 Mon Sep 17 00:00:00 2001 From: Arjun Teotia Date: Thu, 22 Oct 2026 10:48:04 +0000 Subject: [PATCH 1/2] [fab] Let an extended Fab grow to fit a wrapping label 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. --- packages/mui-material/src/Fab/Fab.js | 16 +++++++++------- packages/mui-material/src/Fab/Fab.test.js | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/mui-material/src/Fab/Fab.js b/packages/mui-material/src/Fab/Fab.js index 2c2e5997d5b45a..2eb234015708f3 100644 --- a/packages/mui-material/src/Fab/Fab.js +++ b/packages/mui-material/src/Fab/Fab.js @@ -103,31 +103,33 @@ const FabRoot = styled(ButtonBase, { props: { variant: 'extended' }, style: { borderRadius: 48 / 2, - padding: '0 16px', + // Vertical padding rather than a fixed height, so a label that wraps + // stays inside the button instead of overflowing it. + padding: '8px 16px', width: 'auto', - minHeight: 'auto', + height: 'auto', minWidth: 48, - height: 48, + minHeight: 48, }, }, { props: { variant: 'extended', size: 'small' }, style: { width: 'auto', - padding: '0 8px', + padding: '4px 8px', borderRadius: 34 / 2, minWidth: 34, - height: 34, + minHeight: 34, }, }, { props: { variant: 'extended', size: 'medium' }, style: { width: 'auto', - padding: '0 16px', + padding: '4px 16px', borderRadius: 40 / 2, minWidth: 40, - height: 40, + minHeight: 40, }, }, { diff --git a/packages/mui-material/src/Fab/Fab.test.js b/packages/mui-material/src/Fab/Fab.test.js index d1ff7dd41f8372..5eac006a58cd92 100644 --- a/packages/mui-material/src/Fab/Fab.test.js +++ b/packages/mui-material/src/Fab/Fab.test.js @@ -97,6 +97,26 @@ describe('', () => { expect(button).to.have.class(classes.sizeMedium); }); + // 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', () => { + render( +
+ + Create new deployment + +
, + ); + + const fab = screen.getByRole('button').getBoundingClientRect(); + const label = screen.getByTestId('label').getBoundingClientRect(); + + // The button grew past its one-line height, so the label wrapped. + expect(fab.height).to.be.greaterThan(48); + expect(label.top).to.be.at.least(fab.top); + expect(label.bottom).to.be.at.most(fab.bottom); + }); + it('should have a ripple', async () => { const { container } = render(Fab); await ripple.startTouch(screen.getByRole('button')); From c12baa2f0805a635c45c76bed29df454b393279c Mon Sep 17 00:00:00 2001 From: Arjun Teotia Date: Tue, 27 Oct 2026 08:10:51 +0000 Subject: [PATCH 2/2] [fab] Size the extended Fab to its content, not its parent 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. --- packages/mui-material/src/Fab/Fab.js | 9 ++++--- packages/mui-material/src/Fab/Fab.test.js | 33 +++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/mui-material/src/Fab/Fab.js b/packages/mui-material/src/Fab/Fab.js index 2eb234015708f3..42102d9797e678 100644 --- a/packages/mui-material/src/Fab/Fab.js +++ b/packages/mui-material/src/Fab/Fab.js @@ -103,11 +103,12 @@ const FabRoot = styled(ButtonBase, { props: { variant: 'extended' }, style: { borderRadius: 48 / 2, - // Vertical padding rather than a fixed height, so a label that wraps - // stays inside the button instead of overflowing it. + // Vertical padding and a content-sized height rather than a fixed + // height, so a label that wraps stays inside the button. `fit-content` + // rather than `auto` keeps a flex or grid parent from stretching it. padding: '8px 16px', width: 'auto', - height: 'auto', + height: 'fit-content', minWidth: 48, minHeight: 48, }, @@ -116,7 +117,7 @@ const FabRoot = styled(ButtonBase, { props: { variant: 'extended', size: 'small' }, style: { width: 'auto', - padding: '4px 8px', + padding: '2px 8px', borderRadius: 34 / 2, minWidth: 34, minHeight: 34, diff --git a/packages/mui-material/src/Fab/Fab.test.js b/packages/mui-material/src/Fab/Fab.test.js index 5eac006a58cd92..b21dec2ccf8030 100644 --- a/packages/mui-material/src/Fab/Fab.test.js +++ b/packages/mui-material/src/Fab/Fab.test.js @@ -97,6 +97,39 @@ describe('', () => { expect(button).to.have.class(classes.sizeMedium); }); + // JSDOM does no layout, so the one-line heights are only measurable in a real + // browser. The content is 1.75em tall whatever the font family, so these are + // the minimums rather than anything font-dependent. + it.skipIf(isJsdom())('keeps the one-line height of each extended size', () => { + render( + + + Save + + + Save + + + Save + + , + ); + + expect(screen.getByTestId('large').getBoundingClientRect().height).to.equal(48); + expect(screen.getByTestId('medium').getBoundingClientRect().height).to.equal(40); + expect(screen.getByTestId('small').getBoundingClientRect().height).to.equal(34); + }); + + it.skipIf(isJsdom())('does not stretch to fill a flex parent', () => { + render( +
+ Save +
, + ); + + expect(screen.getByRole('button').getBoundingClientRect().height).to.equal(48); + }); + // 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', () => {