color palette 선정
블로그에서 사용할 color palette 는 radix color 를 사용합니다. radix color 는 아래의 장점을 가지고 있습니다.
- 하나의 색에
light
와dark
를 모두 지원해줍니다. - 일반
css
파일로 제공됩니다. color scale
세분화되어 있습니다.- 문서가 잘 되어있습니다.
color palette
에 대한 이해도 상승에 매우 도움이 됩니다.- 특정
color
와 잘 어울리는gray color
를 찾을 수 있습니다. - 세분화 된
color scale
의 전반적인 사용 방법에 대해 말해줍니다. 따라서 프로젝트가 커져도 해당 규칙만 잘 지킨다면 프로젝트의 색 조화를 깨뜨릴 위험이 없습니다.
아래의 3가지 색상을 블로그의 primary
/ secondary
/ tertiary
로 선정했습니다.
- primary: cyan
- secondary: crimpson
- tertiary: yellow
- gray: slate
color palette 코드 구현
_light.scss
scss// import ... :root { @include css-variable.colors; } @mixin light { @include slate; // gray @include cyan; // primary @include crimson; // secondary @include yellow; // tertiary @include blue; // ... } // NOTE: 추후 light / dark mode 를 위해 생성 @media (prefers-color-scheme: light) { :root { @include light; } } // NOTE: 추후 light / dark mode 를 위해 생성합니다. .light-theme { @include light; }
light 색을 지정합니다.
_light.scss
scss// import ... :root { @include css-variable.colors; } @mixin light { @include slate; // gray @include cyan; // primary @include crimson; // secondary @include yellow; // tertiary @include blue; // ... } // NOTE: 추후 light / dark mode 를 위해 생성 @media (prefers-color-scheme: light) { :root { @include light; } } // NOTE: 추후 light / dark mode 를 위해 생성합니다. .light-theme { @include light; }
light 색을 지정합니다.
_dark.scss
scss// import ... @mixin dark { @include slateDark; @include cyanDark; @include crimsonDark; @include yellowDark; // ... } @media (prefers-color-scheme: dark) { :root { @include dark; } } .dark-theme { @include dark; }
dark 색을 지정합니다.
_dark.scss
scss// import ... @mixin dark { @include slateDark; @include cyanDark; @include crimsonDark; @include yellowDark; // ... } @media (prefers-color-scheme: dark) { :root { @include dark; } } .dark-theme { @include dark; }
dark 색을 지정합니다.
마지막으로 forwarding
해주면 됩니다.
_index.scss
scss@forward "dark"; @forward "light"; @forward "css-variable"; @forward "utils";
forwarding 해줍니다
_index.scss
scss@forward "dark"; @forward "light"; @forward "css-variable"; @forward "utils";
forwarding 해줍니다
이렇게 radix-color
가 global scss
로 관리되도록 세팅되었습니다. 생략되지 않은 모든 코드는 여기서 확인 할 수 있습니다.
color 를 bespoke 시스템으로 관리하자
해당 블로그 프로젝트는 불특정 다수가 사용할 수 있도록 만드는게 목표다. 따라서 위에서 지정한 primary 컬러 등을 각 사용자가 원하는 색으로 지정할 수 있도록 하고 싶었다.
가장 쉬운 방법으로 build
단계에서 원하는 색을 넣으면, 해당 값을 기준으로 scss
파일을 생성하도록 만들었다.
createColor.js
js/* eslint-disable */ const fs = require("fs").promises const path = require("path") const defaultColors = { primary: "cyan", secondary: "crimson", tertiary: "yellow", gray: "slate", } async function createColor(colors) { const darkThemeFilePath = path.resolve() + "/src/styles/2-theme/_dark.scss" const lightThemeFilePath = path.resolve() + "/src/styles/2-theme/_light.scss" await fs.writeFile( lightThemeFilePath, getLightTemplate({ ...defaultColors, ...(colors || {}) }), {} ) await fs.writeFile( darkThemeFilePath, getDarkTemplate({ ...defaultColors, ...(colors || {}) }), {} ) }
createColor.js
js/* eslint-disable */ const fs = require("fs").promises const path = require("path") const defaultColors = { primary: "cyan", secondary: "crimson", tertiary: "yellow", gray: "slate", } async function createColor(colors) { const darkThemeFilePath = path.resolve() + "/src/styles/2-theme/_dark.scss" const lightThemeFilePath = path.resolve() + "/src/styles/2-theme/_light.scss" await fs.writeFile( lightThemeFilePath, getLightTemplate({ ...defaultColors, ...(colors || {}) }), {} ) await fs.writeFile( darkThemeFilePath, getDarkTemplate({ ...defaultColors, ...(colors || {}) }), {} ) }
createBespoke.js
js// import dox from "dox" /* eslint-disable */ const createColor = require("./createColor.js"); const createComponents = require("./createComponents.js"); module.exports = (pluginOptions = {}) => async (nextConfig = {}) => { await createColor(pluginOptions?.colors); await createComponents(); return Object.assign({}, nextConfig, { webpack(config, options) { if (typeof nextConfig.webpack === "function") { return nextConfig.webpack(config, options); } return config; }, }); };
createBespoke.js
js// import dox from "dox" /* eslint-disable */ const createColor = require("./createColor.js"); const createComponents = require("./createComponents.js"); module.exports = (pluginOptions = {}) => async (nextConfig = {}) => { await createColor(pluginOptions?.colors); await createComponents(); return Object.assign({}, nextConfig, { webpack(config, options) { if (typeof nextConfig.webpack === "function") { return nextConfig.webpack(config, options); } return config; }, }); };
next.config.mjs
js// 생략 ... const withBespoke = createBespoke({ colors: { primary: "cyan", secondary: "crimson", tertiary: "yellow", gray: "slate", }, }) export default withBespoke(withMDX(nextConfig))
next.config.mjs
js// 생략 ... const withBespoke = createBespoke({ colors: { primary: "cyan", secondary: "crimson", tertiary: "yellow", gray: "slate", }, }) export default withBespoke(withMDX(nextConfig))
light / dark mode 구현
구체적인 코드는 코드 저장소에서 확인하시면 됩니다.
theme
을 구현하기 위해서는 아래의 것들을 고려해야합니다.
- 사용자 기기의
default theme
을 알아야합니다. - 사용자가 이전에 페이지에 설정해 놓은
theme
이 무엇인지 알 수 있어야합니다. - 페이지 접근 시 깜빡거림 없는 화면을 보여줘야합니다. (
dark
로 지정해놨는데, 페이지 접근 시 흰화면이 보이고dark
가 적용되면 안된다.) - 적용된
theme
은상태
로client-side
에서 관리 되어야합니다.
위 고려사항들을 구현하기 위해서 아래 방법을 이용했습니다.
- matchMedia api 와 prefer-color-scheme 을 이용해서 사용자의
prefer color scheme
을 알아옵니다. color-scheme
을 저장하는 방법으로는cookie
와localstorage
가 있습니다.cookie
는 기본적으로 모든 서버요청에header
로 포함되어 날라가게 됩니다. 굳이 서버로의 요청을 무겁헤 할 필요는 없으니,localstorage
를 사용합나디- 페이지 접근 시 깜빡임 없는 화면 을 보여주기 위해서는 서버로부터 불러온
html
이 렌더링 되기 전에client
영역에서 지정된theme
을 확인하고, 곧바로body
태그에class
를 지정해줍니다.bespoke
블로그는 light / dark 를class
명을 이용해 나타내게 됩니다. 만약dark
면body
의 class명에dark-theme
이 세팅됩니다.- 자세한 코드는 여기를 보시면 됩니다.
theme
의 상태 관리는jotai
를 이용해봤습니다.theme
이 변경될 때 마다 상태 변경은 물론,localStorage
와body
의class
명을 변경하도록 했습니다.