Container queries

Container queries (컨테이너 쿼리)

Material UI는 테마의 브레이크포인트(breakpoints)를 바탕으로 CSS 컨테이너 쿼리를 만들어주는 유틸리티 함수를 제공해요. 화면 크기만 보던 기존 방식에서 벗어나, 부모 컨테이너의 크기에 반응하는 레이아웃을 손쉽게 만들 수 있답니다.

출처: 문서

본문

Usage (사용법)

CSS 컨테이너 쿼리를 만들려면, theme.breakpoints에서 제공하는 어떤 메서드와 함께 theme.containerQueries를 사용하면 돼요. 값은 단위가 없는 숫자(이 경우 픽셀로 렌더링돼요), 문자열, 또는 브레이크포인트 키가 될 수 있어요. 예를 들어:

theme.containerQueries.up('sm'); // => '@container (min-width: 600px)'
import { styled } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Chip from '@mui/material/Chip';
import Typography from '@mui/material/Typography';
import ResizableDemo from './ResizableDemo';

const DynamicCard = styled(Card)(({ theme }) => ({
  display: 'flex',
  flexDirection: 'column',
  [theme.containerQueries.up(350)]: {
    flexDirection: 'row',
  },
}));

const Image = styled('img')(({ theme }) => ({
  alignSelf: 'stretch',
  aspectRatio: '16 / 9',
  objectFit: 'cover',
  width: '100%',
  maxHeight: 160,
  transition: '0.4s',
  [theme.containerQueries.up(350)]: {
    maxWidth: '36%',
    maxHeight: 'initial',
  },
  [theme.containerQueries.up(500)]: {
    maxWidth: 240,
  },
}));

const Content = styled(CardContent)(({ theme }) => ({
  display: 'flex',
  flexDirection: 'column',
  gap: theme.spacing(1),
  padding: theme.spacing(2),
  flex: 'auto',
  transition: 'padding 0.4s',
  [theme.containerQueries.up(500)]: {
    padding: theme.spacing(3),
  },
}));

export default function BasicContainerQueries() {
  return (
    <ResizableDemo>
      <Box
        sx={{
          overflow: 'auto',
          resize: 'horizontal',
          width: 400,
          maxWidth: 'min(80vw, 600px)',
          containerType: 'inline-size', // required for container queries
        }}
      >
        <DynamicCard variant="outlined">
          <Image
            alt="The house from the offer."
            src="https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&w=350&dpr=2"
          />
          <Content>
            <div>
              <Typography
                component="div"
                sx={{ color: 'text.secondary', fontSize: '0.875rem' }}
              >
                123 Main St, Phoenix AZ
              </Typography>
              <Typography
                component="div"
                sx={{
                  color: 'primary.main',
                  fontSize: '1.125rem',
                  fontWeight: 'bold',
                }}
              >
                $280,000 — $310,000
              </Typography>
            </div>
            <Chip
              size="small"
              label="Confidence score: 85%"
              sx={{ p: 0, width: 'fit-content' }}
            />
          </Content>
        </DynamicCard>
      </Box>
    </ResizableDemo>
  );
}

:::info 조상(ancestor) 요소 중 하나에 CSS 컨테이너 타입(container type)이 지정돼 있어야 해요. :::

Named containment contexts (이름이 지정된 컨테이너 컨텍스트)

컨테이너 컨텍스트를 참조하려면, 컨테이너의 이름과 함께 containerQueries 메서드를 호출하면 모든 브레이크포인트 메서드에 접근할 수 있어요:

theme.containerQueries('sidebar').up('500px'); // => '@container sidebar (min-width: 500px)'

Shorthand syntax (축약 문법)

sx prop을 사용해 스타일을 추가할 때는 @<size> 또는 @<size>/<name> 표기법을 사용해서 테마를 참조하지 않고도 컨테이너 쿼리를 적용할 수 있어요.

  • <size>: 너비 또는 브레이크포인트 키.
  • <name> (선택): 이름이 지정된 컨테이너 컨텍스트.
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Chip from '@mui/material/Chip';
import Typography from '@mui/material/Typography';
import ResizableDemo from './ResizableDemo';

export default function SxPropContainerQueries() {
  return (
    <ResizableDemo>
      <Box
        sx={{
          overflow: 'auto',
          resize: 'horizontal',
          width: 400,
          maxWidth: 'min(80vw, 600px)',
          containerType: 'inline-size', // required for container queries
        }}
      >
        <Card
          variant="outlined"
          sx={{
            display: 'flex',
            flexDirection: {
              '@': 'column',
              '@350': 'row',
            },
          }}
        >
          <Box
            component="img"
            alt="The house from the offer."
            src="https://images.unsplash.com/photo-1512917774080-9991f1c4c750?auto=format&w=350&dpr=2"
            sx={{
              alignSelf: 'stretch',
              aspectRatio: '16 / 9',
              objectFit: 'cover',
              width: '100%',
              maxHeight: {
                '@': 160,
                '@350': 'initial',
              },
              maxWidth: {
                '@350': '36%',
                '@500': 240,
              },
              transition: '0.4s',
            }}
          />
          <CardContent
            sx={{
              display: 'flex',
              flexDirection: 'column',
              gap: 1,
              padding: {
                '@': 2,
                '@500': 3,
              },
              flex: 'auto',
              transition: 'padding 0.4s',
            }}
          >
            <div>
              <Typography
                component="div"
                sx={{ color: 'text.secondary', fontSize: '0.875rem' }}
              >
                123 Main St, Phoenix AZ
              </Typography>
              <Typography
                component="div"
                sx={{
                  color: 'primary.main',
                  fontSize: '1.125rem',
                  fontWeight: 'bold',
                }}
              >
                $280,000 — $310,000
              </Typography>
            </div>
            <Chip
              size="small"
              label="Confidence score: 85%"
              sx={{ p: 0, width: 'fit-content' }}
            />
          </CardContent>
        </Card>
      </Box>
    </ResizableDemo>
  );
}

Caveats (주의사항)

  • 단위가 없는 값에 @ 접두사를 붙이면 px로 렌더링되므로, @500은 500px과 동일해요—하지만 @500px은 잘못된 문법이라 제대로 렌더링되지 않아요.

  • 숫자가 없는 @는 0px로 렌더링돼요.

  • 컨테이너 쿼리는 같은 단위를 공유해야 해요 (크기는 어떤 순서로 정의해도 돼요), 아래처럼요:

    // ✅ These container queries will be sorted correctly.
    padding: {
      '@40em': 4,
      '@20em': 2,
      '@': 0,
    }
    
    // ❌ These container queries won't be sorted correctly
    //    because 40em is typically greater than 50px
    //    and the units don't match.
    padding: {
      '@40em': 4,
      '@50': 2,
      '@': 0,
    }
    

API

CSS 컨테이너 쿼리는 브레이크포인트 API에서 제공하는 모든 메서드를 지원해요.

// For default breakpoints
theme.containerQueries.up('sm'); // => '@container (min-width: 600px)'
theme.containerQueries.down('md'); // => '@container (max-width: 900px)'
theme.containerQueries.only('md'); // => '@container (min-width: 600px) and (max-width: 900px)'
theme.containerQueries.between('sm', 'lg'); // => '@container (min-width: 600px) and (max-width: 1200px)'
theme.containerQueries.not('sm'); // => '@container (max-width: 600px)'

더 알아보기 (Learn more)