[TIL/Coin Site Project] 2023/12/18
✅ 다시 공식문서 공식 문서의 theming과 palette 파트에 최근 며칠간 deep dive 했다. 끝난 줄 알았다. 아뿔싸... dark mode 파트를 공부하지 않으면 의미가 없다는 것을 공식 문서를 훑어보며 깨달았다. 도대체 무슨 말을 하고 싶은 걸까?
✅ 다시 공식문서
공식 문서의 theming과 palette 파트에 최근 며칠간 deep dive 했다. 끝난 줄 알았다. 아뿔싸... dark mode 파트를 공부하지 않으면 의미가 없다는 것을 공식 문서를 훑어보며 깨달았다. 도대체 무슨 말을 하고 싶은 걸까?
✅ Dark mode
MUI에는 light와 dark, 두 가지 palette 모드가 있다.
1. Dark mode by default ✍️
createTheme에 mode: 'dark'를 추가하여, MUI에서 제공하는 기본적인 dark mode를 적용할 수 있다.
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
});
export default function App() {
return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<main>This app is using the dark mode</main>
</ThemeProvider>
);
}
다만, 이 부분은 나의 관심사가 아니다. mode: 'dark'라는 코드 한 줄 알자고 공식문서를 읽은 것은 아니기 때문이다. 핵심은 mode: 'dark'를 추가하면 palette 값이 다음의 값으로, 자동적으로 변경된다는 점이다.

Typography, Buttons, Background, Divider라는 구분이 있고 각각 palette 객체 내부에서 text, action, background, divider로 활용된다는 것을 알아야 커스텀을 할 것 아닌가!
2. Dark mode with a custom palette ✍️
사실상 이 파트가 최종보스가 되시겠다.
공식문서에서는 custom palette를 사용하기 위해, 모드에 맞는 palette를 반환하는 함수를 만드는 방법이 있다고 제시하고 있다. 코드는 다음과 같다.
const getDesignTokens = (mode: PaletteMode) => ({
palette: {
mode,
...(mode === 'light'
? {
// palette values for light mode
primary: amber,
divider: amber[200],
text: {
primary: grey[900],
secondary: grey[800],
},
}
: {
// palette values for dark mode
primary: deepOrange,
divider: deepOrange[700],
background: {
default: deepOrange[900],
paper: deepOrange[900],
},
text: {
primary: '#fff',
secondary: grey[500],
},
}),
},
});
형태가 중요한게 아니다. Typography를 커스텀하고 싶다면 text key를 활용해야 하고, 배경색을 변경하고 싶다면 background key를 사용해야 한다. 처음 공식문서의 해당 파트를 읽을 때, 무슨 자신감으로 이 부분을 그냥 넘어갔는지 모르겠다.
공식 문서는 구체적인 custom palette 예제를 제공하고 있다.
import * as React from 'react';
import Box from '@mui/material/Box';
import { ThemeProvider, useTheme, createTheme } from '@mui/material/styles';
import { amber, deepOrange, grey } from '@mui/material/colors';
const getDesignTokens = (mode) => ({
palette: {
mode,
primary: {
...amber,
...(mode === 'dark' && {
main: amber[300],
}),
},
...(mode === 'dark' && {
background: {
default: deepOrange[900],
paper: deepOrange[900],
},
}),
text: {
...(mode === 'light'
? {
primary: grey[900],
secondary: grey[800],
}
: {
primary: '#fff',
secondary: grey[500],
}),
},
},
});
function MyApp() {
const theme = useTheme();
return (
<Box
sx={{
display: 'flex',
width: '100%',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'background.default',
color: 'text.primary',
borderRadius: 1,
p: 3,
}}
>
This is a {theme.palette.mode} mode theme with custom palette
</Box>
);
}
const darkModeTheme = createTheme(getDesignTokens('dark'));
export default function DarkThemeWithCustomPalette() {
return (
<ThemeProvider theme={darkModeTheme}>
<MyApp />
</ThemeProvider>
);
}
bgcolor에는 'background.default'를, color에는 'text.primary'를 적용하고 있다.

