@vanilla-extract/css뿐만 추가로 제공해주는 vanilla extract는 타입스크립트를 전처리기로 사용합니다.
모든 css 코드를 styles.css.ts와 같은 .css.ts postfix 파일에 작성하기 때문에 작성하는 모든 스타일에 대한 타입 추론이 가능합니다.
css 속성을 작성할 때 일반 js 변수처럼 camelCase를 사용합니다.
import { style, globalStyle } from '@vanilla-extract/css';
export const myStyle = style({
display: 'flex',
paddingTop: '3px'
});
globalStyle('body', {
margin: 0
});
위 코드는 아래와 같이 변환됩니다.
.app_myStyle__sznanj0 {
display: flex;
padding-top: 3px;
}
body {
margin: 0;
}
// styles.css.ts
import { style } from '@vanilla-extract/css';
export const myStyle = style({
// cast to pixels
padding: 10,
marginTop: 25,
// unitless properties
flexGrow: 1,
opacity: 0.5
});
.styles_myStyle__1hiof570 {
padding: 10px;
margin-top: 25px;
flex-grow: 1;
opacity: 0.5;
}
post css를 이용해 처리하는 css와 마찬가지로 기본적으로 모든 속성에 대해 vender prefix가 지정되나 다음과 같이 camelCase를 사용해 특정 속성에 대한 특정 vendor prefix를 지정할 수 있습니다.
vendor prefix 속성 값을 위한 키 네이밍 법칙은 원본 vendor prefix의 camelize된 키 값에 - 값을 제외한 것입니다. (-webkit-tap-highlight-color)
import { style } from '@vanilla-extract/css';
export const myStyle = style({
WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)'
});
.styles_myStyle__1hiof570 {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
vanilla extract에서 선언되는 css variables는 vars 키워드 내부에 선언되어야 합니다.
import { style } from '@vanilla-extract/css';
const myStyle = style({
vars: {
'--my-global-variable': 'purple'
}
});
.styles_myStyle__1hiof570 {
--my-global-variable: purple;
}
css variable은 하드코딩 할 수도 있지만 createVar api를 통해 임의의 변수명을 생성해 참조할 수도 있습니다.
import { style, createVar } from '@vanilla-extract/css';
export const accentVar = createVar();
export const blue = style({
vars: {
[accentVar]: 'blue'
}
})
export const pink = style({
vars: {
[accentVar]: 'pink'
}
})
.accent_blue__l3kgsb1 {
--accentVar__l3kgsb0: blue;
}
.accent_pink__l3kgsb2 {
--accentVar__l3kgsb0: pink;
}
동일한 변수명을 참조하더라도 값은 다르게 선언될 수 있습니다. 아래 예시에서는 동일한 accentVar를 참조하나 클래스 명에 따라 다르게 동작하는 color 값을 볼 수 있습니다.
export const accentVar = createVar();
export const blue = style({
vars: {
[accentVar]: 'blue'
},
"@media": {
'(prefers-color-scheme: dark)': {
vars: {
[accentVar]: 'lightblue'
}
}
}
})
export const pink = style({
vars: {
[accentVar]: 'pink'
},
"@media": {
'(prefers-color-scheme: dark)': {
vars: {
[accentVar]: 'lightpink'
}
}
}
})
전처리된 css variable. 각 클래스에서 --accentVar__l3kgsb2라는 동일한 변수 명을 참조하고 있으나 다른 값이 전용됨을 볼 수 있다.
css variable 마저 typescript로 작성되어 export 될 수 있기 때문에 서로 다른 .css.ts 파일에서 이 변수명을 쉽게 참조하여 사용할 수 있습니다.
@media 키 값에 media 쿼리를 한 곳에 모아 작성할 수 있습니다.
const myStyle = style({
'@media': {
'screen and (min-width: 768px)': {
padding: 10
}
},
'(prefers-reduced-motion)': {
transitionProperty: 'color'
}
})
위 코드는 아래처럼 변환됩니다. vanilla extract에서 작성하는 미디어 쿼리는 파일의 제일 마지막에 추가되기 때문에 다른 스타일보다 우선선위가 높게 적용됩니다.
@media screen and (min-width: 768px) {
.styles_myStyle_lhio570 {
padding: 10px;
}
}
@media (prefers-reduced-motion) {
.styles_myStyle_hio570 {
transition-property: color;
}
}
vaniila extract의 selector는 두 가지의 형태가 있습니다.
hover, first-of-type, before과 같은 간단한 pseudo selector는 style 함수 내부에서 1-depth level로 선언할 수 있습니다.
import { style } from '@vanilla-extract/css';
const myStyle = style({
':hover': {
color: 'pink'
},
':first-of-type': {
color: 'blue'
},
'::before': {
content: ''
}
});
.styles_myStyle__1hiof570:hover {
color: pink;
}
.styles_myStyle__1hiof570:first-of-type {
color: blue;
}
.styles_myStyle__1hiof570::before {
content: "";
}
복잡한 형태의 selector는 selector 키 값에서 선언할 수 있습니다. style 태그 내부에서 사용되는 selector는 단일 엘리먼트를 대상으로 작성되어야 하며 current element를 의미하는 & 글자와 함께 사용되어야 합니다.
// styles.css.ts
import { style } from '@vanilla-extract/css';
const link = style({
selectors: {
'&:hover:not(:active)': {
border: '2px solid aquamarine'
},
'nav li > &': {
textDecoration: 'underline'
}
}
});
// css
.styles_link__1hiof570:hover:not(:active) {
border: 2px solid aquamarine;
}
nav li > .styles_link__1hiof570 {
text-decoration: underline;
}
편리한 점은, 이전에 미리 선언해둔 부모 클래스를 selector 내부에서 변수 참조할 수 있다는 점이며
import { style } from '@vanilla-extract/css';
export const parent = style({});
export const child = style({
selectors: {
[`${parent}:focus &`]: {
background: '#fafafa'
}
}
});
한 가지 조심해야 하는 점은 selectors에 선언한 구문의 타겟 엘리먼트가 현재 엘리먼트가 아닌 다른 엘리먼트를 가르키는 경우 유효하지 않은 selector로 간주됩니다.
import { style } from '@vanilla-extract/css';
const invalid = style({
selectors: {
// ❌ ERROR: Targetting `a[href]`
'& a[href]': {...},
// ❌ ERROR: Targetting `.otherClass`
'& ~ div > .otherClass': {...}
}
});
아래 예제에서 보듯이 child 엘리먼트를 대상으로 한 selector를 작성하고 싶을 경우 parent에서 selector를 작성하는 것이 아닌, child 클래스 변수에서 parent를 참조하는 방식으로 selector를 작성해야합니다.
import { style } from '@vanilla-extract/css';
// Invalid example:
export const child = style({});
export const parent = style({
selectors: {
// ❌ ERROR: Targetting `child` from `parent`
[`& ${child}`]: {...}
}
});
// Valid example:
export const parent = style({});
export const child = style({
selectors: {
[`${parent} &`]: {...}
}
});
만약 특정 클래스가 아닌 일반적인 a 태그를 선택하고 싶다면, globalStyle을 사용해야 합니다.
import { style, globalStyle } from '@vanilla-extract/css';
export const parent = style({});
globalStyle(`${parent} a[href]`, {
color: 'pink'
});
특정 브라우저에서 동작하지 않는 css 속성에 fallback style를 작성하고 싶을 경우 다음과 같이 array 형식으로 선언합니다.
import { style } from '@vanilla-extract/css';
export const myStyle = style({
// In Firefox and IE the "overflow: overlay" will be
// ignored and the "overflow: auto" will be applied
overflow: ['auto', 'overlay']
});
이는 아래처럼 변환됩니다. 브라우저에서 이해할 수 없는 css 속성은 무시되기 때문에 overlay를 이해할 수 없는 Firfox, IE에서는 둘째줄이 무시됩니다.
// css
.styles_myStyle__1hiof570 {
overflow: auto;
overflow: overlay;
}