← 글 목록

notes

CSS - @property

CSS @property의 커스텀 프로퍼티 타입·초기값·상속 설정 메모.

@property는 CSS 변수(--name)에 타입· 초기값·상속 규칙을 등록하는 at-rule입니다. 등록된 변수만 브라우저가 “숫자/색/길이”처럼 의미 있게 해석할 수 있어, calc()·애니메이션·보간(interpolation)이 안정적으로 동작합니다.


1. 왜 필요한가?

일반 CSS 변수는 기본적으로 문자열입니다.

:root {
  --progress: 0.5;
}

.box {
  width: calc(100px * var(--progress));
}

calc() 안에서는 대개 잘 됩니다.

하지만 값이 바뀔 때 부드럽게 보간하거나, @property 없이는 타입이 애매한 경우 브라우저마다 동작이 들쭉날쭉할 수 있습니다.

@property로 등록하면:

  • 이 변수는 <number>다라고 명시
  • 초기값·상속 여부 지정
  • 애니메이션/transitions 시 숫자처럼 보간 가능

2. 문법

@property --변수명 {
  syntax: "<number>";   /* 타입 */
  inherits: true;       /* 상속 여부 */
  initial-value: 0;     /* 초�값 */
}

syntax (자주 쓰는 것)

inherits

  • true: 자식이 부모 값을 물려받음
  • false: 자식은 initial-value부터 시작

initial-value

  • 변수가 아직 설정되지 않았을 때 쓰는 기본값
  • @property 등록 시 필수인 경우가 많음

3. 신이의 GNB 코드에서 쓰는 방식

@property --gnb-move {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

@property --gnb-shape {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

@property --gnb-out {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

의미:

  • -gnb-move: 스크롤 진행률 (0 → 1)
  • -gnb-shape: pill 형태 변화 (0 → 1)
  • -gnb-out: 로고 fade (0 → 1)

React에서 헤더에 이렇게 주입합니다:

style={{
  "--gnb-move": move,
  "--gnb-shape": shape,
  "--gnb-out": out,
}}

CSS에서는 한 번 정의해 두고 여러 곳에서 재사용합니다:

.header {
  height: calc(138px - 82px * var(--gnb-move));
}

.searchSlotDocked {
  top: calc(56px - 49px * var(--gnb-move));
  width: calc(100% - 32px - 100px * var(--gnb-shape));
  padding-left: calc(14px * (1 - var(--gnb-shape)));
}

.gnbRow > :global(div) > :global(div:first-child) {
  opacity: calc(1 - var(--gnb-out));
}

핵심 패턴:

JS는 프레임마다 숫자 3개만 넣고, 위치·높이·폭·투명도·padding은 전부 CSS calc()가 --gnb-*로부터 파생합니다. HTML 프로토타입과 같은 구조입니다.


4. @property 없이 vs 있을 때

없을 때

.header {
  --gnb-move: 0;
  height: calc(138px - 82px * var(--gnb-move));
}
  • calc()만 쓰면 대부분 동작함
  • 변수 타입이 문자열이라, transition/animation으로 -gnb-move 자체를 animate하려 할 때 브라우저가 보간을 못 하거나 이상하게 동작할 수 있음

있을 때

  • -gnb-move가 숫자 타입으로 등록됨
  • 나중에 CSS만으로 transition: --gnb-move 0.2s 같은 것도 이론상 가능 (브라우저 지원 범위 내)
  • calc(138px - 82px * var(--gnb-move))에서 곱셈·보간이 더 예측 가능

지금 구현은 JS가 scrollY → move/shape/out 계산, CSS는 표현만 담당합니다. @property는 “이 변수들은 0~1 숫자다”라고 브라우저에 알려주는 역할입니다.


5. inherits: true를 쓰는 이유

헤더(.header)에 --gnb-move 등을 넣으면, 같은 서브트리 안의 자식도 var(--gnb-move)를 쓸 수 있습니다.

.header { --gnb-move: 0.7; }        /* style prop으로 덮어씀 */
.searchSlotDocked {
  top: calc(56px - 49px * var(--gnb-move));  /* 0.7 상속·사용 */
}

inherits: false면 자식이 부모 값을 안 물려받고 initial-value: 0부터 시작합니다. GNB처럼 한 헤더 아래 여러 요소가 같은 진행률을 쓸 때는 true가 자연스럽습니다.


6. @property와 Houdini

@property는 CSS Houdini API의 일부입니다.

  • Paint API, Layout API 등과 함께 “CSS를 JS처럼 확장”하는 실험적 방향이었음
  • @property만은 Chrome / Edge / Safari(최신) 등에서 실용적으로 많이 씀
  • Firefox는 버전에 따라 지원이 달라, 미지원 시에도 calc(var(--x))는 대체로 동작하지만 @property 등록 이점(transition 등)은 줄어듦

프로덕션에서는:

  • 핵심 레이아웃은 @property + calc() (지금처럼)
  • 미지원 fallback: 같은 변수를 그냥 -gnb-move: 0으로 두면 대부분 표시는 됨

7. JS inline style과 함께 쓸 때 주의

React:

style={{ "--gnb-move": move } as CSSProperties}
  • move는 숫자 0.42 → DOM에는 -gnb-move: 0.42
  • @property의 syntax: "<number>"와 맞음
  • "0.42px"처럼 단위를 붙이면 <number>와 안 맞을 수 있음

색/길이 변수를 등록할 때는 syntax를 "<color>", "<length>"로 맞춰야 합니다.


8. @property로 CSS transition까지 하려면 (참고)

등록된 변수는 (지원 브라우저에서) transition 대상이 될 수 있습니다:

@property --gnb-move {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

.header {
  transition: --gnb-move 0.15s ease-out;
}

지금 GNB는 스크롤 1:1 연동이라 JS에서 매 프레임 --gnb-move를 갱신합니다. transition을 추가하면 스크롤과 한 박자 늦게 따라올 수 있어, 현재처럼 transition 없이 직접 넣는 편이 맞습니다.


9. 한 줄 요약