diff --git a/docs/data/system/getting-started/the-sx-prop/the-sx-prop.md b/docs/data/system/getting-started/the-sx-prop/the-sx-prop.md
index a3e75054c37355..17b147787e83b6 100644
--- a/docs/data/system/getting-started/the-sx-prop/the-sx-prop.md
+++ b/docs/data/system/getting-started/the-sx-prop/the-sx-prop.md
@@ -179,7 +179,7 @@ Read more on the [Typography page](/system/typography/).
## Responsive values
-All properties associated with the `sx` prop also support responsive values for specific breakpoints.
+All properties associated with the `sx` prop also support responsive values for specific breakpoints and container queries.
Read more on the [Usage page—Responsive values](/system/getting-started/usage/#responsive-values).
diff --git a/docs/data/system/getting-started/usage/ContainerQueries.js b/docs/data/system/getting-started/usage/ContainerQueries.js
new file mode 100644
index 00000000000000..aa1e523a0989e4
--- /dev/null
+++ b/docs/data/system/getting-started/usage/ContainerQueries.js
@@ -0,0 +1,88 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+
+export default function ContainerQueries() {
+ return (
+
+
+
+
+
+
+ 123 Main St, Phoenix AZ
+
+
+ $280,000 — $310,000
+
+
+
+ Confidence score: 85%
+
+
+
+
+ );
+}
diff --git a/docs/data/system/getting-started/usage/ContainerQueries.tsx b/docs/data/system/getting-started/usage/ContainerQueries.tsx
new file mode 100644
index 00000000000000..aa1e523a0989e4
--- /dev/null
+++ b/docs/data/system/getting-started/usage/ContainerQueries.tsx
@@ -0,0 +1,88 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+
+export default function ContainerQueries() {
+ return (
+
+
+
+
+
+
+ 123 Main St, Phoenix AZ
+
+
+ $280,000 — $310,000
+
+
+
+ Confidence score: 85%
+
+
+
+
+ );
+}
diff --git a/docs/data/system/getting-started/usage/usage.md b/docs/data/system/getting-started/usage/usage.md
index 487340ad2b1585..1e5d3de608f2f2 100644
--- a/docs/data/system/getting-started/usage/usage.md
+++ b/docs/data/system/getting-started/usage/usage.md
@@ -291,6 +291,19 @@ The following demo shows how to define a set of breakpoints using the object syn
{{"demo": "BreakpointsAsObject.js"}}
+:::info
+📣 Starting from v6, the object structure supports [container queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries) shorthand with `@`.
+
+We recommend you to check the [browser support](https://caniuse.com/?search=container%20que) before using CSS container queries.
+:::
+
+The shorthand syntax is `@{breakpoint}/{container}`:
+
+- **breakpoint**: a number for `px` unit or a breakpoint key (e.g. `sm`, `md`, `lg`, `xl` for default breakpoints) or a valid CSS value (e.g. `40em`).
+- **container** (optional): the name of the [containment context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries#naming_containment_contexts).
+
+{{"demo": "ContainerQueries.js"}}
+
#### Breakpoints as an array
The second option is to define your breakpoints as an array, from smallest to largest.
diff --git a/docs/public/static/error-codes.json b/docs/public/static/error-codes.json
index a6486f38f45d49..c5acc584689575 100644
--- a/docs/public/static/error-codes.json
+++ b/docs/public/static/error-codes.json
@@ -18,5 +18,6 @@
"17": "MUI: Expected valid input target. Did you use a custom `slots.input` and forget to forward refs? See https://mui.com/r/input-component-ref-interface for more info.",
"18": "MUI: `vars` is a private field used for CSS variables support.\nPlease use another name.",
"19": "MUI: `useColorScheme` must be called under ",
- "20": "MUI: The `experimental_sx` has been moved to `theme.unstable_sx`.For more details, see https://github.com/mui/material-ui/pull/35150."
+ "20": "MUI: The `experimental_sx` has been moved to `theme.unstable_sx`.For more details, see https://github.com/mui/material-ui/pull/35150.",
+ "21": "MUI: The provided shorthand %s is invalid. The format should be `@` or `@/`.\nFor example, `@sm` or `@600` or `@40rem/sidebar`."
}
diff --git a/packages/mui-babel-macros/MuiError.macro.d.ts b/packages/mui-babel-macros/MuiError.macro.d.ts
index 53d894b2241058..54293408e59b1a 100644
--- a/packages/mui-babel-macros/MuiError.macro.d.ts
+++ b/packages/mui-babel-macros/MuiError.macro.d.ts
@@ -1,3 +1,3 @@
export default class MuiError {
- constructor(message: string);
+ constructor(message: string, ...args: string[]);
}
diff --git a/packages/mui-joy/src/styles/defaultTheme.test.js b/packages/mui-joy/src/styles/defaultTheme.test.js
index 25469909a49cd6..80e9c510728db0 100644
--- a/packages/mui-joy/src/styles/defaultTheme.test.js
+++ b/packages/mui-joy/src/styles/defaultTheme.test.js
@@ -9,6 +9,7 @@ describe('defaultTheme', () => {
'colorSchemeSelector',
'defaultColorScheme',
'breakpoints',
+ 'containerQueries',
'components',
'colorSchemes',
'focus',
@@ -46,4 +47,11 @@ describe('defaultTheme', () => {
expect(defaultTheme.palette.mode).to.equal('light');
expect(defaultTheme.palette.colorScheme).to.equal('light');
});
+
+ it('has `containerQueries` in the theme', () => {
+ expect(defaultTheme.containerQueries('sidebar').up('sm')).to.equal(
+ '@container sidebar (min-width:600px)',
+ );
+ expect(defaultTheme.containerQueries.up(300)).to.equal('@container (min-width:300px)');
+ });
});
diff --git a/packages/mui-joy/src/styles/extendTheme.test.js b/packages/mui-joy/src/styles/extendTheme.test.js
index c13f29d5c99315..4fcbb49a26e0a3 100644
--- a/packages/mui-joy/src/styles/extendTheme.test.js
+++ b/packages/mui-joy/src/styles/extendTheme.test.js
@@ -10,6 +10,7 @@ describe('extendTheme', () => {
expect([
'attribute',
'breakpoints',
+ 'containerQueries',
'colorSchemeSelector',
'components',
'colorSchemes',
diff --git a/packages/mui-joy/src/styles/extendTheme.ts b/packages/mui-joy/src/styles/extendTheme.ts
index 7404c27310ef29..eb73ba81d40c2d 100644
--- a/packages/mui-joy/src/styles/extendTheme.ts
+++ b/packages/mui-joy/src/styles/extendTheme.ts
@@ -10,6 +10,7 @@ import {
unstable_styleFunctionSx as styleFunctionSx,
SxConfig,
} from '@mui/system';
+import cssContainerQueries from '@mui/system/cssContainerQueries';
import { unstable_applyStyles as applyStyles } from '@mui/system/createTheme';
import { createUnarySpacing } from '@mui/system/spacing';
import defaultSxConfig from './sxConfig';
@@ -559,7 +560,7 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme {
? deepmerge(defaultScales, scalesInput)
: defaultScales;
- const theme = {
+ let theme = {
colorSchemes,
defaultColorScheme: 'light',
...mergedScales,
@@ -605,6 +606,7 @@ export default function extendTheme(themeOptions?: CssVarsThemeOptions): Theme {
getCssVar,
spacing: getSpacingVal(spacing),
} as unknown as Theme & { attribute: string; colorSchemeSelector: string }; // Need type casting due to module augmentation inside the repo
+ theme = cssContainerQueries(theme);
/**
Color channels generation
diff --git a/packages/mui-joy/src/styles/types/theme.ts b/packages/mui-joy/src/styles/types/theme.ts
index a75ec6487cba59..78e83cf59dde2d 100644
--- a/packages/mui-joy/src/styles/types/theme.ts
+++ b/packages/mui-joy/src/styles/types/theme.ts
@@ -1,6 +1,7 @@
import { OverridableStringUnion } from '@mui/types';
import {
Breakpoints,
+ CssContainerQueries,
Spacing,
SxProps as SystemSxProps,
SystemProps as SystemSystemProps,
@@ -95,7 +96,7 @@ export type TextColor =
export type ThemeCssVar = OverridableStringUnion, ThemeCssVarOverrides>;
-export interface Theme extends ThemeScales, RuntimeColorSystem {
+export interface Theme extends ThemeScales, RuntimeColorSystem, CssContainerQueries {
colorSchemes: Record;
defaultColorScheme: DefaultColorScheme | ExtendedColorScheme;
focus: Focus;
diff --git a/packages/mui-material/src/styles/experimental_extendTheme.test.js b/packages/mui-material/src/styles/experimental_extendTheme.test.js
index 297418eea7ada1..0a942ae032eb23 100644
--- a/packages/mui-material/src/styles/experimental_extendTheme.test.js
+++ b/packages/mui-material/src/styles/experimental_extendTheme.test.js
@@ -408,6 +408,16 @@ describe('experimental_extendTheme', () => {
});
});
+ describe('container queries', () => {
+ it('should generate container queries', () => {
+ const theme = extendTheme();
+ expect(theme.containerQueries('sidebar').up('sm')).to.equal(
+ '@container sidebar (min-width:600px)',
+ );
+ expect(theme.containerQueries.up(300)).to.equal('@container (min-width:300px)');
+ });
+ });
+
it('shallow merges multiple arguments', () => {
const theme = extendTheme({ foo: 'I am foo' }, { bar: 'I am bar' });
expect(theme.foo).to.equal('I am foo');
diff --git a/packages/mui-system/src/breakpoints/breakpoints.js b/packages/mui-system/src/breakpoints/breakpoints.js
index 4d6fdf84b3eaba..ee98789726298e 100644
--- a/packages/mui-system/src/breakpoints/breakpoints.js
+++ b/packages/mui-system/src/breakpoints/breakpoints.js
@@ -1,6 +1,7 @@
import PropTypes from 'prop-types';
import deepmerge from '@mui/utils/deepmerge';
import merge from '../merge';
+import { isCqShorthand, getContainerQuery } from '../cssContainerQueries';
// The breakpoint **start** at this value.
// For instance with the first breakpoint xs: [xs, sm[.
@@ -19,6 +20,20 @@ const defaultBreakpoints = {
up: (key) => `@media (min-width:${values[key]}px)`,
};
+const defaultContainerQueries = {
+ containerQueries: (containerName) => ({
+ up: (key) => {
+ let result = typeof key === 'number' ? key : values[key] || key;
+ if (typeof result === 'number') {
+ result = `${result}px`;
+ }
+ return containerName
+ ? `@container ${containerName} (min-width:${result})`
+ : `@container (min-width:${result})`;
+ },
+ }),
+};
+
export function handleBreakpoints(props, propValue, styleFromPropValue) {
const theme = props.theme || {};
@@ -33,8 +48,17 @@ export function handleBreakpoints(props, propValue, styleFromPropValue) {
if (typeof propValue === 'object') {
const themeBreakpoints = theme.breakpoints || defaultBreakpoints;
return Object.keys(propValue).reduce((acc, breakpoint) => {
+ if (isCqShorthand(themeBreakpoints.keys, breakpoint)) {
+ const containerKey = getContainerQuery(
+ theme.containerQueries ? theme : defaultContainerQueries,
+ breakpoint,
+ );
+ if (containerKey) {
+ acc[containerKey] = styleFromPropValue(propValue[breakpoint], breakpoint);
+ }
+ }
// key is breakpoint
- if (Object.keys(themeBreakpoints.values || values).indexOf(breakpoint) !== -1) {
+ else if (Object.keys(themeBreakpoints.values || values).indexOf(breakpoint) !== -1) {
const mediaKey = themeBreakpoints.up(breakpoint);
acc[mediaKey] = styleFromPropValue(propValue[breakpoint], breakpoint);
} else {
diff --git a/packages/mui-system/src/createTheme/createTheme.d.ts b/packages/mui-system/src/createTheme/createTheme.d.ts
index aee763ec114398..0d6d555539d1b9 100644
--- a/packages/mui-system/src/createTheme/createTheme.d.ts
+++ b/packages/mui-system/src/createTheme/createTheme.d.ts
@@ -4,6 +4,7 @@ import { Shape, ShapeOptions } from './shape';
import { Spacing, SpacingOptions } from './createSpacing';
import { SxConfig, SxProps } from '../styleFunctionSx';
import { ApplyStyles } from './applyStyles';
+import { CssContainerQueries } from '../cssContainerQueries';
export { Breakpoint, BreakpointOverrides } from './createBreakpoints';
@@ -24,7 +25,7 @@ export interface ThemeOptions {
unstable_sxConfig?: SxConfig;
}
-export interface Theme {
+export interface Theme extends CssContainerQueries {
shape: Shape;
breakpoints: Breakpoints;
direction: Direction;
diff --git a/packages/mui-system/src/createTheme/createTheme.js b/packages/mui-system/src/createTheme/createTheme.js
index 24c1c4d29971cc..dce4ff93cc428d 100644
--- a/packages/mui-system/src/createTheme/createTheme.js
+++ b/packages/mui-system/src/createTheme/createTheme.js
@@ -1,5 +1,6 @@
import deepmerge from '@mui/utils/deepmerge';
import createBreakpoints from './createBreakpoints';
+import cssContainerQueries from '../cssContainerQueries';
import shape from './shape';
import createSpacing from './createSpacing';
import styleFunctionSx from '../styleFunctionSx/styleFunctionSx';
@@ -29,6 +30,7 @@ function createTheme(options = {}, ...args) {
},
other,
);
+ muiTheme = cssContainerQueries(muiTheme);
muiTheme.applyStyles = applyStyles;
diff --git a/packages/mui-system/src/cssContainerQueries/cssContainerQueries.test.ts b/packages/mui-system/src/cssContainerQueries/cssContainerQueries.test.ts
new file mode 100644
index 00000000000000..785aa3186732c7
--- /dev/null
+++ b/packages/mui-system/src/cssContainerQueries/cssContainerQueries.test.ts
@@ -0,0 +1,119 @@
+import { expect } from 'chai';
+
+import createTheme from '@mui/system/createTheme';
+import {
+ isCqShorthand,
+ sortContainerQueries,
+ getContainerQuery,
+} from '@mui/system/cssContainerQueries';
+
+describe('cssContainerQueries', () => {
+ it('should return false if the shorthand is not a container query', () => {
+ expect(isCqShorthand(['xs', 'sm', 'md'], '@container (min-width:600px)')).to.equal(false);
+ expect(isCqShorthand(['xs', 'sm', 'md'], '@media (min-width:600px)')).to.equal(false);
+ expect(isCqShorthand(['xs', 'sm', 'md'], '@page')).to.equal(false);
+ expect(isCqShorthand(['xs', 'sm', 'md'], '@support (display: flex)')).to.equal(false);
+ });
+
+ it('should return true if the shorthand is a container query', () => {
+ expect(isCqShorthand(['xs', 'sm', 'md'], '@xs')).to.equal(true);
+ expect(isCqShorthand(['xs', 'sm', 'md'], '@xs/sidebar')).to.equal(true);
+ expect(isCqShorthand(['xs', 'sm', 'md'], '@md')).to.equal(true);
+ expect(isCqShorthand(['xs', 'sm', 'md'], '@200')).to.equal(true);
+ expect(isCqShorthand(['xs', 'sm', 'md'], '@15.5rem')).to.equal(true);
+ });
+
+ it('should have `up`, `down`, `between`, `only`, and `not` functions', () => {
+ const theme = createTheme();
+
+ expect(theme.containerQueries.up('sm')).to.equal('@container (min-width:600px)');
+ expect(theme.containerQueries.down('sm')).to.equal('@container (max-width:599.95px)');
+ expect(theme.containerQueries.between('sm', 'md')).to.equal(
+ '@container (min-width:600px) and (max-width:899.95px)',
+ );
+ expect(theme.containerQueries.only('sm')).to.equal(
+ '@container (min-width:600px) and (max-width:899.95px)',
+ );
+ expect(theme.containerQueries.not('xs')).to.equal('@container (min-width:600px)');
+ expect(theme.containerQueries.not('xl')).to.equal('@container (max-width:1535.95px)');
+ expect(theme.containerQueries.not('md')).to.equal(
+ '@container (width<900px) and (width>1199.95px)',
+ );
+ });
+
+ it('should be able to create named containment context', () => {
+ const theme = createTheme();
+
+ expect(theme.containerQueries('sidebar').up('sm')).to.equal(
+ '@container sidebar (min-width:600px)',
+ );
+ expect(theme.containerQueries('sidebar').down('sm')).to.equal(
+ '@container sidebar (max-width:599.95px)',
+ );
+ expect(theme.containerQueries('sidebar').between('sm', 'md')).to.equal(
+ '@container sidebar (min-width:600px) and (max-width:899.95px)',
+ );
+ expect(theme.containerQueries('sidebar').only('sm')).to.equal(
+ '@container sidebar (min-width:600px) and (max-width:899.95px)',
+ );
+ expect(theme.containerQueries('sidebar').not('xs')).to.equal(
+ '@container sidebar (min-width:600px)',
+ );
+ expect(theme.containerQueries('sidebar').not('xl')).to.equal(
+ '@container sidebar (max-width:1535.95px)',
+ );
+ expect(theme.containerQueries('sidebar').not('sm')).to.equal(
+ '@container sidebar (width<600px) and (width>899.95px)',
+ );
+ });
+
+ it('should sort container queries', () => {
+ const theme = createTheme();
+
+ const css = {
+ '@container (min-width:960px)': {},
+ '@container (min-width:1280px)': {},
+ '@container (min-width:0px)': {},
+ '@container (min-width:600px)': {},
+ };
+
+ const sorted = sortContainerQueries(theme, css);
+
+ expect(Object.keys(sorted)).to.deep.equal([
+ '@container (min-width:0px)',
+ '@container (min-width:600px)',
+ '@container (min-width:960px)',
+ '@container (min-width:1280px)',
+ ]);
+ });
+
+ it('should sort container queries with other unit', () => {
+ const theme = createTheme();
+
+ const css = {
+ '@container (min-width:30.5rem)': {},
+ '@container (min-width:20rem)': {},
+ '@container (min-width:50.5rem)': {},
+ '@container (min-width:40rem)': {},
+ };
+
+ const sorted = sortContainerQueries(theme, css);
+
+ expect(Object.keys(sorted)).to.deep.equal([
+ '@container (min-width:20rem)',
+ '@container (min-width:30.5rem)',
+ '@container (min-width:40rem)',
+ '@container (min-width:50.5rem)',
+ ]);
+ });
+
+ it('should throw an error if shorthand is invalid', () => {
+ expect(() => {
+ const theme = createTheme();
+ getContainerQuery(theme, 'cq0');
+ }).to.throw(
+ 'MUI: The provided shorthand (cq0) is invalid. The format should be `@` or `@/`.\n' +
+ 'For example, `@sm` or `@600` or `@40rem/sidebar`.',
+ );
+ });
+});
diff --git a/packages/mui-system/src/cssContainerQueries/cssContainerQueries.ts b/packages/mui-system/src/cssContainerQueries/cssContainerQueries.ts
new file mode 100644
index 00000000000000..49b85f558cff00
--- /dev/null
+++ b/packages/mui-system/src/cssContainerQueries/cssContainerQueries.ts
@@ -0,0 +1,118 @@
+import MuiError from '@mui/internal-babel-macros/MuiError.macro';
+import { Breakpoints, Breakpoint } from '../createTheme/createBreakpoints';
+
+interface ContainerQueries {
+ up: Breakpoints['up'];
+ down: Breakpoints['down'];
+ between: Breakpoints['between'];
+ only: Breakpoints['only'];
+ not: Breakpoints['not'];
+}
+
+export interface CssContainerQueries {
+ containerQueries: ((name: string) => ContainerQueries) & ContainerQueries;
+}
+
+/**
+ * For using in `sx` prop to sort the breakpoint from low to high.
+ * Note: this function does not work and will not support multiple units.
+ * e.g. input: { '@container (min-width:300px)': '1rem', '@container (min-width:40rem)': '2rem' }
+ * output: { '@container (min-width:40rem)': '2rem', '@container (min-width:300px)': '1rem' } // since 40 < 300 eventhough 40rem > 300px
+ */
+export function sortContainerQueries(
+ theme: Partial,
+ css: Record,
+) {
+ if (!theme.containerQueries) {
+ return css;
+ }
+ const sorted = Object.keys(css)
+ .filter((key) => key.startsWith('@container'))
+ .sort((a, b) => {
+ const regex = /min-width:\s*([0-9.]+)/;
+ return +(a.match(regex)?.[1] || 0) - +(b.match(regex)?.[1] || 0);
+ });
+ if (!sorted.length) {
+ return css;
+ }
+ return sorted.reduce(
+ (acc, key) => {
+ const value = css[key];
+ delete acc[key];
+ acc[key] = value;
+ return acc;
+ },
+ { ...css },
+ );
+}
+
+export function isCqShorthand(breakpointKeys: string[], value: string) {
+ return (
+ value.startsWith('@') &&
+ (breakpointKeys.some((key) => value.startsWith(`@${key}`)) || !!value.match(/^@\d/))
+ );
+}
+
+export function getContainerQuery(theme: CssContainerQueries, shorthand: string) {
+ const matches = shorthand.match(/^@([^/]+)\/?(.+)?$/);
+ if (!matches) {
+ if (process.env.NODE_ENV !== 'production') {
+ throw new MuiError(
+ 'MUI: The provided shorthand %s is invalid. The format should be `@` or `@/`.\n' +
+ 'For example, `@sm` or `@600` or `@40rem/sidebar`.',
+ `(${shorthand})`,
+ );
+ }
+ return null;
+ }
+ const [, containerQuery, containerName] = matches;
+ const value = (Number.isNaN(+containerQuery) ? containerQuery : +containerQuery) as
+ | Breakpoint
+ | number;
+ return theme.containerQueries(containerName).up(value);
+}
+
+export default function cssContainerQueries(
+ themeInput: T,
+): T & CssContainerQueries {
+ const toContainerQuery = (mediaQuery: string, name?: string) =>
+ mediaQuery.replace('@media', name ? `@container ${name}` : '@container');
+
+ function attachCq(node: any, name?: string) {
+ node.up = (...args: Parameters) =>
+ toContainerQuery(themeInput.breakpoints.up(...args), name);
+
+ node.down = (...args: Parameters) =>
+ toContainerQuery(themeInput.breakpoints.down(...args), name);
+
+ node.between = (...args: Parameters) =>
+ toContainerQuery(themeInput.breakpoints.between(...args), name);
+
+ node.only = (...args: Parameters) =>
+ toContainerQuery(themeInput.breakpoints.only(...args), name);
+
+ node.not = (...args: Parameters) => {
+ const result = toContainerQuery(themeInput.breakpoints.not(...args), name);
+ if (result.includes('not all and')) {
+ // `@container` does not work with `not all and`, so need to invert the logic
+ return result
+ .replace('not all and ', '')
+ .replace('min-width:', 'width<')
+ .replace('max-width:', 'width>');
+ }
+ return result;
+ };
+ }
+ const node = {};
+ const containerQueries = ((name: string) => {
+ attachCq(node, name);
+ return node;
+ }) as CssContainerQueries['containerQueries'];
+
+ attachCq(containerQueries);
+
+ return {
+ ...themeInput,
+ containerQueries,
+ };
+}
diff --git a/packages/mui-system/src/cssContainerQueries/index.ts b/packages/mui-system/src/cssContainerQueries/index.ts
new file mode 100644
index 00000000000000..e1061e417fc187
--- /dev/null
+++ b/packages/mui-system/src/cssContainerQueries/index.ts
@@ -0,0 +1,3 @@
+export { default } from './cssContainerQueries';
+export { isCqShorthand, getContainerQuery, sortContainerQueries } from './cssContainerQueries';
+export type { CssContainerQueries } from './cssContainerQueries';
diff --git a/packages/mui-system/src/cssGrid/cssGrid.test.js b/packages/mui-system/src/cssGrid/cssGrid.test.js
index a12b8d0e511821..62266bcdfb42c4 100644
--- a/packages/mui-system/src/cssGrid/cssGrid.test.js
+++ b/packages/mui-system/src/cssGrid/cssGrid.test.js
@@ -37,4 +37,25 @@ describe('grid', () => {
},
});
});
+
+ it('should support container queries', () => {
+ const output1 = grid({
+ gap: {
+ '@sm': 1,
+ '@900/sidebar': 2,
+ '@80rem/sidebar': 3,
+ },
+ });
+ expect(output1).to.deep.equal({
+ '@container (min-width:600px)': {
+ gap: 8,
+ },
+ '@container sidebar (min-width:900px)': {
+ gap: 16,
+ },
+ '@container sidebar (min-width:80rem)': {
+ gap: 24,
+ },
+ });
+ });
});
diff --git a/packages/mui-system/src/index.d.ts b/packages/mui-system/src/index.d.ts
index ea3580a981cc9f..ec606cc14d467b 100644
--- a/packages/mui-system/src/index.d.ts
+++ b/packages/mui-system/src/index.d.ts
@@ -5,6 +5,8 @@ export * from './borders';
export { default as breakpoints, handleBreakpoints, mergeBreakpointsInOrder } from './breakpoints';
+export { default as cssContainerQueries, type CssContainerQueries } from './cssContainerQueries';
+
export { default as compose } from './compose';
export * from './display';
diff --git a/packages/mui-system/src/index.js b/packages/mui-system/src/index.js
index b7215ef8dee1b4..8bc93c6d64d0c9 100644
--- a/packages/mui-system/src/index.js
+++ b/packages/mui-system/src/index.js
@@ -5,6 +5,7 @@ export { default as GlobalStyles } from './GlobalStyles';
export { default as borders } from './borders';
export * from './borders';
export { default as breakpoints } from './breakpoints';
+export { default as cssContainerQueries } from './cssContainerQueries';
export {
handleBreakpoints,
mergeBreakpointsInOrder,
diff --git a/packages/mui-system/src/spacing/spacing.test.js b/packages/mui-system/src/spacing/spacing.test.js
index 92f8f5306e94fd..8373c3cbbee208 100644
--- a/packages/mui-system/src/spacing/spacing.test.js
+++ b/packages/mui-system/src/spacing/spacing.test.js
@@ -184,6 +184,27 @@ describe('system spacing', () => {
});
});
+ it('should support container queries', () => {
+ const output1 = spacing({
+ p: {
+ '@sm': 1,
+ '@900/sidebar': 2,
+ '@80rem/sidebar': 3,
+ },
+ });
+ expect(output1).to.deep.equal({
+ '@container (min-width:600px)': {
+ padding: 8,
+ },
+ '@container sidebar (min-width:900px)': {
+ padding: 16,
+ },
+ '@container sidebar (min-width:80rem)': {
+ padding: 24,
+ },
+ });
+ });
+
it('should support full version', () => {
const output1 = spacing({
paddingTop: 1,
diff --git a/packages/mui-system/src/styleFunctionSx/styleFunctionSx.js b/packages/mui-system/src/styleFunctionSx/styleFunctionSx.js
index 151f117c416477..a74b44b553311c 100644
--- a/packages/mui-system/src/styleFunctionSx/styleFunctionSx.js
+++ b/packages/mui-system/src/styleFunctionSx/styleFunctionSx.js
@@ -6,6 +6,7 @@ import {
createEmptyBreakpointObject,
removeUnusedBreakpoints,
} from '../breakpoints';
+import { sortContainerQueries } from '../cssContainerQueries';
import defaultSxConfig from './defaultSxConfig';
function objectsHaveSameKeys(...objects) {
@@ -127,7 +128,7 @@ export function unstable_createStyleFunctionSx() {
}
});
- return removeUnusedBreakpoints(breakpointsKeys, css);
+ return sortContainerQueries(theme, removeUnusedBreakpoints(breakpointsKeys, css));
}
return Array.isArray(sx) ? sx.map(traverse) : traverse(sx);
diff --git a/packages/mui-system/src/styleFunctionSx/styleFunctionSx.test.js b/packages/mui-system/src/styleFunctionSx/styleFunctionSx.test.js
index dced5959cb72aa..d8ca1d7a8e778e 100644
--- a/packages/mui-system/src/styleFunctionSx/styleFunctionSx.test.js
+++ b/packages/mui-system/src/styleFunctionSx/styleFunctionSx.test.js
@@ -1,5 +1,6 @@
import { expect } from 'chai';
import styleFunctionSx from './styleFunctionSx';
+import cssContainerQueries from '../cssContainerQueries';
describe('styleFunctionSx', () => {
const breakpointsValues = {
@@ -12,7 +13,7 @@ describe('styleFunctionSx', () => {
const round = (value) => Math.round(value * 1e5) / 1e5;
- const theme = {
+ const theme = cssContainerQueries({
spacing: (val) => `${val * 10}px`,
breakpoints: {
keys: ['xs', 'sm', 'md', 'lg', 'xl'],
@@ -49,7 +50,7 @@ describe('styleFunctionSx', () => {
lineHeight: 1.43,
},
},
- };
+ });
describe('system', () => {
it('resolves system ', () => {
@@ -243,6 +244,79 @@ describe('styleFunctionSx', () => {
});
});
+ describe('container queries', () => {
+ const queriesExpectedResult = {
+ '@container (min-width:0px)': { border: '1px solid' },
+ '@container (min-width:600px)': { border: '2px solid' },
+ '@container (min-width:960px)': { border: '3px solid' },
+ '@container (min-width:1280px)': { border: '4px solid' },
+ '@container (min-width:1920px)': { border: '5px solid' },
+ };
+
+ it('resolves queries object', () => {
+ const result = styleFunctionSx({
+ theme,
+ sx: {
+ border: {
+ '@xs': 1,
+ '@sm': 2,
+ '@md': 3,
+ '@lg': 4,
+ '@xl': 5,
+ },
+ },
+ });
+
+ expect(result).to.deep.equal(queriesExpectedResult);
+ });
+
+ it('merges multiple queries object', () => {
+ const result = styleFunctionSx({
+ theme,
+ sx: {
+ m: {
+ '@xs': 1,
+ '@sm': 2,
+ '@md': 3,
+ },
+ p: {
+ '@xs': 5,
+ '@sm': 6,
+ '@md': 7,
+ },
+ },
+ });
+
+ expect(result).to.deep.equal({
+ '@container (min-width:0px)': { padding: '50px', margin: '10px' },
+ '@container (min-width:600px)': { padding: '60px', margin: '20px' },
+ '@container (min-width:960px)': { padding: '70px', margin: '30px' },
+ });
+ });
+
+ it('writes queries in correct order', () => {
+ const result = styleFunctionSx({
+ theme,
+ sx: { m: { '@md': 1, '@lg': 2 }, p: { '@xs': 0, '@sm': 1, '@md': 2 } },
+ });
+
+ // Test the order
+ expect(Object.keys(result)).to.deep.equal([
+ '@container (min-width:0px)',
+ '@container (min-width:600px)',
+ '@container (min-width:960px)',
+ '@container (min-width:1280px)',
+ ]);
+
+ expect(result).to.deep.equal({
+ '@container (min-width:0px)': { padding: '0px' },
+ '@container (min-width:600px)': { padding: '10px' },
+ '@container (min-width:960px)': { padding: '20px', margin: '10px' },
+ '@container (min-width:1280px)': { margin: '20px' },
+ });
+ });
+ });
+
describe('theme callback', () => {
it('works on CSS properties', () => {
const result = styleFunctionSx({