@property와 동적 스타일링

작성일:2025.05.13|조회수:0

@property와 동적 스타일링

프론트엔드 개발자들 모인 자리에 가서 '어떤 스타일링 라이브러리 좋아하세요?'하고 물어보면 못해도 30분은 할 이야기가 생긴다. 내 경우에는 여러번 밝힌 바와 같이 TailwindCSS와 ModuleCSS를 선호한다. 하지만 내 선호를 밝히면 늘 돌아오는 질문이 있다. '그것들은 동적 스타일링하기 불편하지 않아요?'하고.

요즘 사용자를 늘려가고 있는 Vanila-extract나 이제는 죽어버린 StyledComponent와 비교하면 앞서 언급한 두 라이브러리는 확실히 동적 스타일링에 취약하다. 특히 TailwindCSS는 빌드 과정에서 사용된 클래스 이름만 추출해서 최종 CSS를 생성하는 'content scanning' 과정이 필요한데, 이 때문에 아래와 같이 동적으로 클래스 이름을 조합하거나 조건부로 생성하는 패턴은 제대로 처리되지 않을 수 있다.

JS
// ✅ okay
className={isActive ? 'bg-blue-500' : 'bg-gray-300'}
 
// ❌ wrong
className={'bg-' + color}

CSS 커스텀 속성

ModuleCSS의 경우에는 상황이 조금 나은 편이다. CSS 커스텀 속성을 사용하면 컴포넌트에 동적인 값을 제공할 수 있고, CSS가 이를 런타임에서도 해석할 수 있기 때문이다. 예를 들어, 테마 색상이나 애니메이션 지속 시간 같은 값을 props를 통해 전달하고, 해당 값을 style 속성으로 변수에 바인딩하면, CSS 모듈은 이를 유연하게 처리할 수 있다.

이런 접근 방식은 정적인 클래스 이름을 기반으로 동작하는 Tailwind보다 확실히 더 동적 스타일링에 유리하다. 물론 스타일 로직이 복잡해지면 유지보수가 어려워질 수 있고, CSS 커스텀 속성의 범위 관리에도 신경 써야 하지만, 적절히 활용한다면 기능과 유지보수 사이의 균형을 잡을 수 있는 현실적인 대안이 된다.

JSX
export default function Page() {
  const [size, setSize] = useState(100);
  
  // CSS 커스텀 속성으로 사이즈 전달
  const style = {
    "--img-size": `${size}px`
  } as React.CSSProperties;

  return (
    <div>
      <div className={styles.box} style={style}>
        박스 영역
      </div>
      <button onClick={() => setSize(size + 10)}>사이즈 키우기</button>
    </div>
  )
}
CSS
/* Box.module.css */
.image {
  width: var(--img-size);
  height: var(--img-size);
  background-color: #eee;
  display: flex;
  align-items: center;
  justify-content: center;
}

@property

CSS 커스텀 속성은 단순히 값의 저장소 역할만 하므로, 애니메이션이나 계산 등에서 사용하기에 제한적이다. 또한, 값이 문자열로 처리되기 때문에 계산 기능을 활용하는 데에도 한계가 있다. 반면, @property는 CSS 커스텀 속성에 타입, 초기값, 상속 여부 등을 정의할 수 있는 규칙이다. 이 규칙은 CSS Houdini의 일부로, 커스텀 속성이 애니메이션이나 계산에 활용될 수 있도록 확장하는 역할을 한다. @property를 사용하면, 커스텀 속성이 특정 데이터 타입을 갖도록 정의하고, 그 값이 애니메이션 등에서 자연스럽게 동작할 수 있다.

CSS
@property --progress {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}
 
.progress_bar {
  width: var(--progress);
  height: 10px;
  background: blue;
  transition: --progress 0.3s;
}

/* syntax에 사용할 수 있는 타입 */
<length>: px, em, rem 등의 길이 값
<number>: 숫자 값
<percentage>: 백분율 값
<color>: 색상 값
<image>: 이미지 값
<angle>: 각도 값
<time>: 시간 값
<resolution>: 해상도 값
<transform-function>: 변환 함수
*: 모든 값

한 가지 주의할 점은, @property를 CSS 모듈 내부에서 선언하더라도 해당 선언은 로컬 스코프가 아니라 전역 스코프에 적용된다는 점이다. 이로 인해 여러 컴포넌트의 모듈 CSS 파일에서 동일한 이름의 @property를 선언하면 네이밍 충돌이 발생할 수 있다. 이러한 충돌을 방지하려면 고유한 접두사를 사용하거나, @property 선언은 전역 스타일 파일(global.css)에만 작성하는 것이 바람직하다.

attr()

아예 CSS 커스텀 속성을 사용하지 않고도 동적 스타일링을 할 수 있도록 하는 기능도 있다. attr() 함수는 태그에 존재하는 속성 값을 CSS에서 직접 읽어와 사용할 수 있게 한다. 스타일을 구성하는 로직을 JS가 아닌 HTML 속성에 위임하면서도 CSS가 그것을 동적으로 해석할 수 있게 해주며, 이를 통해 모듈 CSS나 전통적인 CSS에서도 동적 스타일링을 보다 선언적으로 구현할 수 있는 가능성을 제공한다.

