반응형 스타일

반응형 스타일 (Responsive styles)

미디어 쿼리

.demo {
  background-color: var(--mantine-color-blue-filled);
  color: var(--mantine-color-white);
  padding: var(--mantine-spacing-md);
  text-align: center;

  @media (min-width: em(750px)) {
    background-color: var(--mantine-color-red-filled);
  }
}

출처: 문서

본문

breakpoint 구성

theme.breakpoints는 모든 반응형 Mantine 컴포넌트에서 사용돼요. breakpoint는 em 단위로 설정해야 해요. 이 값들은 MantineProvider로 구성할 수 있어요:

import { createTheme, MantineProvider } from '@mantine/core';

const theme = createTheme({
  breakpoints: {
    xs: '30em',
    sm: '48em',
    md: '64em',
    lg: '74em',
    xl: '90em',
  },
});

function Demo() {
  return (
    <MantineProvider theme={theme}>
      {/* Your app here */}
    </MantineProvider>
  );
}

기본 theme.breakpoints 값:

Breakpoint Viewport width px 값
xs 36em 576px
sm 48em 768px
md 62em 992px
lg 75em 1200px
xl 88em 1408px

CSS modules에서 breakpoint 변수

미디어 쿼리 안에서는 CSS 변수를 사용할 수 없어요 – 이 값들은 MantineProvider가 동적으로 생성할 수 없기 때문이에요. .css 파일에서 Mantine theme breakpoint를 사용하려면 postcss-simple-vars 패키지가 필요해요:

yarn add --dev postcss-simple-vars

PostCSS config의 postcss.config.cjs에 추가해요:

module.exports = {
  plugins: {
    'postcss-preset-mantine': {},
    'postcss-simple-vars': {
      variables: {
        'mantine-breakpoint-xs': '36em',
        'mantine-breakpoint-sm': '48em',
        'mantine-breakpoint-md': '62em',
        'mantine-breakpoint-lg': '75em',
        'mantine-breakpoint-xl': '88em',
      },
    },
  },
};

그러면 .css 파일에서 이 변수들에 접근할 수 있어요:

.demo {
  @media (max-width: $mantine-breakpoint-xs) {
    background-color: red;
  }
}

다음으로 변환돼요:

@media (max-width: 36em) {
  .demo {
    background-color: red;
  }
}

동적 breakpoint는 지원되지 않아요

postcss-simple-vars config에 정의된 값은 정적이며 theme과 연결되지 않아요 – 값이 바뀌면 theme override와 postcss config 양쪽에서 수동으로 업데이트해야 해요.

hiddenFrom, visibleFrom props

루트 요소를 가진 모든 Mantine 컴포넌트는 hiddenFrom과 visibleFrom prop을 지원해요. 이 prop들은 breakpoint(xs, sm, md, lg, xl)를 받아서 뷰포트 너비가 지정된 breakpoint보다 작거나 클 때 컴포넌트를 숨겨요:

import { Button, Group } from '@mantine/core';

function Demo() {
  return (
    <Group>
      <Button hiddenFrom="sm">Hidden from sm</Button>
      <Button visibleFrom="sm">Visible from sm</Button>
      <Button visibleFrom="md">Visible from md</Button>
    </Group>
  );
}

클래스로 hidden/visible 사용

커스텀 컴포넌트를 만들면서 hiddenFrom/visibleFrom props와 같은 로직을 사용하고 싶지만 Mantine 컴포넌트를 사용하고 싶지 않다면, mantine-hidden-from-{x}와 mantine-visible-from-{x} 클래스를 사용할 수 있어요.

function CustomComponent() {
  return (
    <>
      <div className="mantine-hidden-from-md">Hidden from md</div>
      <div className="mantine-visible-from-xl">Visible from xl</div>
    </>
  );
}

미디어 쿼리에 기반한 컴포넌트 크기

일부 컴포넌트는 컴포넌트 외관의 여러 측면을 바꾸는 size prop을 지원해요. size prop은 반응형이 아니에요 – 화면 크기마다 다른 컴포넌트 크기를 정의할 수 없어요. 대신 크기가 다른 여러 컴포넌트를 렌더링하고 className 또는 hiddenFrom/visibleFrom props로 미디어 쿼리에 따라 보이거나 숨길 수 있어요:

import { TextInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <TextInput size="md" label="My input" className="mantine-visible-from-sm" />
      <TextInput size="lg" label="My input" className="mantine-hidden-from-sm" />
    </>
  );
}

use-media-query 훅