17번과 28번 라인을 변경하면?

3. 생각을 해봅시다! ✍️
1. index.js

App이 MUIProvider로 감싸져 있다. MUIProvider는 DarkModeProvider로 감싸져 있다.
2. DarkModeProvider.js

context를 만들고, 하위에서 마음껏 사용할 수 있도록 useDarkContext도 만들었다. value로 mode라는 상태와 toggleColorMode라는 함수를 전달하고 있기에, 개별 컴포넌트에서는 useDarkContext 함수를 실행하면 해당 값을 자유롭게 꺼내서 사용할 수 있게 된다.
3. MUIProvider.js

언급한 대로, useDarkContext의 반환값인 mode와 toggleColorMode로 구성된 객체를 context라는 변수에 담았다. MUIProvider는 모드에 따라 변경된 theme을 하위에 적용하는 provider다.
4. getDesignTokens.js

context.mode가 무엇이냐에 따라 미리 정해놓은 커스텀 컬러가 적용된다.
5. App.js

변경 버튼을 적용하기 위해 useDarkContext를 실행했고, 스타일링은 간단하다.
bgcolor에는 background.default가 적용되어 있다. 버튼을 클릭하는 순간 DarkModeProvider의 mode 상태가 dark로 변경되고, 해당 mode는 MUIThemeProvider에서 mode에 맞는 theme으로 반영된다. 최종적으로 getDesignTokens에서는 모드에 맞는 background.default를 반영하게 된다. color에 적용되어 있는 text.primary도 마찬가지의 논리를 따른다.
4. 구현된 모습 ✍️


정말 준비가 끝났다. 내일은 프로젝트에서 불필요한 코드를 솎아낸 뒤, 오늘까지 정리한 모든 내용을 적용할 것이다.
✅ 회고
오늘도 또 한 번 느낀 것은, 역시 프로젝트는 수단에 불과하다는 점이다. 사실 화면에서는 단순한 버튼 하나만 눈에 띌 것이다. 그런데 그 버튼 안에는 theming, palette, custom theme, context, custom hook, state 등 다양한 개념이 녹아들어 있다.
이 모든 개념들을, 책을 읽는 것처럼 하나하나 배웠다면 굉장히 비효율적이었을 것이라는 생각이 든다. 자신이 해결하고자 하는 문제를 정의하고, 그 문제를 중심으로 학습해 나가는 과정이 가장 효율적인 방법일 수 있겠다. 물론 이런 과정은 간단하지 않겠지만 말이다.
More to read
프론트엔드와 백엔드 사이
HTTP 상태 코드는 프론트엔드에서 백엔드로 보냈던 요청의 수행 결과를 의미하는 일종의 약속이며, API를 구성하는 핵심 요소 중 하나입니다. 상태 코드와 관련하여, 백엔드는 잘 모르는 프론트엔드의 슬픈 사정이 있습니다.아래는 요청이 실패했음에도, 백엔드에서 상태 코드
JWT토큰 관리 방식 톺아보기
0. 들어가며 🎯 서비스에 접근하려는 사용자가 누구인지 확인하는 과정을 사용자 인증이라고 합니다. 인증된 사용자에게 주어진 권한을 확인하는 작업은 인가라고 부릅니다. 이번 글에서는 인가는 다루지 않습니다. 사용자 인증에는 많은 방식이 있지만, 오늘은 세션 인증 방
A2AA2A / MCP 멀티 에이전트 오케스트레이션
0. 들어가며 ✍️ Google for Developers에, 레스토랑 공급망 시나리오로 엮은 6대 프로토콜(MCP, A2A, UCP, AP2, A2UI, AG-UI)에 대한 가이드가 게시된 이후, MCP와 A2A부터 구현해 보는 것이 좋을 것 같다는 생각이 들었습니