커스터마이징 방법
커스터마이징 방법 (How to customize)
Material UI 컴포넌트의 스타일을 특정 상황에 맞춰 커스터마이징하는 여러 전략을 함께 배워볼게요. Material UI는 컴포넌트 스타일을 바꾸는 여러 방법을 제공하는데, 어떤 방법이 최적인지는 여러분의 상황에 따라 달라져요. 좁은 범위에서 넓은 범위 순서로 정리하면 다음과 같아요.
출처: 문서
본문
- 일회성 커스터마이징 (One-off customization)
- 재사용 컴포넌트 (Reusable component)
- 전역 테마 오버라이드 (Global theme overrides)
- 전역 CSS 오버라이드 (Global CSS override)
:::success
Material UI 스타일링 에이전트 스킬을 사용해서 AI 코딩 어시스턴트가 sx, styled(), 테마 오버라이드, 전역 CSS 중 어떤 것을 선택해야 할지 안내받도록 해 보세요.
:::
1. 일회성 커스터마이징 (One-off customization)
컴포넌트의 단일 인스턴스(one single instance) 스타일을 바꾸려면 다음 옵션 중 하나를 사용하면 돼요.
sx prop
sx prop은 대부분의 경우 컴포넌트 단일 인스턴스에 스타일 오버라이드를 추가하는 가장 좋은 옵션이에요. 모든 Material UI 컴포넌트와 함께 사용할 수 있답니다.
import Slider from '@mui/material/Slider';
export default function SxProp() {
return <Slider defaultValue={30} sx={{ width: 300, color: 'success.main' }} />;
}
중첩 컴포넌트 스타일 오버라이드 (Overriding nested component styles)
컴포넌트의 특정 부분을 커스터마이징하려면 sx prop 안에서 Material UI가 제공하는 클래스 이름을 사용하면 돼요. 예를 들어 Slider 컴포넌트의 thumb(엄지)을 원형에서 사각형으로 바꾸고 싶다고 해 볼게요.
먼저 브라우저의 개발자 도구(dev tools)를 사용해서 오버라이드하려는 컴포넌트 슬롯의 클래스를 찾아보세요.
Material UI가 DOM에 주입하는 스타일은 모두 표준 패턴을 따르는 클래스 이름에 의존해요:
[hash]-Mui[Component name]-[name of the slot].
이 경우 스타일은 .css-ae2u5c-MuiSlider-thumb로 적용되지만, 실제로 필요한 건 .MuiSlider-thumb뿐이에요. 여기서 Slider가 컴포넌트, thumb이 슬롯이죠. 이 클래스 이름을 sx prop 안에서 CSS 선택자(& .MuiSlider-thumb)로 사용해 오버라이드를 추가하면 됩니다.
import Slider from '@mui/material/Slider';
export default function DevTools() {
return (
<Slider
defaultValue={30}
sx={{
width: 300,
color: 'success.main',
'& .MuiSlider-thumb': {
borderRadius: '1px',
},
}}
/>
);
}
:::warning 이 클래스 이름들은 불안정(unstable)하기 때문에 CSS 선택자로 사용할 수 없어요. :::
클래스 이름으로 스타일 오버라이드 (Overriding styles with class names)
커스텀 클래스를 사용해서 컴포넌트의 스타일을 오버라이드하려면, 모든 컴포넌트에 있는 className prop을 사용하면 돼요. 컴포넌트의 특정 부분 스타일을 오버라이드하려면 앞선 "중첩 컴포넌트 스타일 오버라이드" 섹션에서 설명한 대로 Material UI가 제공하는 전역 클래스를 사용하세요. 해당 방법은 sx prop 섹션에서 확인할 수 있어요.
다른 스타일링 라이브러리를 사용하는 접근 방식의 예시는 스타일 라이브러리 상호운용성 (Style library interoperability) 가이드에서 찾아볼 수 있습니다.
상태 클래스 (State classes)
hover, focus, disabled, selected 같은 상태는 더 높은 CSS 특이성(specificity)으로 스타일링돼요. 이들을 커스터마이징하려면 특이성을 높여야 합니다.
다음은 Button 컴포넌트의 disabled 상태를 의사 클래스(:disabled)로 처리하는 예시예요:
.Button {
color: black;
}
/* Increase the specificity */
.Button:disabled {
color: white;
}
<Button disabled className="Button">
웹 스펙에 해당 상태가 존재하지 않으면 CSS 의사 클래스를 항상 사용할 수는 없어요. MenuItem 컴포넌트와 그 selected 상태를 예로 들어 볼게요. 이런 상황에서는 CSS 의사 클래스처럼 동작하는 Material UI의 상태 클래스(state classes) 를 사용할 수 있답니다. .Mui-selected 전역 클래스 이름을 타깃으로 MenuItem 컴포넌트의 특별한 상태를 커스터마이징해 봐요:
.MenuItem {
color: black;
}
/* Increase the specificity */
.MenuItem.Mui-selected {
color: blue;
}
<MenuItem selected className="MenuItem">
이 주제에 대해 더 알고 싶다면 MDN Web Docs의 CSS 특이성 문서를 확인해 보시길 권장해요.
하나의 컴포넌트 상태를 오버라이드하려면 왜 특이성을 높여야 하나요?
CSS 의사 클래스는 매우 높은 특이성을 가져요. 네이티브 요소와 일관성을 유지하기 위해, Material UI의 상태 클래스는 CSS 의사 클래스와 동일한 수준의 특이성을 가집니다. 그래서 개별 컴포넌트의 상태를 타깃으로 지정할 수 있게 되는 거예요.
Material UI에서 어떤 커스텀 상태 클래스를 사용할 수 있나요?
Material UI가 생성하는 다음 전역 클래스 이름을 사용할 수 있어요:
| 상태 (State) | 전역 클래스 이름 (Global class name) |
|---|---|
| active | .Mui-active |
| checked | .Mui-checked |
| completed | .Mui-completed |
| disabled | .Mui-disabled |
| error | .Mui-error |
| expanded | .Mui-expanded |
| focus visible | .Mui-focusVisible |
| focused | .Mui-focused |
| readOnly | .Mui-readOnly |
| required | .Mui-required |
| selected | .Mui-selected |
:::error 상태 클래스 이름에 직접 스타일을 적용하지 마세요. 그러면 명확하지 않은 부작용과 함께 모든 컴포넌트에 영향을 미칩니다. 항상 컴포넌트와 함께 상태 클래스를 타깃으로 지정하세요. :::
/* ❌ NOT OK */
.Mui-error {
color: red;
}
/* ✅ OK */
.MuiOutlinedInput-root.Mui-error {
color: red;
}
2. 재사용 컴포넌트 (Reusable component)
애플리케이션의 여러 위치에서 동일한 오버라이드를 재사용하려면, styled() 유틸리티로 재사용 가능한 컴포넌트를 만들어 보세요:
import Slider, { SliderProps } from '@mui/material/Slider';
import { alpha, styled } from '@mui/material/styles';
const SuccessSlider = styled(Slider)<SliderProps>(({ theme }) => ({
width: 300,
color: theme.palette.success.main,
'& .MuiSlider-thumb': {
'&:hover, &.Mui-focusVisible': {
boxShadow: `0px 0px 0px 8px ${alpha(theme.palette.success.main, 0.16)}`,
},
'&.Mui-active': {
boxShadow: `0px 0px 0px 14px ${alpha(theme.palette.success.main, 0.16)}`,
},
},
}));
export default function StyledCustomization() {
return <SuccessSlider defaultValue={30} />;
}
동적 오버라이드 (Dynamic overrides)
styled() 유틸리티를 사용하면 컴포넌트의 prop에 기반한 동적 스타일을 추가할 수 있어요. 동적 CSS(dynamic CSS) 또는 CSS 변수(CSS variables) 를 사용해서 처리할 수 있답니다.
동적 CSS (Dynamic CSS)
:::warning TypeScript를 사용하고 있다면 새 컴포넌트의 prop 타입을 업데이트해야 합니다. :::
import * as React from 'react';
import { alpha, styled } from '@mui/material/styles';
import Slider, { SliderProps } from '@mui/material/Slider';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
interface StyledSliderProps extends SliderProps {
success?: boolean;
}
const StyledSlider = styled(Slider, {
shouldForwardProp: (prop) => prop !== 'success',
})<StyledSliderProps>(({ theme }) => ({
width: 300,
variants: [
{
props: ({ success }) => success,
style: {
color: theme.palette.success.main,
'& .MuiSlider-thumb': {
[`&:hover, &.Mui-focusVisible`]: {
boxShadow: `0px 0px 0px 8px ${alpha(theme.palette.success.main, 0.16)}`,
},
[`&.Mui-active`]: {
boxShadow: `0px 0px 0px 14px ${alpha(theme.palette.success.main, 0.16)}`,
},
},
},
},
],
}));
export default function DynamicCSS() {
const [success, setSuccess] = React.useState(false);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSuccess(event.target.checked);
};
return (
<React.Fragment>
<FormControlLabel
control={
<Switch
checked={success}
onChange={handleChange}
color="primary"
value="dynamic-class-name"
/>
}
label="Success"
/>
<StyledSlider success={success} defaultValue={30} sx={{ mt: 1 }} />
</React.Fragment>
);
}
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Slider, { SliderProps } from '@mui/material/Slider';
interface StyledSliderProps extends SliderProps {
success?: boolean;
}
const StyledSlider = styled(Slider, {
shouldForwardProp: (prop) => prop !== 'success',
})<StyledSliderProps>(({ success, theme }) => ({
...(success &&
{
// the overrides added when the new prop is used
}),
}));
CSS 변수 (CSS variables)
import * as React from 'react';
import { styled } from '@mui/material/styles';
import Slider from '@mui/material/Slider';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
const CustomSlider = styled(Slider)({
width: 300,
color: 'var(--color)',
'& .MuiSlider-thumb': {
[`&:hover, &.Mui-focusVisible`]: {
boxShadow: '0px 0px 0px 8px var(--box-shadow)',
},
[`&.Mui-active`]: {
boxShadow: '0px 0px 0px 14px var(--box-shadow)',
},
},
});
const successVars = {
'--color': '#4caf50',
'--box-shadow': 'rgb(76, 175, 80, .16)',
} as React.CSSProperties;
const defaultVars = {
'--color': '#1976d2',
'--box-shadow': 'rgb(25, 118, 210, .16)',
} as React.CSSProperties;
export default function DynamicCSSVariables() {
const [vars, setVars] = React.useState<React.CSSProperties>(defaultVars);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setVars(event.target.checked ? successVars : defaultVars);
};
return (
<React.Fragment>
<FormControlLabel
control={
<Switch
checked={vars === successVars}
onChange={handleChange}
color="primary"
value="dynamic-class-name"
/>
}
label="Success"
/>
<CustomSlider style={vars} defaultValue={30} sx={{ mt: 1 }} />
</React.Fragment>
);
}
3. 전역 테마 오버라이드 (Global theme overrides)
Material UI는 사용자 인터페이스 전체의 모든 컴포넌트 간 스타일 일관성을 관리하기 위한 테마 도구를 제공해요. 자세한 내용은 컴포넌트 테마 커스터마이징 (Component theming customization) 페이지를 방문해 보세요.
4. 전역 CSS 오버라이드 (Global CSS override)
일부 HTML 요소에 전역 baseline 스타일을 추가하려면 GlobalStyles 컴포넌트를 사용하세요. 다음은 h1 요소의 스타일을 오버라이드하는 방법의 예시예요:
import * as React from 'react';
import GlobalStyles from '@mui/material/GlobalStyles';
export default function GlobalCssOverride() {
return (
<React.Fragment>
<GlobalStyles styles={{ h1: { color: 'grey' } }} />
<h1>Grey h1 element</h1>
</React.Fragment>
);
}
GlobalStyles 컴포넌트의 styles prop은 테마에 접근해야 할 때 콜백을 지원해요.
import * as React from 'react';
import GlobalStyles from '@mui/material/GlobalStyles';
export default function GlobalCssOverrideTheme() {
return (
<React.Fragment>
<GlobalStyles
styles={(theme) => ({
h1: { color: theme.palette.primary.main },
})}
/>
<h1>Grey h1 element</h1>
</React.Fragment>
);
}
이미 baseline 스타일 설정에 CssBaseline 컴포넌트를 사용하고 있다면, 이 전역 스타일을 해당 컴포넌트의 오버라이드로 추가할 수도 있어요. 같은 결과를 이 방식으로 얻는 방법은 다음과 같습니다.
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider, createTheme } from '@mui/material/styles';
const theme = createTheme({
components: {
MuiCssBaseline: {
styleOverrides: `
h1 {
color: grey;
}
`,
},
},
});
export default function OverrideCssBaseline() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<h1>Grey h1 element</h1>
</ThemeProvider>
);
}
MuiCssBaseline 컴포넌트 슬롯의 styleOverrides 키도 테마에 접근할 수 있는 콜백을 지원해요. 같은 결과를 이 방식으로 얻는 방법은 다음과 같습니다.
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider, createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
success: {
main: '#ff0000',
},
},
components: {
MuiCssBaseline: {
styleOverrides: (themeParam) => `
h1 {
color: ${themeParam.palette.success.main};
}
`,
},
},
});
export default function OverrideCallbackCssBaseline() {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<h1>h1 element</h1>
</ThemeProvider>
);
}
:::success
<GlobalStyles />를 정적 상수(static constant)로 끌어올려(hoist) 재렌더링을 피하는 것이 좋은 관례예요. 이렇게 하면 생성되는 <style> 태그가 매 렌더링마다 다시 계산되지 않습니다.
:::
import * as React from 'react';
import GlobalStyles from '@mui/material/GlobalStyles';
+const inputGlobalStyles = <GlobalStyles styles={...} />;
function Input(props) {
return (
<React.Fragment>
- <GlobalStyles styles={...} />
+ {inputGlobalStyles}
<input {...props} />
</React.Fragment>
)
}
GlobalStyles API
데모 (Demos)
이 React 컴포넌트의 사용 예시와 자세한 내용은 컴포넌트 데모 페이지를 방문해 보세요:
Import
import GlobalStyles from '@mui/material/GlobalStyles';
// or
import { GlobalStyles } from '@mui/material';
Props
| 이름 (Name) | 타입 (Type) | 기본값 (Default) | 필수 (Required) | 설명 (Description) |
|---|---|---|---|---|
| styles | array | func | number | object | string | bool |
- | No |
참고 (Note):
ref는 루트 요소로 전달됩니다.
제공된 다른 모든 props는 루트 요소(네이티브 요소)로 전달됩니다.
소스 코드 (Source code)
이 페이지에서 필요한 정보를 찾지 못했다면, 컴포넌트의 구현을 살펴보면서 더 자세한 내용을 확인해 보세요.