diff --git a/docs/data/system/components/grid/OverflowGrid.js b/docs/data/system/components/grid/OverflowGrid.js
new file mode 100644
index 00000000000000..47b49aafed5012
--- /dev/null
+++ b/docs/data/system/components/grid/OverflowGrid.js
@@ -0,0 +1,54 @@
+import * as React from 'react';
+import Box from '@mui/system/Box';
+import Grid from '@mui/system/Unstable_Grid';
+import styled from '@mui/system/styled';
+
+const Item = styled('div')(({ theme }) => ({
+ backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
+ border: '1px solid',
+ borderColor: theme.palette.mode === 'dark' ? '#444d58' : '#ced7e0',
+ padding: theme.spacing(1),
+ borderRadius: '4px',
+ textAlign: 'center',
+}));
+
+export default function AutoGrid() {
+ return (
+ ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 3,
+ '& > div': {
+ overflow: 'auto hidden',
+ '&::-webkit-scrollbar': { height: 10, WebkitAppearance: 'none' },
+ '&::-webkit-scrollbar-thumb': {
+ borderRadius: 8,
+ border: '2px solid',
+ borderColor: theme.palette.mode === 'dark' ? '' : '#E7EBF0',
+ backgroundColor: 'rgba(0 0 0 / 0.5)',
+ },
+ },
+ })}
+ >
+
+
+
+ - Scroll bar appears
+
+
+
+
+
+
+ - `disableEqualOverflow` prevents scrollbar
+
+
+
+
+ );
+}
diff --git a/docs/data/system/components/grid/OverflowGrid.tsx b/docs/data/system/components/grid/OverflowGrid.tsx
new file mode 100644
index 00000000000000..47b49aafed5012
--- /dev/null
+++ b/docs/data/system/components/grid/OverflowGrid.tsx
@@ -0,0 +1,54 @@
+import * as React from 'react';
+import Box from '@mui/system/Box';
+import Grid from '@mui/system/Unstable_Grid';
+import styled from '@mui/system/styled';
+
+const Item = styled('div')(({ theme }) => ({
+ backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
+ border: '1px solid',
+ borderColor: theme.palette.mode === 'dark' ? '#444d58' : '#ced7e0',
+ padding: theme.spacing(1),
+ borderRadius: '4px',
+ textAlign: 'center',
+}));
+
+export default function AutoGrid() {
+ return (
+ ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 3,
+ '& > div': {
+ overflow: 'auto hidden',
+ '&::-webkit-scrollbar': { height: 10, WebkitAppearance: 'none' },
+ '&::-webkit-scrollbar-thumb': {
+ borderRadius: 8,
+ border: '2px solid',
+ borderColor: theme.palette.mode === 'dark' ? '' : '#E7EBF0',
+ backgroundColor: 'rgba(0 0 0 / 0.5)',
+ },
+ },
+ })}
+ >
+
+
+
+ - Scroll bar appears
+
+
+
+
+
+
+ - `disableEqualOverflow` prevents scrollbar
+
+
+
+
+ );
+}
diff --git a/docs/data/system/components/grid/grid.md b/docs/data/system/components/grid/grid.md
index 0e28986debcae5..cb381fe49c509e 100644
--- a/docs/data/system/components/grid/grid.md
+++ b/docs/data/system/components/grid/grid.md
@@ -156,6 +156,18 @@ declare module '@mui/system' {
}
```
+## Prevent scrollbar
+
+If you use grid as a container in a small viewport, you might see a horizontal scrollbar because the negative margin is applied on all sides of the grid container.
+
+To prevent the scrollbar, set `disableEqualOverflow` prop to `true`. It will enable negative margin only on the top and left sides of the grid which remove overflow on the right-hand side.
+
+{{"demo": "OverflowGrid.js", "bg": true}}
+
+:::warning
+You should avoid adding borders or background to the grid when `disableEqualOverflow: true` because the negative margin (applied only at the top and left sides) makes the grid visually misaligned.
+:::
+
## Limitations
### direction column and column-reverse
diff --git a/docs/pages/system/api/grid.json b/docs/pages/system/api/grid.json
index f57038887ffade..566f0fa04968bf 100644
--- a/docs/pages/system/api/grid.json
+++ b/docs/pages/system/api/grid.json
@@ -20,6 +20,7 @@
"description": "'column-reverse'
| 'column'
| 'row-reverse'
| 'row'
| Array<'column-reverse'
| 'column'
| 'row-reverse'
| 'row'>
| object"
}
},
+ "disableEqualOverflow": { "type": { "name": "bool" } },
"lg": {
"type": { "name": "union", "description": "'auto'
| number
| bool" }
},
diff --git a/docs/translations/api-docs/grid/grid.json b/docs/translations/api-docs/grid/grid.json
index 0698e6942801b8..5f47be155550d5 100644
--- a/docs/translations/api-docs/grid/grid.json
+++ b/docs/translations/api-docs/grid/grid.json
@@ -6,6 +6,7 @@
"columnSpacing": "Defines the horizontal space between the type item
components. It overrides the value of the spacing
prop.",
"container": "If true
, the component will have the flex container behavior. You should be wrapping items with a container.",
"direction": "Defines the flex-direction
style property. It is applied for all screen sizes.",
+ "disableEqualOverflow": "If true
, the negative margin and padding are apply only to the top and left sides of the grid.",
"lg": "If a number, it sets the number of columns the grid item uses. It can't be greater than the total number of columns of the container (12 by default). If 'auto', the grid item's width matches its content. If false, the prop is ignored. If true, the grid item's width grows to use the space available in the grid container. The value is applied for the lg
breakpoint and wider screens if not overridden.",
"lgOffset": "If a number, it sets the margin-left equals to the number of columns the grid item uses. If 'auto', the grid item push itself to the right-end of the container. The value is applied for the lg
breakpoint and wider screens if not overridden.",
"md": "If a number, it sets the number of columns the grid item uses. It can't be greater than the total number of columns of the container (12 by default). If 'auto', the grid item's width matches its content. If false, the prop is ignored. If true, the grid item's width grows to use the space available in the grid container. The value is applied for the md
breakpoint and wider screens if not overridden.",
diff --git a/packages/mui-system/src/Unstable_Grid/Grid.test.js b/packages/mui-system/src/Unstable_Grid/Grid.test.js
index 5299976d42680f..b9f706a530362b 100644
--- a/packages/mui-system/src/Unstable_Grid/Grid.test.js
+++ b/packages/mui-system/src/Unstable_Grid/Grid.test.js
@@ -211,6 +211,71 @@ describe('System ', () => {
});
});
+ describe('prop: disableEqualOverflow', () => {
+ it('should apply to top and left sides only', function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+ const { container } = render(
+
+
+ ,
+ );
+
+ expect(container.firstChild).toHaveComputedStyle({
+ marginTop: '-16px',
+ marginLeft: '-16px',
+ });
+ expect(container.firstChild.firstChild).toHaveComputedStyle({
+ paddingTop: '16px',
+ paddingLeft: '16px',
+ });
+ });
+
+ it('should use the value from theme and nestable', function test() {
+ if (/jsdom/.test(window.navigator.userAgent)) {
+ this.skip();
+ }
+ const { container } = render(
+
+
+
+
+
+
+ ,
+ );
+ expect(container.firstChild).toHaveComputedStyle({
+ marginTop: '-16px',
+ marginLeft: '-16px',
+ });
+ expect(container.firstChild.firstChild).toHaveComputedStyle({
+ marginTop: '-12px',
+ marginLeft: '-12px',
+ marginRight: '-12px',
+ marginBottom: '-12px',
+ paddingTop: '16px',
+ paddingLeft: '16px',
+ });
+ expect(container.firstChild.firstChild.firstChild).toHaveComputedStyle({
+ paddingTop: '12px',
+ paddingLeft: '12px',
+ paddingRight: '12px',
+ paddingBottom: '12px',
+ });
+ });
+ });
+
describe('Custom breakpoints', () => {
it('should apply the custom breakpoint class', () => {
const { container } = render(
diff --git a/packages/mui-system/src/Unstable_Grid/Grid.tsx b/packages/mui-system/src/Unstable_Grid/Grid.tsx
index 97670cb6fb6af8..ffe3f614bb0735 100644
--- a/packages/mui-system/src/Unstable_Grid/Grid.tsx
+++ b/packages/mui-system/src/Unstable_Grid/Grid.tsx
@@ -56,6 +56,10 @@ Grid.propTypes /* remove-proptypes */ = {
PropTypes.arrayOf(PropTypes.oneOf(['column-reverse', 'column', 'row-reverse', 'row'])),
PropTypes.object,
]),
+ /**
+ * If `true`, the negative margin and padding are apply only to the top and left sides of the grid.
+ */
+ disableEqualOverflow: PropTypes.bool,
/**
* If a number, it sets the number of columns the grid item uses.
* It can't be greater than the total number of columns of the container (12 by default).
diff --git a/packages/mui-system/src/Unstable_Grid/GridProps.ts b/packages/mui-system/src/Unstable_Grid/GridProps.ts
index 3169d5559da434..f4456f15edafb4 100644
--- a/packages/mui-system/src/Unstable_Grid/GridProps.ts
+++ b/packages/mui-system/src/Unstable_Grid/GridProps.ts
@@ -137,6 +137,10 @@ export interface GridBaseProps extends Breakpoints {
* @default 'row'
*/
direction?: ResponsiveStyleValue;
+ /**
+ * If `true`, the negative margin and padding are apply only to the top and left sides of the grid.
+ */
+ disableEqualOverflow?: boolean;
/**
* Defines the vertical space between the type `item` components.
* It overrides the value of the `spacing` prop.
diff --git a/packages/mui-system/src/Unstable_Grid/createGrid.tsx b/packages/mui-system/src/Unstable_Grid/createGrid.tsx
index ce3b38eac2e4b1..6123d164bd80d0 100644
--- a/packages/mui-system/src/Unstable_Grid/createGrid.tsx
+++ b/packages/mui-system/src/Unstable_Grid/createGrid.tsx
@@ -58,6 +58,7 @@ export default function createGrid(
} = options;
const NestedContext = React.createContext(false);
+ const OverflowContext = React.createContext(undefined);
const useUtilityClasses = (ownerState: GridOwnerState, theme: typeof defaultTheme) => {
const { container, direction, spacing, wrap, gridSize } = ownerState;
@@ -93,6 +94,7 @@ export default function createGrid(
const themeProps = useThemeProps(inProps);
const props = extendSxProp(themeProps) as Omit; // `color` type conflicts with html color attribute.
const nested = React.useContext(NestedContext);
+ const overflow = React.useContext(OverflowContext);
const {
className,
columns: columnsProp = 12,
@@ -103,9 +105,15 @@ export default function createGrid(
spacing: spacingProp = 0,
rowSpacing: rowSpacingProp = spacingProp,
columnSpacing: columnSpacingProp = spacingProp,
+ disableEqualOverflow: themeDisableEqualOverflow,
...rest
} = props;
- // collect breakpoints related props because they can be custom from the theme.
+ // Because `disableEqualOverflow` can be set from the theme's defaultProps, the **nested** grid should look at the instance props instead.
+ let disableEqualOverflow = themeDisableEqualOverflow;
+ if (nested && themeDisableEqualOverflow !== undefined) {
+ disableEqualOverflow = inProps.disableEqualOverflow;
+ }
+ // collect breakpoints related props because they can be customized from the theme.
const gridSize = {} as GridOwnerState['gridSize'];
const gridOffset = {} as GridOwnerState['gridOffset'];
const other: Record = {};
@@ -138,6 +146,8 @@ export default function createGrid(
columnSpacing,
gridSize,
gridOffset,
+ disableEqualOverflow: disableEqualOverflow ?? overflow ?? false, // use context value if exists.
+ parentDisableEqualOverflow: overflow, // for nested grid
};
const classes = useUtilityClasses(ownerState, theme);
@@ -156,6 +166,15 @@ export default function createGrid(
result = {result};
}
+ if (disableEqualOverflow !== undefined && disableEqualOverflow !== (overflow ?? false)) {
+ // There are 2 possibilities that should wrap with the OverflowContext to communicate with the nested grids:
+ // 1. It is the root grid with `disableEqualOverflow`.
+ // 2. It is a nested grid with different `disableEqualOverflow` from the context.
+ result = (
+ {result}
+ );
+ }
+
return result;
}) as OverridableComponent;
@@ -180,6 +199,7 @@ export default function createGrid(
PropTypes.arrayOf(PropTypes.oneOf(['column-reverse', 'column', 'row-reverse', 'row'])),
PropTypes.object,
]),
+ disableEqualOverflow: PropTypes.bool,
lg: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.bool]),
lgOffset: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]),
md: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.bool]),
diff --git a/packages/mui-system/src/Unstable_Grid/gridGenerator.test.js b/packages/mui-system/src/Unstable_Grid/gridGenerator.test.js
index edcc16b8585aea..0319dcb738820a 100644
--- a/packages/mui-system/src/Unstable_Grid/gridGenerator.test.js
+++ b/packages/mui-system/src/Unstable_Grid/gridGenerator.test.js
@@ -250,6 +250,49 @@ describe('grid generator', () => {
padding: `calc(var(--Grid-nested-rowSpacing) / 2) calc(var(--Grid-nested-columnSpacing) / 2)`,
});
});
+
+ it('root container with disableEqualOverflow', () => {
+ const result = generateGridStyles({
+ ownerState: { container: true, nested: true, disableEqualOverflow: true },
+ });
+ sinon.assert.match(result, {
+ margin: `calc(var(--Grid-rowSpacing) * -1) 0px 0px calc(var(--Grid-columnSpacing) * -1)`,
+ padding: `calc(var(--Grid-nested-rowSpacing)) 0px 0px calc(var(--Grid-nested-columnSpacing))`,
+ });
+ });
+
+ it('nested container without disableEqualOverflow but parent has', () => {
+ const result = generateGridStyles({
+ ownerState: {
+ container: true,
+ nested: true,
+ disableEqualOverflow: false,
+ parentDisableEqualOverflow: true,
+ },
+ });
+ sinon.assert.match(result, {
+ margin: `calc(var(--Grid-rowSpacing) / -2) calc(var(--Grid-columnSpacing) / -2)`,
+ padding: `calc(var(--Grid-nested-rowSpacing)) 0px 0px calc(var(--Grid-nested-columnSpacing))`,
+ });
+ });
+
+ it('item', () => {
+ const result = generateGridStyles({ ownerState: { container: false, nested: false } });
+ expect(result).to.deep.equal({
+ minWidth: 0,
+ boxSizing: 'border-box',
+ padding: `calc(var(--Grid-rowSpacing) / 2) calc(var(--Grid-columnSpacing) / 2)`,
+ });
+ });
+
+ it('item with disableEqualOverflow', () => {
+ const result = generateGridStyles({
+ ownerState: { container: false, disableEqualOverflow: true },
+ });
+ sinon.assert.match(result, {
+ padding: `calc(var(--Grid-rowSpacing)) 0px 0px calc(var(--Grid-columnSpacing))`,
+ });
+ });
});
describe('generateGridSizeStyles', () => {
diff --git a/packages/mui-system/src/Unstable_Grid/gridGenerator.ts b/packages/mui-system/src/Unstable_Grid/gridGenerator.ts
index ea0bb974502c19..2618b42638b5b4 100644
--- a/packages/mui-system/src/Unstable_Grid/gridGenerator.ts
+++ b/packages/mui-system/src/Unstable_Grid/gridGenerator.ts
@@ -194,9 +194,15 @@ export const generateGridStyles = ({ ownerState }: Props): {} => {
flexWrap: ownerState.wrap,
}),
margin: `calc(var(--Grid-rowSpacing) / -2) calc(var(--Grid-columnSpacing) / -2)`,
+ ...(ownerState.disableEqualOverflow && {
+ margin: `calc(var(--Grid-rowSpacing) * -1) 0px 0px calc(var(--Grid-columnSpacing) * -1)`,
+ }),
...(ownerState.nested
? {
padding: `calc(var(--Grid-nested-rowSpacing) / 2) calc(var(--Grid-nested-columnSpacing) / 2)`,
+ ...((ownerState.disableEqualOverflow || ownerState.parentDisableEqualOverflow) && {
+ padding: `calc(var(--Grid-nested-rowSpacing)) 0px 0px calc(var(--Grid-nested-columnSpacing))`,
+ }),
}
: {
'--Grid-nested-rowSpacing': 'var(--Grid-rowSpacing)',
@@ -205,6 +211,9 @@ export const generateGridStyles = ({ ownerState }: Props): {} => {
}
: {
padding: `calc(var(--Grid-rowSpacing) / 2) calc(var(--Grid-columnSpacing) / 2)`,
+ ...(ownerState.disableEqualOverflow && {
+ padding: `calc(var(--Grid-rowSpacing)) 0px 0px calc(var(--Grid-columnSpacing))`,
+ }),
}),
};
};