attr() 함수는 아래와 같은 구조로 작성되며 데이터타입과 기본값은 선택사항이다. 다만, 데이터타입을 제공하지 않을 시 속성으로 넘겨받는 값은 문자열 취급이며, 따라서 숫자를 받아야 하는 width나 height 혹은 hex 값으로 색상을 넘겨받아야 하는 background-color 등에서는 반드시 데이터타입을 제공해야 한다.

CSS
property: attr(속성명 데이터타입, 기본값);

위에서 CSS 커스텀 속성을 통해 작성했던 코드를 attr() 함수로 변경하면 아래와 같다. 큰 틀에서는 같지만 훨씬 짧고 간단하게 작성할 수 있다는 점이 좋다.

JSX
export default function Page() {
  const [size, setSize] = useState(100);

  return (
    <div>
      <div
        className={styles.box}
        data-size={size}
        data-hover-color="red"
      >
        박스 영역
      </div>
      <button onClick={() => setSize(size + 10)}>사이즈 키우기</button>
    </div>
  )
}
CSS
/* Box.module.css */
.box {
  width: attr(data-size rem, 1rem);
  height: attr(data-size rem, 1rem);
  background-color: #eee;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s;
}

.box:hover {
  background-color: attr(data-hover-color type(<color>));
}

이렇게만 놓고보면 attr() 함수가 @property보다 월등히 뛰어난 것처럼 느껴진다. 하지만 앵커 포지셔닝 때와 마찬가지로 attr() 함수는 현재 대부분의 브라우저에서 속성 값을 콘텐츠(content) 속성 안에서만 사용할 수 있도록 제한되어 있으며, color, width, margin과 같은 일반적인 스타일 속성에 자유롭게 사용할 수 있는 기능은 아직 정식으로 구현되지 않았거나 실험적인 단계에 머물러 있다. 다시 말해, 지금 이 기능을 활용하고자 해도 실제로 적용 가능한 브라우저는 소수에 불과하며, 이조차도 플래그를 활성화해야 하거나 불안정한 동작을 보일 수 있다.

따라서 이런 상황에서 attr()을 주요한 스타일링 수단으로 채택하는 것은 매우 위험하며, 현실적으로는 아직 프로덕션에 투입할 수 없는 기술이라고 보는 것이 정확하다. 결국 attr() 함수의 등장은 미래의 가능성을 보여주는 흥미로운 시도이지만, 지금 당장은 호기심 이상의 실용성을 제공하진 못한다.

그러나 일단 이런 게 존재한다는 건 알고 있으니, 나중에 정식 출시되면 누구보다 빠르게 남들과는 다르게 동적 스타일링을 처리할 수 있지 않을까 :)

더 읽어보기

  • 2026.03.01

    렌더링 전략 정리

    리액트와 Next.js에서 렌더링 전략은 단순한 옵션 선택이 아니다. 이는 서비스의 초기 로딩 속도, 서버 비용, 캐싱 전략, SEO 노출, 개발 복잡도까지 동시에 좌우하는 아키텍처 결정이다. 프로젝트 규모가 커질수록 “어디에서 HTML을 생성하는가”, “언제 자바스크립트를 실행하는가”…

  • 2026.02.22

    View Transition API

    웹 애플리케이션에서 전환 품질은 기능 완성도와 동등한 수준으로 중요하다. 사용자가 목록에서 항목을 선택해 상세 화면으로 이동할 때, 화면이 자연스럽게 이어지면 서비스는 빠르고 안정적으로 느껴진다. 반대로 동일한 기능이라도 전환이 끊기면 체감 성능과 신뢰도는 동시에 하락한다. 전환은 부가…

  • 2026.01.03

    CSS Color Functions

    이 포스트는 Sunkanmi Fafowora가 css-tricks에 올린 CSS Color Functions 게시글을 번역한 것이다. 번역하는 과정에서 다소 의역이 있을 수 있으며, 일부 번역에는 사견이 포함되어있기도 하다.몇 달 전 누군가 저에게 “웹사이트가 돋보이려면 무엇이 필요할까…

  • 2025.11.02

    @function

    CSS는 오랫동안 스타일 값을 선언하는 정적인 언어로 인식되어 왔다. 그 안에서 반복되는 패턴이나 디자인 시스템의 일관성을 유지하기 위해 Sass나 CSS-in-JS 같은 추가 도구들을 활용해 왔으며, 최근에는 CSS Custom Properties를 활용하여 값의 재사용을 가능하게 만…

  • 2026.04.11

    Trie 자료구조

    문자열 데이터를 다룰 때 단순히 “이 단어가 있나?”만으로는 부족한 순간이 있다. 자동 완성처럼 특정 접두사로 시작하는 후보를 모아야 할 때도 있고, 어떤 키에 값을 두고 빠르게 찾고 싶을 때도 있다. 이럴 때 가장 자연스럽게 떠올릴 수 있는 자료구조가 바로 Trie이다.Trie는 무엇…

  • 2026.03.19

    Streams API 부록 2. 왜 이미지는 위에서 아래로 나타날까

    웹 페이지에서 이미지를 로드할 때 흥미로운 장면을 종종 볼 수 있다. 이미지가 한 번에 완전히 나타나는 것이 아니라, 위에서 아래로 조금씩 채워지면서 나타나는 경우가 있기 때문이다. 특히 네트워크가 느리거나 이미지가 큰 경우 이런 현상이 더 분명하게 보인다. 마치 이미지가 위쪽부터 스캔…

댓글

댓글을 불러오는 중...