use-media-query 훅을 사용해 미디어 쿼리에 따라 일부 컴포넌트 props를 바꿀 수 있어요. 이 방식은 애플리케이션에 ssr이 있다면(Next.js, React Router, Gatsby 또는 ssr을 포함하는 다른 프레임워크) 대부분의 경우 권장되지 않는다는 점에 주의하세요 – hydration 불일치가 발생할 수 있기 때문이에요. 애플리케이션에 ssr이 없다면(예: Vite 사용), 이 훅을 안전하게 사용해 훅 반환 값에 따라 컴포넌트 props를 바꾸거나 컴포넌트를 조건부로 렌더링할 수 있어요.

use-media-query 훅은 서버에서 렌더링되지 않는 컴포넌트(모달, 툴팁 등)의 props를 바꾸는 데는 안전하게 사용할 수 있어요. 다음 예시에서는 Tooltip이 서버에서 렌더링되지 않으므로 useMediaQuery 훅으로 Tooltip props를 바꾸는 것이 안전해요:

import { Tooltip, Button, em } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';

function Demo() {
  const isMobile = useMediaQuery(`(max-width: ${em(750)})`);

  return (
    <Tooltip label={isMobile ? 'Mobile' : 'Desktop'}>
      <Button>Hover me</Button>
    </Tooltip>
  );
}

use-matches 훅

@mantine/core에서 내보내는 use-matches 훅은 여러 미디어 쿼리와 값을 매칭해야 할 때 use-media-query의 대안이에요. 미디어 쿼리를 키로, 주어진 breakpoint에서의 값을 값으로 가지는 객체를 받아요.

use-matches 훅은 내부적으로 use-media-query와 같은 로직을 사용한다는 점에 주의하세요. 특히 애플리케이션에 ssr이 있다면 반응형 스타일의 주된 수단으로 사용하는 것은 권장되지 않아요.

다음 예시에서:

  • theme.breakpoints.lg부터는 색상이 red.9예요

  • theme.breakpoints.sm과 theme.breakpoints.lg 사이에는 색상이 orange.9예요

  • theme.breakpoints.sm 아래에서는 색상이 blue.9예요

import { Box, useMatches } from '@mantine/core';

function Demo() {
  const color = useMatches({
    base: 'blue.9',
    sm: 'orange.9',
    lg: 'red.9',
  });

  return <Box c={color}>Box with color that changes based on screen size</Box>;
}

Container queries

Container queries를 사용하면 요소 컨테이너의 크기에 기반해 요소에 스타일을 적용할 수 있어요. 예를 들어 컨테이너가 주변 컨텍스트에서 공간이 부족하면 특정 요소를 숨기거나 더 작은 폰트를 사용할 수 있어요. Container queries는 모든 현대 브라우저에서 지원돼요.

Container queries에서 postcss-preset-mantine의 rem과 em 함수를 사용할 수 있어요. CSS 변수는 container queries에서 동작하지 않으므로 rem scaling 기능을 사용할 수 없다는 점에 주의하세요. 이 기능에 의존한다면 breakpoint를 px 단위로 정의하는 것이 좋아요.

.root {
  min-width: 200px;
  max-width: 100%;
  min-height: 120px;
  container-type: inline-size;
  overflow: auto;
  resize: horizontal;
}

.child {
  background-color: var(--mantine-color-dimmed);
  color: var(--mantine-color-white);
  padding: var(--mantine-spacing-md);

  @container (max-width: 500px) {
    background-color: var(--mantine-color-blue-filled);
  }

  @container (max-width: 300px) {
    background-color: var(--mantine-color-red-filled);
  }
}

반응형 style props

style props로 반응형 스타일을 추가하려면 객체 문법을 사용할 수 있어요. 반응형 style props는 일반 style props보다 성능이 떨어지므로 요소가 많은 목록에서는 사용하지 않는 것이 좋아요.

import { Box } from '@mantine/core';

function Demo() {
  return (
    <Box
      w={{ base: 200, sm: 300, lg: 400 }}
    >
      Box with responsive style props
    </Box>
  );
}

반응형 값은 다음 방식으로 계산돼요:

  • base 값은 breakpoint 값이 적용되지 않을 때 사용돼요

  • xs, sm, md, lg, xl 값은 뷰포트 너비가 theme.breakpoints에 지정된 해당 breakpoint 값보다 클 때 사용돼요

import { Box } from '@mantine/core';

function Demo() {
  return <Box w={{ base: 200, sm: 300, lg: 400 }} />;
}

이 경우 요소는 다음 스타일을 가지게 돼요:

/* Base styles added to element and then get overwritten with responsive values */
.element {
  width: 20rem;
}

/* 48em is theme.breakpoints.sm by default */
@media (min-width: 48em) {
  .element {
    width: 30rem;
  }
}

/* 75em is theme.breakpoints.lg by default */
@media (min-width: 75em) {
  .element {
    width: 40rem;
  }
}

더 알아보기 (Learn more)