CSS @property 완전 가이드 — 디자인 토큰 타입 등록부터 Style Dictionary v4·Tailwind v4 트랜지션 자동화까지
CSS 커스텀 프로퍼티(--var)를 사용하면서 테마 전환 시 색상이 뚝 끊기거나, 그라디언트에 트랜지션이 전혀 먹히지 않는 경험을 해보신 적 있으신가요? 원인은 단순합니다. 브라우저가 일반 CSS 변수를 그냥 문자열로 저장하기 때문입니다. #6366f1이든 42px이든 브라우저 입장에서는 의미 없는 텍스트일 뿐이라 두 값 사이를 수치로 보간(interpolate)하는 것 자체가 불가능합니다.
@property at-rule은 이 문제를 근본적으로 해결합니다. 커스텀 프로퍼티에 타입·초기값·상속 여부를 선언하면 브라우저가 값의 의미를 이해하고, transition과 @keyframes가 정확하게 작동하게 됩니다. 2024년 7월 9일, Chrome·Edge·Firefox·Safari 모두 지원을 완료하면서 프로덕션 적용의 마지막 장벽도 사라졌습니다.
이 글에서는 @property의 핵심 동작 원리부터, Style Dictionary v4로 자동 생성하고 Tailwind CSS v4에 통합하는 실전 파이프라인까지 단계적으로 살펴봅니다. Style Dictionary는 디자인 토큰 JSON을 플랫폼별 코드로 변환하는 빌드 시스템이고, Tailwind v4는 CSS 파일 자체를 설정 소스로 삼는 유틸리티 프레임워크입니다. 두 도구를 처음 접하는 분도 각 섹션에서 필요한 맥락을 제공하니 순서대로 따라오시면 됩니다.
핵심 개념
CSS @property — 타입이 있는 커스텀 프로퍼티
@property(CSS Properties and Values API Level 1)는 CSS 커스텀 프로퍼티에 세 가지 정보를 명시적으로 등록할 수 있는 at-rule입니다.
@property --color-primary {
syntax: "<color>";
inherits: true;
initial-value: #6366f1;
}
@property --spacing-base {
syntax: "<length>";
inherits: true;
initial-value: 16px;
}
@property --opacity-overlay {
syntax: "<number>";
inherits: false;
initial-value: 0;
}세 가지 필수 디스크립터의 역할은 다음과 같습니다.
| 디스크립터 | 역할 |
|---|---|
syntax |
허용 타입 (<color>, <length>, <number>, <angle>, <time>, * 등) |
inherits |
부모로부터 값을 상속할지 여부 (true / false) |
initial-value |
타입과 일치하는 기본값 (syntax가 *가 아닐 경우 필수) |
왜 일반 CSS 변수는 트랜지션이 안 될까요? 브라우저는
--color: #fff를 문자열"#fff"로 저장합니다.transition은 숫자 보간이 필요한데, 문자열에서는 시작값과 끝값 사이를 계산하는 것 자체가 불가능합니다.@property로syntax: "<color>"를 등록하는 순간, 브라우저는 이 값이 색상임을 인지하고 R·G·B 채널을 수치로 보간할 수 있게 됩니다.
왜 디자인 토큰과 연결되는가
디자인 토큰은 색상·간격·타이포그래피 같은 디자인 결정을 플랫폼 독립적 값으로 추상화한 것입니다. 이 토큰을 CSS 커스텀 프로퍼티로 출력할 때 @property 등록을 함께 추가하면 두 가지 효과를 얻을 수 있습니다.
- 타입 안전성: 잘못된 값이 들어오면 브라우저가
initial-value로 조용히 폴백해 사일런트 버그를 방지합니다. - 애니메이션 해금: 테마 전환 시 색상, 간격, 각도 등이 CSS만으로 부드럽게 보간됩니다.
브라우저 지원 현황
2024년 7월 9일, @property가 Baseline Newly Available 상태에 진입했습니다. 이제 모든 주요 브라우저에서 안심하고 사용할 수 있습니다.
| 브라우저 | 지원 버전 |
|---|---|
| Chrome / Edge | 85+ |
| Firefox | 128+ |
| Safari | 16.4+ |
실전 적용
라이트/다크 테마 색상 트랜지션
가장 즉각적인 효과를 볼 수 있는 사례입니다. @property로 색상 토큰을 등록하면 테마 전환 시 색상이 뚝 끊기지 않고 부드럽게 보간됩니다.
/* globals.css */
/* 1. 색상 토큰 타입 등록 */
@property --color-bg {
syntax: "<color>";
inherits: true;
initial-value: #ffffff;
}
@property --color-text {
syntax: "<color>";
inherits: true;
initial-value: #111111;
}
/* 2. 루트에서 트랜지션 선언 */
:root {
--color-bg: #ffffff;
--color-text: #111111;
transition: --color-bg 0.3s ease, --color-text 0.3s ease;
}
/* 3. 다크 테마 값만 덮어쓰기 */
[data-theme="dark"] {
--color-bg: #0f0f0f;
--color-text: #f5f5f5;
}
body {
background-color: var(--color-bg);
color: var(--color-text);
}| 포인트 | 설명 |
|---|---|
inherits: true |
루트에서 선언한 값을 모든 자식 요소가 상속받음 |
transition을 :root에 선언 |
data-theme 속성 토글만으로 전체 페이지 색상이 보간됨 |
| JS 불필요 | 속성 토글 외에 별도 스크립트 없음 |
그라디언트 트랜지션
기존에는 CSS만으로 그라디언트를 트랜지션하는 것이 불가능했습니다. 각 색상 정지점을 <color> 타입으로 등록하면 transition 한 줄로 부드러운 그라디언트 변화를 구현할 수 있습니다.
@property --grad-start {
syntax: "<color>";
inherits: false;
initial-value: #6366f1;
}
@property --grad-end {
syntax: "<color>";
inherits: false;
initial-value: #ec4899;
}
.button {
background: linear-gradient(135deg, var(--grad-start), var(--grad-end));
transition: --grad-start 0.4s ease, --grad-end 0.4s ease;
}
.button:hover {
--grad-start: #8b5cf6;
--grad-end: #f43f5e;
}
inherits: false를 사용한 이유: 그라디언트 색상은 이 컴포넌트 내부에서만 제어하는 값이므로 부모로부터 상속받지 않도록 격리합니다. 전역으로 공유되어야 하는 테마 색상에는inherits: true가 적합합니다.
숫자 토큰으로 진행 표시기 애니메이션
<number> 타입을 활용하면 진행률 같은 수치도 @keyframes로 자연스럽게 애니메이션할 수 있습니다.
@property --progress {
syntax: "<number>";
inherits: false;
initial-value: 0;
}
.progress-ring {
/* stroke-dasharray: 100으로 전체 둘레를 100으로 설정한 경우 */
stroke-dasharray: 100;
--progress: 0;
stroke-dashoffset: calc(100 - var(--progress));
transition: --progress 1s cubic-bezier(0.4, 0, 0.2, 1);
}
/* JS에서 --progress 값만 변경하면 부드러운 애니메이션이 적용됩니다 */
/* element.style.setProperty('--progress', '75'); */Style Dictionary v4 — @property 자동 생성 포매터
Style Dictionary는 디자인 토큰 JSON을 CSS, Android, iOS 등 각 플랫폼에 맞는 코드로 변환해주는 오픈소스 빌드 도구입니다. v4에서 기존 CommonJS 동기 방식(v3)을 버리고 ESM 기반의 비동기 아키텍처로 전면 재작성되었습니다. 덕분에 커스텀 포매터에서도 await를 사용할 수 있게 되었습니다.
먼저 포매터가 읽을 토큰 JSON 구조입니다.
{
"color": {
"primary": {
"$type": "color",
"$value": "#6366f1"
},
"secondary": {
"$type": "color",
"$value": "#ec4899"
}
},
"spacing": {
"base": {
"$type": "dimension",
"$value": "16px"
}
}
}이 JSON을 읽어 @property 선언을 자동으로 생성하는 커스텀 포매터입니다.
// style-dictionary.config.mjs
import StyleDictionary from 'style-dictionary';
const TYPE_MAP = {
color: '<color>',
number: '<number>',
dimension: '<length>',
duration: '<time>',
};
StyleDictionary.registerFormat({
name: 'css/at-property',
format: async ({ dictionary }) => {
// transformGroup: 'css' 적용 후 token.name은 kebab-case
// CSS 변수명으로 사용하려면 -- 접두사를 직접 붙여야 합니다
const registrations = dictionary.allTokens
.filter(token => TYPE_MAP[token.$type])
.map(token => `@property --${token.name} {
syntax: "${TYPE_MAP[token.$type]}";
inherits: true;
initial-value: ${token.value};
}`)
.join('\n\n');
const variables = dictionary.allTokens
.map(token => ` --${token.name}: ${token.value};`)
.join('\n');
return `${registrations}\n\n:root {\n${variables}\n}`;
},
});
export default {
source: ['tokens/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
destination: 'tokens.css',
format: 'css/at-property',
},
},
};| 코드 포인트 | 설명 |
|---|---|
--${token.name} |
transformGroup: 'css'가 적용된 token.name은 color-primary 같은 kebab-case이므로 -- 접두사를 직접 붙여야 유효한 CSS 변수명이 됩니다 |
TYPE_MAP |
토큰 $type을 CSS syntax 문자열로 변환하는 매핑 테이블로, 필요 시 타입을 자유롭게 추가할 수 있습니다 |
filter(token => TYPE_MAP[token.$type]) |
매핑 가능한 타입의 토큰만 @property 등록 대상으로 선별합니다 |
format: async |
v4의 비동기 포매터 시그니처로, await를 사용하는 외부 API 호출 등이 가능합니다 |
Tailwind CSS v4 — @theme과 @property
Tailwind v4는 기존 tailwind.config.js 파일을 버리고, CSS 파일 자체를 단일 설정 소스로 사용하도록 전면 설계가 바뀌었습니다. @theme 블록에 토큰을 선언하면 엔진이 내부적으로 @property 선언을 자동 생성하고, bg-brand-500·duration-fast 같은 유틸리티 클래스를 동적으로 산출합니다.
/* globals.css */
@import "tailwindcss";
@theme {
--color-brand-500: #6366f1;
--color-brand-600: #4f46e5;
--duration-fast: 150ms;
--duration-base: 300ms;
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--color-hero-from: #6366f1;
--color-hero-to: #ec4899;
}
/*
Tailwind의 @property 자동 등록은 Tailwind 내부 토큰에 한정됩니다.
사용자가 @theme에 추가한 커스텀 토큰을 트랜지션에 활용하려면
@property를 별도로 명시적으로 선언해야 합니다.
*/
@property --color-hero-from {
syntax: "<color>";
inherits: false;
initial-value: #6366f1;
}
@property --color-hero-to {
syntax: "<color>";
inherits: false;
initial-value: #ec4899;
}
.hero {
background: linear-gradient(135deg, var(--color-hero-from), var(--color-hero-to));
transition: --color-hero-from 0.5s ease, --color-hero-to 0.5s ease;
}Style Dictionary + Tailwind v4 통합 파이프라인
Figma에서 출발해 최종 CSS까지 이어지는 전체 워크플로입니다.
디자인 도구 (Figma / Tokens Studio)
↓ JSON 토큰 내보내기
Style Dictionary v4
├── 커스텀 포매터: @property 선언 자동 생성
└── 출력: tokens.css
↓ CSS 파일 import
globals.css (@theme 블록에 병합)
↓ Tailwind v4 엔진 처리 (Lightning CSS)
최종 CSS (타입 등록 + 유틸리티 클래스)/* globals.css */
@import "tailwindcss";
@import "./tokens.css"; /* Style Dictionary 출력물 */
/*
@theme inline: @theme 블록 내 변수를 Tailwind가 별도 CSS 변수로 재출력하지 않고
참조 위치에 값을 직접 인라인으로 치환합니다.
이미 tokens.css에 --color-brand-500이 정의되어 있으므로 중복 출력을 방지할 수 있습니다.
*/
@theme inline {
--color-brand: var(--color-brand-500);
--duration-transition: var(--duration-base);
}장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 타입 안전성 | 잘못된 값 입력 시 브라우저가 initial-value로 폴백하여 사일런트 버그를 방지합니다 |
| 애니메이션 해금 | 그라디언트·각도·수치 등 기존에 보간이 불가능했던 값을 transition/@keyframes로 제어할 수 있습니다 |
| 성능 향상 | 타입 정보 덕분에 브라우저 렌더링 최적화 여지가 증가하며, Tailwind v4는 이를 활용해 대규모 페이지 렌더 속도를 개선했습니다 |
| 순수 CSS | CSS.registerProperty() 없이 CSS 파일만으로 등록이 완결됩니다 |
| 표준 기반 | W3C 명세로 장기적 안정성이 보장됩니다 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 브라우저 지원 하한 | Firefox 128+, Safari 16.4+ 미만 구형 브라우저에서 미지원 | 점진적 강화(Progressive Enhancement) 패턴 적용 |
initial-value 필수 |
syntax가 *가 아닌 경우 생략 시 등록 자체가 실패 |
포매터에서 initial-value 누락 토큰을 빌드 시 검증 |
| 전역 스코프 고정 | @property는 항상 전역(:root) 범위로 등록됨, 컴포넌트 캡슐화 불가 |
컴포넌트 지역 변수는 @property 없이 일반 --var 사용 |
| Style Dictionary 마이그레이션 | v4 전환 시 CJS → ESM, 동기 → 비동기 API 전면 수정 필요 | 공식 마이그레이션 가이드 참고, 단계적 전환 권장 |
Tailwind @theme 제약 |
사용자 정의 토큰의 @property 자동 등록 미지원 |
별도 @property 선언을 명시적으로 추가 |
Progressive Enhancement(점진적 강화):
@property선언은 미지원 브라우저가 조용히 무시합니다. 즉,@property가 없는 환경에서는 CSS 변수가 일반 변수처럼 동작하고 트랜지션만 빠질 뿐 기본 기능은 유지됩니다. JS에서 지원 여부를 확인하려면 아래 방법을 활용할 수 있습니다.
// @property 지원 여부 감지 (JavaScript)
const supportsAtProperty = CSS.supports(
'@property --x { syntax: "<color>"; inherits: false; initial-value: red; }'
);
if (supportsAtProperty) {
// 향상된 트랜지션 경험 제공
}/* CSS 폴백 패턴 — @property 미지원 브라우저는 @property 블록을 무시합니다 */
:root {
--color-bg: #ffffff; /* 미지원 환경: 즉시 값 변경 (트랜지션 없음) */
}
/* 지원 환경: @property 등록 및 트랜지션 활성화 */
@property --color-bg {
syntax: "<color>";
inherits: true;
initial-value: #ffffff;
}
:root {
transition: --color-bg 0.3s ease;
}실무에서 가장 흔한 실수
-
initial-value를 생략하는 경우:syntax가*가 아닌 모든 타입은initial-value가 필수입니다. 생략하면@property등록 자체가 무효화되어 일반 CSS 변수처럼 동작하게 됩니다. Style Dictionary 포매터에서initial-value누락 여부를 빌드 단계에 검증하는 로직을 추가해 두는 것을 권장합니다. -
@property선언 우선순위를 잘못 이해하는 경우: CSS 캐스케이드 규칙에 따라@property도 일반 선언처럼 나중에 오는 선언이 앞선 선언을 덮어씁니다. 다만 이미 등록된 프로퍼티는 동일한 명세를 따를 때만 갱신되므로, 동일한 이름의@property는 토큰 파일 한 곳에 모아 관리하는 것이 예측 가능하고 유지보수에 유리합니다. -
Tailwind
@theme토큰을 트랜지션에 바로 사용하는 경우:@theme안에 선언한 사용자 정의 토큰은 Tailwind 내부 처리 대상이 아니므로@property자동 등록 대상이 아닙니다. 트랜지션을 적용하려면@property를 반드시 별도로 선언해야 합니다.
마치며
이제 여러분의 디자인 토큰은 단순한 CSS 변수가 아니라, 브라우저가 의미를 이해하는 타입 안전한 계약(contract)입니다. @property는 2024년 7월 이후 모든 주요 브라우저에서 프로덕션 적용이 가능한 표준이며, Style Dictionary v4와 Tailwind v4 파이프라인을 통해 팀 단위의 자동화로 확장할 수 있습니다.
지금 바로 시작해볼 수 있는 3단계를 안내해 드립니다.
-
기존 프로젝트의 색상 토큰 하나에
@property를 등록해 보시면 좋습니다.--color-primary처럼 가장 자주 쓰는 색상 변수를 골라syntax: "<color>"와initial-value를 추가하고,:root에transition: --color-primary 0.3s ease를 선언한 뒤 값을 바꿔보시면 즉각적인 효과를 확인하실 수 있습니다. -
Style Dictionary v4 커스텀 포매터를 도입해 토큰 빌드 단계에서
@property를 자동 생성하는 파이프라인 구성을 고려해 보시기 바랍니다. 이 글의css/at-property포매터 예시를 출발점으로 삼아TYPE_MAP을 확장하면 됩니다. 토큰이 100개 이상으로 늘어날 경우,initial-value누락 검증 로직을 포매터에 추가해 두면 빌드 단계에서 오류를 미리 잡을 수 있습니다. -
Tailwind v4를 사용 중이라면
@theme에 토큰을 정의하고, 트랜지션이 필요한 커스텀 토큰에@property를globals.css에 명시적으로 추가해 두시기 바랍니다. JS 감지 코드나 CSS 폴백 패턴까지 함께 작성해 두면 구형 브라우저 사용자도 기본 동작을 보장받을 수 있으며, CI/CD 파이프라인에서 Style Dictionary 빌드를 자동화하면 Figma 변경사항이 코드에 즉시 반영되는 토큰 싱크 워크플로를 완성할 수 있습니다.
다음 글: W3C DTCG 2025.10 드래프트에 새롭게 추가된
transition타입 토큰을 Figma Tokens Studio와 Style Dictionary로 연동해 디자인–코드 간 전환 효과를 완전히 동기화하는 방법을 살펴보겠습니다.
참고 자료
입문용으로 먼저 읽기 좋은 자료
- @property: Next-gen CSS variables now with universal browser support | web.dev
- @property | MDN Web Docs
- @property | CSS-Tricks Almanac
- CSS @property and the New Style | Ryan Mulligan
실무 적용 및 심화 자료
- The Times You Need A Custom @property Instead Of A CSS Variable | Smashing Magazine
- Using @property for loosely typed CSS custom properties | LogRocket Blog
- CSS @property Explained: The Secret Weapon for Dynamic & Smooth Animations | DEV Community
- Exploring Typesafe design tokens in Tailwind 4 | DEV Community
- BYO CSS tokens to Tailwind v4's new CSS-centric config | nerdy.dev
도구 공식 문서