Style library interoperability

Style library interoperability (스타일 라이브러리 상호운용성)

Material UI가 제공하는 Emotion 기반 스타일링 솔루션을 사용할 수 있지만, 이미 알고 있는 다른 스타일 솔루션(평범한 CSS부터 styled-components까지)을 사용할 수도 있어요.

이 가이드는 가장 인기 있는 대안들을 문서화하는 데 목적이 있지만, 여기 적용된 원칙들은 다른 라이브러리에도 적용할 수 있다는 점을 알게 될 거예요. 다음 스타일링 솔루션에 대한 예제가 있어요:

출처: 문서

본문

Plain CSS

특별한 것 없이, 그냥 평범한 CSS예요.

import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

const SliderCustomized = styled(Slider)`
  color: #20b2aa;

  :hover {
    color: #2e8b57;
  }
`;

export default function StyledComponents() {
  return (
    <Box sx={{ width: 300 }}>
      <Slider defaultValue={30} />
      <SliderCustomized defaultValue={30} />
    </Box>
  );
}

Edit Button

.slider {
  color: #20b2aa;
}

.slider:hover {
  color: #2e8b57;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
import './PlainCssSlider.css';

export default function PlainCssSlider() {
  return (
    <div>
      <Slider defaultValue={30} />
      <Slider defaultValue={30} className="slider" />
    </div>
  );
}

CSS injection order ⚠️ (CSS 주입 순서)

참고: 대부분의 CSS-in-JS 솔루션은 스타일을 HTML <head>의 맨 아래에 주입하기 때문에, Material UI가 커스텀 스타일보다 우선하게 돼요. !important의 필요성을 없애려면 CSS 주입 순서를 변경해야 해요. Material UI에서 이를 처리하는 방법의 데모는 다음과 같아요:

import * as React from 'react';
import { StyledEngineProvider } from '@mui/material/styles';

export default function GlobalCssPriority() {
  return (
    <StyledEngineProvider injectFirst>
      {/* Your component tree. Now you can override Material UI's styles. */}
    </StyledEngineProvider>
  );
}

참고: Emotion을 사용하고 앱에 커스텀 캐시가 있다면, 그 캐시가 Material UI에서 오는 캐시를 덮어쓰게 돼요. 주입 순서가 여전히 올바르게 유지되려면 prepend 옵션을 추가해야 해요. 예는 다음과 같아요:

import * as React from 'react';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';

const cache = createCache({
  key: 'css',
  prepend: true,
});

export default function PlainCssPriority() {
  return (
    <CacheProvider value={cache}>
      {/* Your component tree. Now you can override Material UI's styles. */}
    </CacheProvider>
  );
}

참고: styled-components를 사용하고 커스텀 target이 있는 StyleSheetManager가 있다면, target이 HTML <head>의 첫 번째 요소인지 확인하세요. 어떻게 하는지 궁금하다면, @mui/styled-engine-sc 패키지의 StyledEngineProvider 구현을 살펴볼 수 있어요.

Deeper elements (더 깊은 요소들)

Slider를 스타일링하려고 한다면, Slider의 자식 요소들(예: thumb) 중 일부를 건드려야 할 가능성이 높아요. Material UI에서 모든 자식 요소는 2의 특이성(specificity)을 가져요: .parent .child {}. 오버라이드를 작성할 때도 동일하게 해야 해요.

다음 예제들은 슬라이더 자체의 커스텀 스타일에 더해 슬라이더의 thumb 스타일도 오버라이드해요.

import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

const CustomizedSlider = styled(Slider)`
  color: #20b2aa;

  &:hover {
    color: #2e8b57;
  }

  & .MuiSlider-thumb {
    border-radius: 1px;
  }
`;

export default function StyledComponentsDeep() {
  return (
    <Box sx={{ width: 300 }}>
      <Slider defaultValue={30} />
      <CustomizedSlider defaultValue={30} />
    </Box>
  );
}

.slider {
  color: #20b2aa;
}

.slider:hover {
  color: #2e8b57;
}

.slider .MuiSlider-thumb {
  border-radius: 1px;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
import './PlainCssSliderDeep1.css';

export default function PlainCssSliderDeep1() {
  return (
    <div>
      <Slider defaultValue={30} />
      <Slider defaultValue={30} className="slider" />
    </div>
  );
}

위 데모는 기본 className 값에 의존하지만, slotProps API로 고유한 클래스 이름을 제공할 수도 있어요.

.slider {
  color: #20b2aa;
}

.slider:hover {
  color: #2e8b57;
}

.slider .thumb {
  border-radius: 1px;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
import './PlainCssSliderDeep2.css';

export default function PlainCssSliderDeep2() {
  return (
    <div>
      <Slider defaultValue={30} />
      <Slider
        defaultValue={30}
        className="slider"
        slotProps={{ thumb: { className: 'thumb' } }}
      />
    </div>
  );
}

Global CSS

컴포넌트에 클래스 이름을 명시적으로 제공하는 것이 너무 번거로운가요? Material UI가 생성한 클래스 이름을 대상으로 할 수 있어요.

Edit Button

.MuiSlider-root {
  color: #20b2aa;
}

.MuiSlider-root:hover {
  color: #2e8b57;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
import './GlobalCssSlider.css';

export default function GlobalCssSlider() {
  return <Slider defaultValue={30} />;
}

CSS injection order ⚠️ (CSS 주입 순서)

참고: 대부분의 CSS-in-JS 솔루션은 스타일을 HTML <head>의 맨 아래에 주입하기 때문에, Material UI가 커스텀 스타일보다 우선하게 돼요. !important의 필요성을 없애려면 CSS 주입 순서를 변경해야 해요. Material UI에서 이를 처리하는 방법의 데모는 다음과 같아요:

import * as React from 'react';
import { StyledEngineProvider } from '@mui/material/styles';

export default function GlobalCssPriority() {
  return (
    <StyledEngineProvider injectFirst>
      {/* Your component tree. Now you can override Material UI's styles. */}
    </StyledEngineProvider>
  );
}

참고: Emotion을 사용하고 앱에 커스텀 캐시가 있다면, 그 캐시가 Material UI에서 오는 캐시를 덮어쓰게 돼요. 주입 순서가 여전히 올바르게 유지되려면 prepend 옵션을 추가해야 해요. 예는 다음과 같아요:

import * as React from 'react';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';

const cache = createCache({
  key: 'css',
  prepend: true,
});

export default function GlobalCssPriority() {
  return (
    <CacheProvider value={cache}>
      {/* Your component tree. Now you can override Material UI's styles. */}
    </CacheProvider>
  );
}

참고: styled-components를 사용하고 커스텀 target이 있는 StyleSheetManager가 있다면, target이 HTML <head>의 첫 번째 요소인지 확인하세요. 어떻게 하는지 궁금하다면, @mui/styled-engine-sc 패키지의 StyledEngineProvider 구현을 살펴볼 수 있어요.

Deeper elements (더 깊은 요소들)

Slider를 스타일링하려고 한다면, Slider의 자식 요소들(예: thumb) 중 일부를 건드려야 할 가능성이 높아요. Material UI에서 모든 자식 요소는 2의 특이성(specificity)을 가져요: .parent .child {}. 오버라이드를 작성할 때도 동일하게 해야 해요.

다음 예제는 슬라이더 자체의 커스텀 스타일에 더해 슬라이더의 thumb 스타일도 오버라이드해요.

import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

const CustomizedSlider = styled(Slider)`
  color: #20b2aa;

  &:hover {
    color: #2e8b57;
  }

  & .MuiSlider-thumb {
    border-radius: 1px;
  }
`;

export default function StyledComponentsDeep() {
  return (
    <Box sx={{ width: 300 }}>
      <Slider defaultValue={30} />
      <CustomizedSlider defaultValue={30} />
    </Box>
  );
}

.MuiSlider-root {
  color: #20b2aa;
}

.MuiSlider-root:hover {
  color: #2e8b57;
}

.MuiSlider-root .MuiSlider-thumb {
  border-radius: 1px;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
import './GlobalCssSliderDeep.css';

export default function GlobalCssSliderDeep() {
  return <Slider defaultValue={30} />;
}

Styled Components (styled-components)

stars npm

Change the default styled engine (기본 styled 엔진 변경)

기본적으로 Material UI 컴포넌트는 스타일 엔진으로 Emotion을 사용해요. 하지만 styled-components를 사용하고 싶다면, styled-components 가이드를 따라 앱을 구성할 수 있어요.

이 접근 방식을 따르면 번들 크기가 줄어들고, CSS 주입 순서를 구성할 필요도 없어져요.

스타일 엔진이 제대로 구성된 후에는 @mui/material/styles의 styled() 유틸리티를 사용해서 테마에 직접 접근할 수 있어요.

import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

const SliderCustomized = styled(Slider)`
  color: #20b2aa;

  :hover {
    color: #2e8b57;
  }
`;

export default function StyledComponents() {
  return (
    <Box sx={{ width: 300 }}>
      <Slider defaultValue={30} />
      <SliderCustomized defaultValue={30} />
    </Box>
  );
}

Edit Button

import * as React from 'react';
import Slider from '@mui/material/Slider';
import { styled } from '@mui/material/styles';

const CustomizedSlider = styled(Slider)`
  color: #20b2aa;

  :hover {
    color: #2e8b57;
  }
`;

export default function StyledComponents() {
  return <CustomizedSlider defaultValue={30} />;
}

Deeper elements (더 깊은 요소들)

Slider를 스타일링하려고 한다면, Slider의 자식 요소들(예: thumb) 중 일부를 건드려야 할 가능성이 높아요. Material UI에서 모든 자식 요소는 2의 특이성(specificity)을 가져요: .parent .child {}. 오버라이드를 작성할 때도 동일하게 해야 해요.

다음 예제들은 슬라이더 자체의 커스텀 스타일에 더해 슬라이더의 thumb 스타일도 오버라이드해요.

import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

const CustomizedSlider = styled(Slider)`
  color: #20b2aa;

  &:hover {
    color: #2e8b57;
  }

  & .MuiSlider-thumb {
    border-radius: 1px;
  }
`;

export default function StyledComponentsDeep() {
  return (
    <Box sx={{ width: 300 }}>
      <Slider defaultValue={30} />
      <CustomizedSlider defaultValue={30} />
    </Box>
  );
}

위 데모는 기본 className 값에 의존하지만, slotProps API로 고유한 클래스 이름을 제공할 수도 있어요.

import * as React from 'react';
import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';

const CustomizedSlider = styled((props) => (
  <Slider slotProps={{ thumb: { className: 'thumb' } }} {...props} />
))`
  color: #20b2aa;

  :hover {
    color: #2e8b57;
  }

  & .thumb {
    border-radius: 1px;
  }
`;

export default function StyledComponentsDeep2() {
  return (
    <div>
      <Slider defaultValue={30} />
      <CustomizedSlider defaultValue={30} />
    </div>
  );
}

Theme (테마)

Material UI 테마 프로바이더를 사용하면, 테마가 styled 엔진의 테마 컨텍스트(구성에 따라 Emotion 또는 styled-components)에서도 사용 가능하게 돼요.

:::warning styled-components나 Emotion으로 이미 커스텀 테마를 사용하고 있다면, 그 테마가 Material UI의 테마 스펙과 호환되지 않을 수 있어요. 호환되지 않는다면, Material UI의 ThemeProvider를 먼저 렌더링해야 해요. 이렇게 하면 테마 구조가 격리되도록 보장돼요. 이는 코드베이스에 Material UI 컴포넌트를 점진적으로 도입하는 데 이상적이에요. :::

Material UI와 프로젝트의 나머지 부분에서 같은 테마 객체를 공유하는 것을 권장해요.

const CustomizedSlider = styled(Slider)(
  ({ theme }) => `
  color: ${theme.palette.primary.main};

  :hover {
    color: ${darken(theme.palette.primary.main, 0.2)};
  }
`,
);
import { createTheme, styled, ThemeProvider, darken } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

const customTheme = createTheme({
  palette: {
    primary: {
      main: '#20b2aa',
    },
  },
});

const CustomizedSlider = styled(Slider)(
  ({ theme }) => `
  color: ${theme.palette.primary.main};

  :hover {
    color: ${darken(theme.palette.primary.main, 0.2)};
  }
`,
);

export default function StyledComponentsTheme() {
  return (
    <Box sx={{ width: 300 }}>
      <ThemeProvider theme={customTheme}>
        <CustomizedSlider defaultValue={30} />
      </ThemeProvider>
    </Box>
  );
}

Portals (포털)

Portal 컴포넌트는 부모 컴포넌트의 DOM 계층 밖에 존재하는 DOM 노드에 자식을 렌더링하는 일급 클래스(first-class) 방법을 제공해요. styled-components가 CSS를 스코프하는 방식 때문에, 스타일이 적용되지 않는 문제가 발생할 수 있어요.

예를 들어, Tooltip 컴포넌트가 생성한 tooltip을 스타일링하려고 한다면, DOM 계층 밖에 렌더링되는 요소에 className 속성을 전달해야 해요. 다음 예제는 해결 방법(workaround)을 보여줘요:

import * as React from 'react';
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
import Tooltip from '@mui/material/Tooltip';

const StyledTooltip = styled(({ className, ...props }) => (
  <Tooltip {...props} classes={{ popper: className }} />
))`
  & .MuiTooltip-tooltip {
    background: navy;
  }
`;
import { styled } from '@mui/material/styles';
import Button from '@mui/material/Button';
import Tooltip, { TooltipProps } from '@mui/material/Tooltip';

const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
  <Tooltip {...props} classes={{ popper: className }} />
))`
  & .MuiTooltip-tooltip {
    background: navy;
  }
`;

export default function StyledComponentsPortal() {
  return (
    <StyledTooltip title="I am navy">
      <Button variant="contained" color="primary">
        Styled tooltip
      </Button>
    </StyledTooltip>
  );
}

CSS Modules

stars

이 스타일링 솔루션의 시장 점유율은 사람들이 사용하는 번들링 솔루션에 의존하기 때문에 알기 어려워요.

import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

const SliderCustomized = styled(Slider)`
  color: #20b2aa;

  :hover {
    color: #2e8b57;
  }
`;

export default function StyledComponents() {
  return (
    <Box sx={{ width: 300 }}>
      <Slider defaultValue={30} />
      <SliderCustomized defaultValue={30} />
    </Box>
  );
}

Edit Button

.slider {
  color: #20b2aa;
}

.slider:hover {
  color: #2e8b57;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
// webpack, Parcel or else will inject the CSS into the page
import styles from './CssModulesSlider.module.css';

export default function CssModulesSlider() {
  return (
    <div>
      <Slider defaultValue={30} />
      <Slider defaultValue={30} className={styles.slider} />
    </div>
  );
}

CSS injection order ⚠️ (CSS 주입 순서)

참고: 대부분의 CSS-in-JS 솔루션은 스타일을 HTML <head>의 맨 아래에 주입하기 때문에, Material UI가 커스텀 스타일보다 우선하게 돼요. !important의 필요성을 없애려면 CSS 주입 순서를 변경해야 해요. Material UI에서 이를 처리하는 방법의 데모는 다음과 같아요:

import * as React from 'react';
import { StyledEngineProvider } from '@mui/material/styles';

export default function GlobalCssPriority() {
  return (
    <StyledEngineProvider injectFirst>
      {/* Your component tree. Now you can override Material UI's styles. */}
    </StyledEngineProvider>
  );
}

참고: Emotion을 사용하고 앱에 커스텀 캐시가 있다면, 그 캐시가 Material UI에서 오는 캐시를 덮어쓰게 돼요. 올바른 주입 순서를 보장하려면 prepend 옵션을 사용해요:

import * as React from 'react';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';

const cache = createCache({
  key: 'css',
  prepend: true,
});

export default function CssModulesPriority() {
  return (
    <CacheProvider value={cache}>
      {/* Your component tree. Now you can override Material UI's styles. */}
    </CacheProvider>
  );
}

참고: styled-components를 사용하면서 커스텀 target이 있는 StyleSheetManager를 쓴다면, target이 HTML <head>의 첫 번째 요소인지 확인하세요. 예를 보려면 @mui/styled-engine-sc 패키지의 StyledEngineProvider 구현을 참고하세요.

Deeper elements (더 깊은 요소들)

Slider를 스타일링할 때 thumb 같은 자식 요소를 대상으로 해야 할 수도 있어요. Material UI 컴포넌트는 자식 요소에 증가된 특이성(예: .parent .child)을 자주 사용해요. CSS Modules는 클래스 이름을 스코프하기 때문에, 생성된 클래스 이름이 Material UI의 것과 일치하지 않아요.

CSS Modules에서 Material UI 클래스에 스타일을 적용하려면 :global 선택자를 사용해요.

Using :global (:global 사용)

.slider {
  color: #20b2aa;
}

.slider:hover {
  color: #2e8b57;
}

.slider :global(.MuiSlider-thumb) {
  border-radius: 1px;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
// webpack, Parcel or else will inject the CSS into the page
import styles from './CssModulesSliderDeep1.module.css';

export default function CssModulesSliderDeep1() {
  return (
    <div>
      <Slider defaultValue={30} />
      <Slider defaultValue={30} className={styles.slider} />
    </div>
  );
}

Using slotProps (slotProps 사용)

.slider {
  color: #20b2aa;
}

.slider:hover {
  color: #2e8b57;
}

.slider .thumb {
  border-radius: 1px;
}
import * as React from 'react';
import Slider from '@mui/material/Slider';
// webpack, Parcel or else will inject the CSS into the page
import styles from './CssModulesSliderDeep2.module.css';

export default function CssModulesSliderDeep2() {
  return (
    <div>
      <Slider defaultValue={30} />
      <Slider
        defaultValue={30}
        className={styles.slider}
        slotProps={{ thumb: { className: styles.thumb } }}
      />
    </div>
  );
}

Targeting Material UI state classes with CSS Modules (CSS Modules로 Material UI 상태 클래스 대상 지정)

Material UI는 컴포넌트 상태를 나타내기 위해 전역 클래스 이름(예: .Mui-selected, .Mui-disabled)을 사용해요. CSS Modules는 스타일을 로컬로 스코프하기 때문에, 이런 전역 상태 클래스를 대상으로 하려면 :global이 필요해요.

Material UI 상태 클래스에 따라 조건부로 스타일을 적용하는 방법은 다음과 같아요:

.myListItem {
  padding: 10px;
  border-bottom: 1px solid #ccc;
}

/* Combine global state class with a locally scoped class */
:global(.Mui-selected).myListItem {
  background-color: #1976d2;
  color: white;
}
import * as React from 'react';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import styles from './MyList.module.css';

export default function MyList() {
  return (
    <List>
      <ListItem className={styles.myListItem} selected>
        <ListItemText primary="Selected item" />
      </ListItem>
      <ListItem className={styles.myListItem}>
        <ListItemText primary="Regular item" />
      </ListItem>
    </List>
  );
}

이 기법을 사용하면 Material UI의 전역 클래스 이름으로 설정되는 동적 상태에 반응하면서도 모듈식 스타일을 유지할 수 있어요.

Emotion

stars npm

The css prop (css prop)

Emotion의 css() 메서드는 Material UI와 매끄럽게 동작해요.

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

export default function EmotionCSS() {
  return (
    <Box sx={{ width: 300 }}>
      <Slider defaultValue={30} />
      <Slider
        defaultValue={30}
        css={css`
          color: #20b2aa;

          :hover {
            color: #2e8b57;
          }
        `}
      />
    </Box>
  );
}

Theme (테마)

styled components와 똑같이 동작해요. 같은 가이드를 사용할 수 있어요.

The styled() API (styled() API)

styled components와 똑같이 동작해요. 같은 가이드를 사용할 수 있어요.

Tailwind CSS v3

stars npm

:::info Tailwind CSS v4의 경우 v4 통합 가이드를 참조하세요. :::

Setup (설정)

Tailwind CSS를 Material UI 컴포넌트와 함께 사용하려면, Vite와 TypeScript로 구축된 예제 프로젝트를 클론해서 시작할 수 있어요. 다른 프레임워크를 사용하거나 이미 프로젝트를 설정했다면, 다음 단계를 따르세요:

  1. https://v3.tailwindcss.com/docs/installation/framework-guides의 지침에 따라 프로젝트에 Tailwind CSS를 추가해요.
  2. Tailwind CSS의 preflight 스타일을 제거해서 대신 Material UI의 preflight(CssBaseline)를 사용할 수 있게 해요.
 module.exports = {
+  corePlugins: {
+    preflight: false,
+  },
 };
  1. 앱 래퍼의 id를 사용해서 important 옵션을 추가해요.

    • Next.js 프로젝트의 경우 #__next를 사용해요. Next.js 13+ 사용자(App Router) 참고사항: Next.js가 더 이상 자동으로 추가하지 않으므로, 이제 루트 요소(일반적으로 <body>)에 직접 id="__next"를 추가해야 해요:
    <body id="__next">{/* Your app content */}</body>
    
    • Vite/SPA 프로젝트의 경우 #root를 사용해요 (대부분의 템플릿에서 기본값).
 module.exports = {
   content: [
     "./src/**/*.{js,jsx,ts,tsx}",
   ],
+  important: '#__next', // or '#root'
   theme: {
     extend: {},
   },
   plugins: [],
 }

Material UI가 사용하는 CSS 대부분은 특이성이 1이므로 이 important 속성은 불필요해요. 그러나 몇 가지 엣지 케이스에서 Material UI는 Tailwind CSS를 이기는 중첩 CSS 선택자를 사용해요. 이 단계를 사용해서 더 깊은 요소가 항상 Tailwind의 유틸리티 클래스로 커스터마이즈될 수 있도록 하는 데 도움이 돼요. 이 옵션에 대한 더 많은 세부 내용은 https://v3.tailwindcss.com/docs/configuration#selector-strategy에서 찾을 수 있어요.

  1. CSS 주입 순서를 수정해요. 대부분의 CSS-in-JS 솔루션은 스타일을 HTML <head>의 맨 아래에 주입하기 때문에, Material UI가 Tailwind CSS보다 우선하게 돼요. important 속성의 필요성을 줄이려면 CSS 주입 순서를 변경해야 해요. Material UI에서 이를 처리하는 방법의 데모는 다음과 같아요:
import * as React from 'react';
import { StyledEngineProvider } from '@mui/material/styles';

export default function GlobalCssPriority() {
  return (
    <StyledEngineProvider injectFirst>
      {/* Your component tree. Now you can override Material UI's styles. */}
    </StyledEngineProvider>
  );
}

참고: Emotion을 사용하고 앱에 커스텀 캐시가 있다면, 그 캐시가 Material UI에서 오는 캐시를 덮어쓰게 돼요. 주입 순서가 여전히 올바르게 유지되려면 prepend 옵션을 추가해야 해요. 예는 다음과 같아요:

import * as React from 'react';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';

const cache = createCache({
  key: 'css',
  prepend: true,
});

export default function PlainCssPriority() {
  return (
    <CacheProvider value={cache}>
      {/* Your component tree. Now you can override Material UI's styles. */}
    </CacheProvider>
  );
}

참고: styled-components를 사용하고 커스텀 target이 있는 StyleSheetManager가 있다면, target이 HTML <head>의 첫 번째 요소인지 확인하세요. 어떻게 하는지 궁금하다면, @mui/styled-engine-sc 패키지의 StyledEngineProvider 구현을 살펴볼 수 있어요.

  1. Tailwind 구성에서 important 옵션을 설정하기 위해 3단계에서 사용한 메인 앱 래퍼 아래에 주입되도록 Portal 관련 요소의 대상 컨테이너를 변경해요.
// For Next.js:
const rootElement = document.getElementById("__next");
// For Vite/SPA:
// const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

const theme = createTheme({
  components: {
    MuiPopover: {
      defaultProps: {
        container: rootElement,
      },
    },
    MuiPopper: {
      defaultProps: {
        container: rootElement,
      },
    },
    MuiDialog: {
      defaultProps: {
        container: rootElement,
      },
    },
    MuiModal: {
      defaultProps: {
        container: rootElement,
      },
    },
  },
});

root.render(
  <StyledEngineProvider injectFirst>
    <ThemeProvider theme={theme}>
      <App />
    </ThemeProvider>
  </StyledEngineProvider>;
);

Troubleshooting (문제 해결)

스타일이 제대로 적용되지 않는다면:

  1. 루트 ID가 Tailwind 구성의 important 선택자와 일치하는지 확인해요.
Framework Root Element ID important Selector
Next.js id="__next" #__next
Vite/SPA id="root" #root
  1. preflight: false가 설정되어 있는지 확인해요.
  2. injectFirst가 있는 StyledEngineProvider가 제대로 구성되어 있는지 확인해요.

Usage (사용)

이제 모든 것이 설정되었으니 Material UI 컴포넌트에서 Tailwind CSS를 사용하기 시작할 수 있어요!

import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

const SliderCustomized = styled(Slider)`
  color: #20b2aa;

  :hover {
    color: #2e8b57;
  }
`;

export default function StyledComponents() {
  return (
    <Box sx={{ width: 300 }}>
      <Slider defaultValue={30} />
      <SliderCustomized defaultValue={30} />
    </Box>
  );
}

Edit on StackBlitz

import * as React from 'react';
import Slider from '@mui/material/Slider';

export default function App() {
  return (
    <div>
      <Slider defaultValue={30} />
      <Slider defaultValue={30} className="text-teal-600" />
    </div>
  );
}

Deeper elements (더 깊은 요소들)

예를 들어 Slider를 스타일링하려고 한다면, 자식 요소들을 커스터마이즈하고 싶을 가능성이 높아요.

이 예제는 Slider의 thumb 스타일을 오버라이드하는 방법을 보여줘요.

import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import Box from '@mui/material/Box';

const CustomizedSlider = styled(Slider)`
  color: #20b2aa;

  &:hover {
    color: #2e8b57;
  }

  & .MuiSlider-thumb {
    border-radius: 1px;
  }
`;

export default function StyledComponentsDeep() {
  return (
    <Box sx={{ width: 300 }}>
      <Slider defaultValue={30} />
      <CustomizedSlider defaultValue={30} />
    </Box>
  );
}

import * as React from 'react';
import Slider from '@mui/material/Slider';

export default function SliderThumbOverrides() {
  return (
    <div>
      <Slider defaultValue={30} />
      <Slider
        defaultValue={30}
        className="text-teal-600"
        slotProps={{ thumb: { className: 'rounded-sm' } }}
      />
    </div>
  );
}

Styling pseudo states (의사 상태 스타일링)

컴포넌트의 의사 상태(pseudo-state)를 스타일링하려면 classes prop에서 적절한 키를 사용할 수 있어요. Slider의 활성 상태를 스타일링하는 방법의 예는 다음과 같아요:

import * as React from 'react';
import Slider from '@mui/material/Slider';

export default function SliderThumbOverrides() {
  return <Slider defaultValue={30} classes={{ active: 'shadow-none' }} />;
}

JSS TSS

JSS 자체는 Material UI에서 더 이상 지원되지 않지만, react-jss가 제공했던 훅 기반 API(makeStyles → useStyles)가 좋다면 tss-react를 선택할 수 있어요.

TSS는 Material UI와 잘 통합되며 JSS보다 더 나은 TypeScript 지원을 제공해요.

:::info @material-ui/core(v4)에서 @mui/material(v5)로 업데이트 중이라면, 마이그레이션 가이드의 tss-react 섹션을 확인해보세요. :::

import { render } from 'react-dom';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';
import { ThemeProvider } from '@mui/material/styles';

export const muiCache = createCache({
  key: 'mui',
  prepend: true,
});

//NOTE: Don't use <StyledEngineProvider injectFirst/>
render(
  <CacheProvider value={muiCache}>
    <ThemeProvider theme={myTheme}>
      <Root />
    </ThemeProvider>
  </CacheProvider>,
  document.getElementById('root'),
);

이제 간단히 import { makeStyles, withStyles } from 'tss-react/mui'라고 하면 돼요. 콜백 함수에 전달될 theme 객체는 import { useTheme } from '@mui/material/styles'로 얻는 것과 같아요.

theme 객체가 무엇이어야 할지 제어하고 싶다면, 예를 들어 makesStyles.ts라는 파일에서 makeStyles와 withStyles를 다시 export할 수 있어요:

import { useTheme } from '@mui/material/styles';
//WARNING: tss-react require TypeScript v4.4 or newer. If you can't update use:
//import { createMakeAndWithStyles } from "tss-react/compat";
import { createMakeAndWithStyles } from 'tss-react';

export const { makeStyles, withStyles } = createMakeAndWithStyles({
  useTheme,
  /*
    OR, if you have extended the default mui theme adding your own custom properties:
    Let's assume the myTheme object that you provide to the <ThemeProvider /> is of
    type MyTheme then you'll write:
    */
  //"useTheme": useTheme as (()=> MyTheme)
});

그런 다음 라이브러리는 이렇게 사용돼요:

import { makeStyles } from 'tss-react/mui';

export function MyComponent(props: Props) {
  const { className } = props;

  const [color, setColor] = useState<'red' | 'blue'>('red');

  const { classes, cx } = useStyles({ color });

  //Thanks to cx, className will take priority over classes.root
  return <span className={cx(classes.root, className)}>hello world</span>;
}

const useStyles = makeStyles<{ color: 'red' | 'blue' }>()((theme, { color }) => ({
  root: {
    color,
    '&:hover': {
      backgroundColor: theme.palette.primary.main,
    },
  },
}));

SSR 설정이나 그 외 다른 것에 대한 정보는 TSS 문서를 참조하세요.

:::info 사용하지 않는 클래스를 감지하기 위한 ESLint 플러그인이 있어요. :::

:::warning @emotion/styled를 프로젝트의 의존성으로 유지하세요. 명시적으로 사용하지 않더라도, 그것은 @mui/material의 피어 의존성(peer dependency)이기 때문이에요. :::

더 알아보기 (Learn more)