반응형
원문 : https://blog.maximeheckel.com/posts/the-power-of-composition-with-css-variables/
CSS 변수와 HSLA 색상을 사용한 좋은 CSS 습관
테마와 색상을 관리하기 위해 ThemeProviders에 지나치게 의존하지 마세요
JS기반 테마를 제거하고, CSS 파일에만 의존하여 변수를 삽입합니다.
// Before I was constantly using JS based theme with interpolation function:
const MyStyledComponent = styled('div')`
background-color: ${(p) => p.theme.primaryColor};
color: ${(p) => p.theme.typefaceColor1};
`;
// Now I, for anything theme related,
// I simply used CSS variables that are defined in a global CSS file:
const MyStyledComponent = styled('div')`
background-color: var(--primaryColor);
color: var(--typefaceColor1);
`;
--spacing-0: 4px;
--spacing-1: 8px;
.myclass {
padding: var(--spacing-0) var(--spacing-1); /* equivalent to padding: 4px 8px; */
}
HEX 포맷 사용하기
HSLA는 Hue Saturation Lightness Alpha 채널을 의미합니다.
색상을 정의하는 데 필요한 네 가지 주요 구성 요소입니다.
Hue-색조는 색상환의 각 정도가 색상인 색상환에서 정의된 360 색상 팔레트에서 나옵니다.
채도, 밝기 및 알파 채널 속성은 백분율(%)로 정의되며 각각 다음을 나타냅니다.
- saturation-채도 : 색상의 생동감: 0%가 가장 적은 생동감, 100%가 가장 큰 생동감
- lightness-조명 강도: 0%가 가장 어둡고 100%가 가장 밝음
- Alpha channel-불투명도: 0%는 투명하고 100%는 색상을 완전히 보이게 합니다.
HSLA를 사용하면 여러 색상이 얼마나 가까운지 쉽게 알 수 있습니다.
유사한 색상이 동일한 hue(색조)와 saturationI(채도)를 공유한다는 것을 알 수 있습니다.
CSS 변수를 통해 색조와 채도의 일부만 정의하고 이를 재사용하여 다른 색상 변수를 정의할 수 있다면 어떨까요?
컬러 스케일
제 경력 전반에 걸쳐 함께 일한 디자이너들은 종종 다음과 같이 HSLA를 사용하여 색상 스케일을 구축했습니다.
- 밝기(lightness)가 50%일 때 색상을 선택합니다. 이것은 스케일의 "중간" 색상이 됩니다.
- 밝기를 10% 높여 해당 색상의 다음 "밝은 음영(shade)"을 만듭니다.
- 밝기를 10% 줄여 다음 "해당 색상의 더 어두운 음영"을 만듭니다.
이를 통해 주어진 색상의 풀 컬러 스케일을 얻은 다음 앱에서 사용합니다.
이를 css로 옮겨봅시다.
--base-blue: 222, 89%; /* All my colors here share the same hue and saturation */
--palette-blue-10: hsla(var(--base-blue), 10%, 100%);
--palette-blue-20: hsla(var(--base-blue), 20%, 100%);
--palette-blue-30: hsla(var(--base-blue), 30%, 100%);
--palette-blue-40: hsla(var(--base-blue), 40%, 100%);
--palette-blue-50: hsla(var(--base-blue), 50%, 100%);
--palette-blue-60: hsla(var(--base-blue), 60%, 100%);
--palette-blue-70: hsla(var(--base-blue), 70%, 100%);
--palette-blue-80: hsla(var(--base-blue), 80%, 100%);
--palette-blue-90: hsla(var(--base-blue), 90%, 100%);
--palette-blue-100: hsla(var(--base-blue), 100%, 100%);
--base-blue CSS 변수가 여기에서 핵심입니다.
partial 값에 할당됩니다.
CSS 변수 자체는 CSS 값으로 사용할 수 없으며 합성을 통해서만 사용할 수 있습니다.
- 내 파란색 스케일의 나머지 부분을 매우 쉽게 합성합니다.
- 기본 색상을 변경하여 색상 스케일을 완전히 업데이트합니다.
이 기술을 통해 단순히 "멋져 보이는" 임의의 색상을 선택하는 대신 이해할 수 있는 색상을 생성할 수 있었습니다.
색상을 의미에 따라 합성하기
중요한 것은 색상의 의미와 실제 값입니다.
--base-blue: 222, 89%; /* All my colors here share the same hue and saturation */
/*
Here I declared my color palette.
Each color is a partial value
*/
--palette-blue-10: var(--base-blue), 10%;
--palette-blue-20: var(--base-blue), 20%;
--palette-blue-30: var(--base-blue), 30%;
--palette-blue-40: var(--base-blue), 40%;
--palette-blue-50: var(--base-blue), 50%;
--palette-blue-60: var(--base-blue), 60%;
--palette-blue-70: var(--base-blue), 70%;
--palette-blue-80: var(--base-blue), 80%;
--palette-blue-90: var(--base-blue), 90%;
--palette-blue-100: var(--base-blue), 100%;
/*
Here I compose a color based on its meaning:
- primary and emphasis are composed by using --palette-blue-50
- they use a different opacity level thus they have different meaning:
- primary is a bold color used for buttons and primary CTA
- emphasis is used to highlight content or for the background of my callout cards
*/
--primary: hsla(var(--palette-blue-50), 100%);
--emphasis: hsla(var(--palette-blue-50), 8%);
- 동일한 partial value에서 상속되는 2개의 서로 다른 색상 변수를 정의하여 일부 계층 구조를 생성할 수 있습니다.
- 컴포넌트 스타일을 더 일반적/추상적으로 만듭니다.
고급 구성 패턴
다음 시나리오를 상상해 봅시다.
- 내 앱에는 기본 색상의 색조(hue)와 채도(saturation)를 정의하는 --base-primary 변수가 있는 기본 테마가 있습니다.
- 해당 앱 내의 버튼에는 --base-primary로 구성된 배경색과 50%의 밝기가 있습니다.
- 해당 버튼의 호버 및 포커스 색상은 각각 기본 색상보다 10%, 20% 어둡고, 합성을 통해 정의됩니다.
- 사용자가 원하는 기본 색상을 선택할 수 있습니다. 이는 다른 색조, 채도 및 밝기(lightness)를 의미합니다.
/**
Our set of CSS variables in this case is as follows:
--base-primary: 222, 89%;
--primary: hsla(var(--base-primary), 50%, 100%);
--primary-hover: hsla(var(--base-primary), 40%, 100%);
--primary-focus: hsla(var(--base-primary), 30%, 100%);
--text-color: hsla(0, 0%, 100%, 100%);
**/
const StyledButton = styled('button')`
height: 45px;
border-radius: 8px;
box-shadow: none;
border: none;
padding: 8px 16px;
text-align: center;
color: var(--text-color);
background-color: var(--primary);
cursor: pointer;
font-weight: 500;
&:hover {
background-color: var(--primary-hover);
}
&:focus {
background-color: var(--primary-focus);
}
`;
사용자가 디폴트 밝기를 50%가 아닌 것으로 선택하면 문제가 복잡해집니다.
따라서 밝기를 사용자 입력값에 기반해 계산합니다.
- 기본 색상의 색조와 채도를 포함하기 전에 --base-primary를 정의합니다.
- 기본 색상의 기본 밝기를 포함하는 --base-primary-lightness를 정의합니다.
- 호버, 포커스 시의 밝기를 계산합니다
/**
You can see here that we're only passing CSS variables for the background color
for the hover, focus and rest state of the Button.
**/
const StyledButton = styled('button', {
height: '45px',
borderRadius: '8px',
boxShadow: 'none',
border: 'none',
padding: '8px 16px',
textAlign: 'center',
color: 'var(--text-color)',
backgroundColor: 'var(--primary)',
cursor: 'pointer',
fontWeight: '500',
'&:hover': {
backgroundColor: 'var(--primary-hover)',
},
'&:focus': {
backgroundColor: 'var(--primary-focus)',
},
});
/**
You can try to modify the lightness or base hue/saturation below.
You should see that the button hover and focus color will adapt and take into account
almost (see below why) any color!
**/
const BlueThemeWrapper = styled('div', {
'--base-primary': '222, 89%',
'--base-primary-lightness': '50%',
'--primary': 'hsla(var(--base-primary), var(--base-primary-lightness), 100%)',
'--primary-hover': `hsla(
var(--base-primary),
calc(var(--base-primary-lightness) - 10%),
/* --primary-hover is --primary but 10% darker */ 100%
)`,
'--primary-focus': `hsla(
var(--base-primary),
calc(var(--base-primary-lightness) - 20%),
/* --primary-hover is --primary but 20% darker */ 100%
)`,
'--text-color': 'hsla(0, 0%, 100%, 100%)',
});
const CyanThemedWrapper = styled('div', {
'--base-primary': '185, 75%',
'--base-primary-lightness': '60%',
'--primary': 'hsla(var(--base-primary), var(--base-primary-lightness), 100%)',
'--primary-hover': `hsla(
var(--base-primary),
calc(var(--base-primary-lightness) - 10%),
100%
)`,
'--primary-focus': `hsla(
var(--base-primary),
calc(var(--base-primary-lightness) - 20%),
100%
)`,
'--text-color': 'hsla(0, 0%, 100%, 100%)',
});
const RedThemeWrapper = styled('div', {
'--base-primary': '327, 80%',
'--base-primary-lightness': '40%',
'--primary': 'hsla(var(--base-primary), var(--base-primary-lightness), 100%)',
'--primary-hover': `hsla(
var(--base-primary),
calc(var(--base-primary-lightness) - 10%),
100%
)`,
'--primary-focus': `hsla(
var(--base-primary),
calc(var(--base-primary-lightness) - 20%),
100%
)`,
'--text-color': 'hsla(0, 0%, 100%, 100%)',
});
const ThemedButton = () => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
height: '175px',
}}
>
<BlueThemeWrapper>
<StyledButton>Primary Button</StyledButton>
</BlueThemeWrapper>
<CyanThemedWrapper>
<StyledButton>Primary Button</StyledButton>
</CyanThemedWrapper>
<RedThemeWrapper>
<StyledButton>Primary Button</StyledButton>
</RedThemeWrapper>
</div>
);
};
render(<ThemedButton />);
위의 이 기술은 매우 훌륭하지만 한계가 있습니다.
- 사용자가 너무 어두운 색상을 정의하면 호버 및 포커스 배경 색상이 잘 보이지 않습니다.
- 테마 중 하나의 --base-primary-lightness를 5%로 수정하여 위에서 시도해 볼 수 있습니다.
- 색상이 너무 밝으면 또 다른 문제가 발생합니다.
- 텍스트가 버튼 안에서 흰색이며,이를 고려해야 합니다.
- 테마 중 하나의 --base-primary-lightness를 95%로 수정하여 위에서 시도해 볼 수 있습니다.
SaaS를 사용하여 이러한 문제를 쉽게 해결할 수 있긴 합니다.
참고
tailwind css의 idea
Composing the Uncomposable with CSS variables
반응형
'FrontEnd' 카테고리의 다른 글
[짤막글] styled-components는 어떻게 스타일을 적용하는가 (0) | 2022.07.03 |
---|---|
디자인 시스템 컴포넌트를 만들 때 고려할 사항들 (0) | 2022.07.02 |
[Next.js 튜토리얼] JS에서 리액트로 (0) | 2022.07.01 |
웹 애플리케이션을 구성하는 요소들 (0) | 2022.07.01 |
React DnD 튜토리얼 (0) | 2022.06.30 |