Vanilla Extract - Styling

흑우·2023년 12월 11일

vanilla-extract

목록 보기
1/4

Vanilla Extract란?

Zero-runtime Stylesheets

  • css-in-js가 아닌! css-in-ts 라이브러리로 style을 ts로 작성하고 빌드 타임에 css로 전처리해줍니다.
  • 기존의 css-in-js 라이브러리인 styled-components나 emotion의 경우 런타임에 js를 css로 변환해줍니다.
  • 즉, vanilla extract 이하 ve는 일반적인 css-in-js 라이브러리보다 더 빠른 스타일 loading 시간을 갖습니다.

Type-safe static CSS

  • css를 typescript로 작성되기 때문에 완벽한 타입 추론을 지원하며 타입 안정성을 지켜 css를 사용할 수 있습니다.

Built for extension

  • 기본적으로 제공나는 @vanilla-extract/css뿐만 추가로 제공해주는

Styling

CSS Properties

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;
}

Unitless Properties


// 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;
}

Vender Prefixes

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);
}

CSS Variables

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 Queries

@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;
    }
}

Selectors

vaniila extract의 selector는 두 가지의 형태가 있습니다.

simple pseudo selectors

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: "";
}

complex selectors

복잡한 형태의 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'
});

Cross Browsing

특정 브라우저에서 동작하지 않는 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;
}

Reference

profile
흑우 모르는 흑우 없제~

0개의 댓글