
CSS는 오랫동안 정적인 선언 언어처럼 다뤄졌다. 물론 calc()도 있고, 커스텀 속성도 있고, 미디어 쿼리나 컨테이너 쿼리도 있으니 실제로 그렇게 단순한 언어는 아니다. 그래도 반복되는 계산이나 디자인 시스템의 규칙을 재사용하려고 하면 금방 한계가 보인다. 값은 변수로 빼둘 수 있지만, “이 값을 받아서 이런 방식으로 계산한다”는 규칙 자체를 CSS 안에서 이름 붙여 재사용하기는 어려웠다.
그래서 Sass 함수나 mixin, 혹은 CSS-in-JS의 자바스크립트 함수를 끌어오게 된다. 빌드 타임에 계산해도 되는 값이라면 이 방식은 꽤 오래 잘 작동했다. 문제는 CSS 커스텀 속성처럼 런타임에 바뀌는 값과 섞일 때다. Sass는 브라우저에서 실행되지 않으므로, 브라우저가 나중에 알게 되는 값까지 함수처럼 계산해주지는 못한다.
CSS Custom Functions, 즉 @function은 이 빈틈을 CSS 안에서 메우려는 시도다. CSS 파일 안에 직접 함수를 정의하고, 그 함수를 속성 값 자리에서 호출한다. 문법만 놓고 보면 Sass 함수와 비슷해 보이지만, 중요한 차이는 이 함수가 CSS의 계산 모델 안에 들어온다는 점이다.
가장 단순한 형태는 이렇다.
@function --scale(--size <length>, --ratio <number>: 1.25) returns <length> {
result: calc(var(--size) * var(--ratio));
}
.card {
padding: --scale(12px, 1.5);
border-radius: --scale(6px);
}함수 이름은 커스텀 속성과 비슷하게 --로 시작한다. 호출할 때도 --scale()처럼 dashed function 문법을 사용한다. 매개변수에는 타입을 지정할 수 있고, : 뒤에 기본값을 둘 수 있다. 반환 타입은 returns <length>처럼 적는다. 반환 타입을 생략하면 어떤 타입이든 허용하는 쪽에 가깝지만, 실제로는 result가 쓰이는 속성의 문맥과 맞아야 한다.
함수 본문에서는 result 디스크립터가 반환값 역할을 한다. 다만 자바스크립트의 return처럼 그 줄에서 즉시 종료되는 것은 아니다. CSS답게 선언 순서가 중요하고, 여러 result가 있다면 나중에 적용되는 선언이 이긴다.
@function --suitable-font-size() returns <length> {
result: 16px;
@media (width > 1000px) {
result: 20px;
}
}
.title {
font-size: --suitable-font-size();
}이 함수는 기본적으로 16px을 반환하지만, 미디어 쿼리가 참이면 뒤쪽 result가 적용되어 20px을 반환한다. 반대로 순서를 잘못 쓰면 의도와 다르게 항상 마지막 값만 남을 수 있다.
@function --suitable-font-size() returns <length> {
@media (width > 1000px) {
result: 20px;
}
result: 16px;
}이 코드는 미디어 쿼리가 참이어도 마지막 result: 16px;이 다시 덮어쓴다. CSS 안의 함수라고 해서 갑자기 명령형 언어가 되는 것은 아니다. 기존 CSS의 캐스케이드와 선언 순서 감각이 그대로 따라온다.
함수 안에서는 지역 커스텀 속성처럼 중간 값을 둘 수도 있다. 복잡한 계산을 한 줄 calc()에 우겨 넣는 대신, 계산의 의미를 나눠서 적을 수 있다는 점이 좋다.
@function --circle-area(--radius <number>) returns <number> {
--radius-square: calc(var(--radius) * var(--radius));
result: calc(pi * var(--radius-square));
}물론 이 예제가 당장 실무 UI에 엄청 유용하다는 뜻은 아니다. 중요한 것은 CSS가 최종 값만 적는 곳에서, 값이 만들어지는 규칙까지 표현하는 쪽으로 조금씩 확장되고 있다는 점이다. 디자인 시스템에서는 이런 차이가 꽤 크다. 여백, 글자 크기, 투명도, 색상 변형 같은 규칙을 함수 이름으로 드러낼 수 있으면, 반복되는 calc()보다 의도가 잘 남는다.
색상 함수를 예로 들면 더 그럴듯하다.
@function --transparent(--color type(<color>), --alpha type(<number>)) returns type(<color>) {
result: oklch(from var(--color) l c h / var(--alpha));
}
.notice {
--notice-color: oklch(65% 0.2 250);
color: var(--notice-color);
background-color: --transparent(var(--notice-color), 0.16);
}여기서 --transparent()는 색상과 알파 값을 받아 반투명한 색상을 만든다. 같은 계산을 여러 컴포넌트에서 반복하지 않고, “기준 색상의 투명 배경”이라는 의미 단위로 호출할 수 있다. Sass 함수로도 비슷한 추상화는 가능하지만, CSS 커스텀 속성과 런타임 조건이 섞일수록 네이티브 CSS 함수 쪽이 더 자연스러운 그림이 된다.
Sass 함수와 비교하면 차이는 대략 이렇다.
| 구분 | Sass 함수 | CSS @function |
|---|---|---|
| 실행 시점 | 빌드 타임 | 브라우저의 CSS 계산 단계 |
| 외부 도구 | Sass 컴파일러 필요 | CSS 기능으로 동작 |
| 런타임 값 | 컴파일 이후에는 값이 고정됨 | 커스텀 속성, 미디어 쿼리 등 CSS 조건 변화와 함께 계산 가능 |
| 적용 범위 | Sass를 쓰는 프로젝트 내부 | 브라우저가 지원하는 CSS 환경 |
다만 여기서 너무 들뜨면 안 된다. MDN 기준으로 @function은 아직 experimental이고 limited availability에 속한다. 일부 환경에서 CSS custom functions를 실험적으로 사용할 수 있더라도, 모든 주요 브라우저에서 안심하고 프로덕션에 넣을 수 있는 기능이라고 보기는 어렵다. 지원하지 않는 브라우저에서는 함수 호출이 들어간 선언이 유효한 값으로 계산되지 않을 수 있으므로, 핵심 스타일을 여기에만 의존하면 위험하다.
기능 감지는 @supports로 어느 정도 할 수 있다. 다만 지원 여부와 실제 함수 로직의 세부 동작까지 모두 보장하는 만능 장치는 아니므로, 실험 코드와 프로덕션 코드는 분리해서 생각하는 편이 낫다.
.card {
padding: calc(var(--space, 8px) * 2);
}
@supports (padding: --scale(1px, 2)) {
@function --scale(--size <length>, --ratio <number>: 1) returns <length> {
result: calc(var(--size) * var(--ratio));
}
.card {
padding: --scale(var(--space, 8px), 2);
}
}이 예제처럼 기본 스타일을 먼저 제공하고, 지원하는 브라우저에서만 @function을 얹는 식이 안전하다. 물론 실제 프로젝트에서 이 정도 fallback을 감수할 가치가 있는지는 별개의 문제다. 대부분의 경우 커스텀 속성, calc(), color-mix(), Sass 함수 정도로 충분할 가능성이 높다.
이름이 조금 헷갈리는 부분도 있다. CSS Functions and Mixins Module은 custom functions와 mixins를 함께 다루지만, 브라우저에서 이야기할 때 둘의 상태를 섞어 말하면 안 된다. CSS custom functions는 일부 지원과 실험이 진행되고 있는 반면, CSS mixins는 아직 브라우저에서 사용할 수 있는 기능으로 보면 곤란하다. Sass의 @mixin을 떠올리고 네이티브 CSS에서도 곧바로 같은 방식으로 쓸 수 있다고 생각하면 자빠지기 좋다.
그래서 @function을 바라보는 태도는 꽤 분명하다. 실무의 기본 도구라기보다는, CSS가 어디로 가고 있는지 보여주는 신호에 가깝다. 커스텀 속성이 “값에 이름을 붙이는 기능”이었다면, @function은 “값을 만드는 규칙에 이름을 붙이는 기능”이다. 이 차이는 작지 않다.
결국 디자인 시스템에서 반복되는 것은 값 자체만이 아니다. 기준 간격에 배율을 곱한다, 기준 색상에 투명도를 입힌다, 화면 조건에 따라 적절한 크기를 고른다 같은 규칙이 반복된다. 이 규칙을 빌드 도구나 자바스크립트에만 맡기면 CSS 안에는 계산 결과나 긴 calc()만 남는다. @function은 그 규칙을 CSS의 언어 안으로 끌어오려는 기능이다.
아직은 조심해서 봐야 한다. 하지만 방향은 흥미롭다. CSS가 단순히 스타일 값을 나열하는 언어가 아니라, 스타일 시스템의 규칙과 의미를 직접 표현하는 언어가 되어가고 있다는 뜻이기 때문이다. 당장 모든 프로젝트에 넣을 수는 없어도, 이 기능이 어떤 문제를 해결하려는지 알고 있으면 앞으로 CSS를 설계하는 기준이 조금 달라질 수 있다. 여기까지로 충분하다.
댓글
댓글을 불러오는 중...