Custom functions and mixins/Using CSS custom functions

김동현·2026년 3월 21일

mdn 학습 번역 - CSS

목록 보기
77/190

안녕하세요! 오늘 다뤄볼 주제는 프론트엔드 개발자라면 반드시 주목해야 할 CSS의 혁명적인 최신 기능, 바로 CSS 커스텀 함수(CSS custom functions)입니다.

과거에는 Sass나 SCSS 같은 전처리기(Preprocessor)를 통해서만 가능했던 '함수 만들기'가 이제 순수 CSS(Vanilla CSS)에서도 가능해졌습니다. 변수(Custom Properties)를 넘어서 논리를 처리하고 값을 반환하는 진정한 의미의 프로그래밍이 CSS 내에서 가능해진 것이죠.

이 강력한 기능을 어떻게 활용할 수 있는지, MDN 공식 문서를 통해 꼼꼼하게 번역하고 실무 팁을 더해 설명해 드리겠습니다!


CSS 커스텀 함수 사용하기 (Using CSS custom functions)

CSS 커스텀 함수를 사용하면 인자(arguments)를 받아들이고, 복잡한 로직(CSS if() 함수나 @media @규칙 같은 기능들을 사용하여 정의됨)을 포함하며, 그 로직에 따라 결과값을 반환하는 재사용 가능한 CSS 코드 블록을 만들 수 있습니다. 이 기능은 CSS 커스텀 속성(CSS 변수)과 비슷하게 작동하지만 훨씬 더 큰 유연성을 제공합니다.

이 문서에서는 커스텀 함수를 어떻게 사용하는지 알아보고 몇 가지 실용적인 예제들을 선보일 것입니다.

이 문서에서 다룰 내용 (In this article)


함수의 기초 (Function basics)

기본적인 CSS 커스텀 함수 정의는 다음과 같이 생겼습니다:

@function --half-opacity() {
  result: 0.5;
}

@function 구문 뒤에 함수의 이름을 정의합니다. 여기서는 --half-opacity입니다. 이 이름은 반드시 <dashed-ident> 타입이어야 합니다. 즉, 반드시 대시 두 개(--)로 시작해야 하며 대소문자를 구분합니다. 함수 이름 바로 뒤에는 괄호(()) 한 쌍과 중괄호({}) 한 쌍이 이어집니다.

💡 노트 (Note):
만약 여러 개의 CSS 함수가 동일한 이름을 갖게 된다면, 우선순위가 더 높은(stronger) 캐스케이드(cascade) @layer에 있는 함수가 이깁니다. 만약 모두 같은 레이어에 있다면, 소스 코드 순서상 가장 나중에 정의된 함수가 이깁니다.

중괄호 안쪽이 바로 함수의 본문(body)이며, 이곳에 함수의 로직이 정의됩니다. 이 본문에는 여러 개의 선언이 들어갈 수 있는데, 함수 본문 내에서만 유효한(locally scoped) 커스텀 속성을 선언할 수도 있고, @media 같은 @규칙, 그리고 result 설명자(descriptor)도 포함될 수 있습니다. 이 result 설명자의 값이 평가(evaluate)되어 최종적으로 함수가 반환(return)할 값을 결정하게 됩니다.

위 예제에서 우리는 result0.5로 설정했습니다. 따라서 --half-opacity() 함수는 호출될 때마다 항상 0.5를 반환할 것입니다.

왜 "return"이 아니라 "result"인가요? (Why "result" and not "return"?)

result 설명자는 기능적으로 보면 자바스크립트의 return 문과 매우 비슷해 보입니다. 하지만 CSS 함수에서는 return이라는 단어를 쓰지 않습니다. 왜냐하면 자바스크립트의 return 문은 그 줄에 도달하는 즉시 함수를 종료하고 값을 반환하지만, CSS 함수는 result 선언을 만났다고 해서 바로 함수 실행을 끝내지 않기 때문입니다.

CSS 함수의 본문은 처음부터 끝까지 모두 평가됩니다. 만약 본문 안에 여러 개의 result 선언이 포함되어 있다면, 자바스크립트처럼 첫 번째에서 끝나는 것이 아니라 소스 순서상 가장 마지막에 선언된 result가 이전의 선언들을 덮어쓰고 최종 반환값이 됩니다.

CSS 함수 호출하기 (Calling a CSS function)

CSS 함수는 <dashed-function> 문법을 사용해서, 해당 값이 들어갈 수 있는 어떤 속성값 자리에서든 호출할 수 있습니다. 호출할 때는 함수 이름 뒤에 괄호를 붙이고, (만약 있다면) 함수에 전달할 인자(arguments)를 괄호 안에 넣습니다. 예를 들어, 우리가 만든 --half-opacity() 함수는 다음과 같이 호출할 수 있습니다:

h2 {
  opacity: --half-opacity();
}

이 함수는 항상 0.5라는 값을 반환하기 때문에, 위의 선언은 opacity: 0.5와 완전히 동일합니다. 사실 이건 별로 쓸모가 없죠. 그냥 커스텀 속성(변수)을 쓰거나 숫자 0.5를 직접 입력하는 게 낫습니다.

이제 CSS 함수를 어떻게 '제대로' 활용할 수 있는지 다음 단계로 넘어가 보겠습니다.


CSS 함수 기능 감지하기 (Feature detecting CSS functions)

매개변수(parameter)가 없는 CSS 함수의 실용적인 사용처 중 하나는 바로 기능 감지(feature detection)입니다. 이 문서에서 살펴볼 모든 예제들에서 우리는 다음과 같은 형태의 --supports() 함수를 정의하고 있습니다:

@function --supports() {
  result: none;
}

이렇게 해두면, "해당 기능을 지원하지 않음"을 알리는 배너 영역을 만든 뒤 그 요소의 display 속성을 --supports()로 설정할 수 있습니다:

<p class="support">
  ⚠️ Your browser doesn't currently support CSS custom functions.
</p>
.support {
  /* ... */
  display: --supports();
}

커스텀 함수를 지원하는 최신 브라우저에서는 display 값이 --supports() 함수의 결과인 none으로 설정되어 경고 배너가 숨겨집니다. 반면, 커스텀 함수를 지원하지 않는 구형 브라우저에서는 display: --supports()라는 구문 자체가 유효하지 않은(invalid) 것으로 간주되어 무시되겠죠. 따라서 display: none이 적용되지 않아 경고 배너가 화면에 노출되게 됩니다.

💡 강사의 실무 팁!
이 테크닉은 아주 영리한 해킹입니다! 보통 @supports 규칙을 많이 쓰지만, 이렇게 함수 자체를 이용해 지원 여부를 가려내는 방식은 코드를 간결하게 만들 수 있습니다.


함수 매개변수 지정하기 (Specifying function parameters)

CSS 함수의 매개변수(parameters)는 함수 이름 뒤의 괄호 안에 쉼표(,)로 구분된 커스텀 속성 형태로 지정합니다. 예를 들면 다음과 같습니다:

@function --transparent(--color, --alpha) {
  result: oklch(from var(--color) l c h / var(--alpha));
}

이 함수는 --transparent라는 이름을 가졌고 --color--alpha라는 두 개의 커스텀 속성을 매개변수로 받습니다. 이 매개변수들은 함수 본문 안에서 지역 변수(local variable)처럼 사용할 수 있습니다. 함수 본문 안에는 result 설명자가 있는데, CSS 상대 색상 구문 (CSS relative color syntax)을 사용하여 입력받은 --color 값을 oklch() 색상으로 변환하면서, 동시에 알파(투명도) 채널 값은 입력받은 --alpha 값으로 바꿔서 적용해 줍니다.

이제 기존 색상에 반투명한 버전을 만들고 싶은 곳이라면 어디서든 이 함수를 호출할 수 있습니다.

예를 들어:

section {
  --base-color: #faa6ff;
  background-color: --transparent(var(--base-color), 0.8);
}

데이터 타입 지정하기 (Specifying data types)

함수의 매개변수와 반환값(return value)에 대해 허용되는 데이터 타입(permitted data types)을 지정하는 것이 가능합니다. 만약 타입을 지정하지 않으면 함수는 어떤 타입의 값이든 가리지 않고 다 받아들일 것입니다.

앞서 만든 함수에 데이터 타입을 명시해 보도록 수정해 보겠습니다:

@function --transparent(--color type(<color>), --alpha type(<number>)) returns
  type(<color>) {
  result: oklch(from var(--color) l c h / var(--alpha));
}

각 매개변수의 데이터 타입은 매개변수 이름 뒤에 명시하며, result의 데이터 타입은 여는 중괄호({) 직전에 returns라는 키워드와 함께 명시합니다. 데이터 타입을 지정할 때는 type() 함수를 사용합니다.

참고로, 오직 '하나의' 데이터 타입만 지정하는 경우라면 type() 구문을 생략하고 단축형으로 타입 이름만 적어도 무방합니다:

@function --transparent(--color <color>, --alpha <number>) returns <color> {
  result: oklch(from var(--color) l c h / var(--alpha));
}

이제 이 함수는 입력된 인자가 각각 <color><number>일 때만 유효한 값을 만들어 내며, 결과값 역시 반드시 <color> 타입이어야 합니다.

만약 조건에 맞지 않는 인자를 넣으면 어떻게 될까요? 예를 들어 아래 코드를 보세요:

section {
  --base-color: #faa6ff;
  background-color: --transparent(var(--base-color), 50%);
}

위의 경우, 함수에 전달된 두 번째 인자가 <number>가 아니라 <percentage> 타입(50%)이기 때문에 이 값은 계산되는 시점(computed-value time)에 유효하지 않은(invalid) 것으로 처리됩니다. 그 결과 background-color는 결국 기본값인 transparent로 세팅되어 버립니다.

💡 강사의 기술 면접 꿀팁!
"자바스크립트나 타입스크립트처럼 CSS에서도 타입 검사가 가능한가요?"라는 질문을 받을 때, @property 규칙이나 새로 나온 @function의 타입 지정 기능을 언급해 보세요. 프론트엔드 생태계의 트렌드를 꿰뚫고 있다는 인상을 줄 수 있습니다.

여러 개의 허용 타입 지정하기 (Specifying multiple permitted types)

수직선(|) 기호를 구분자로 사용하여 허용되는 데이터 타입을 여러 개 지정할 수도 있습니다. 예를 들면 다음과 같습니다:

@function --transparent(--color <color>, --alpha type(<number> | <percentage>))
  returns <color> {
  result: oklch(from var(--color) l c h / var(--alpha));
}

이렇게 여러 개의 타입을 명시해야 할 때는 반드시 type() 구문을 전체적으로 써주어야 합니다 (단축형 사용 불가).

이렇게 수정하고 나면, 아까는 오류가 났던 --transparent(var(--base-color), 50%) 형태의 함수 호출도 정상적으로 유효하게 동작하게 됩니다.


기본값 지정하기 (Specifying default values)

매개변수의 정의 끝에 콜론(:)을 붙여서 기본값(default values)을 지정해 줄 수도 있습니다. 예를 들면:

@function --transparent(--color <color>, --alpha <number>: 0.8) returns <color> {
  result: oklch(from var(--color) l c h / var(--alpha));
}

이제 --alpha 매개변수의 기본값은 0.8이 되었습니다. 만약 이 0.8이라는 기본값을 그대로 사용하고 싶다면, 함수를 호출할 때 두 번째 인자를 아예 생략해도 됩니다:

section {
  --base-color: #faa6ff;
  background-color: --transparent(var(--base-color));
}

💡 노트 (Note):
매개변수 정의에 기본값이 설정되어 있는데, 정작 함수를 호출할 때 타입에 맞지 않는 유효하지 않은 값을 전달했다면? 브라우저는 그 유효하지 않은 값을 가볍게 무시하고 대신 설정해둔 기본값을 사용하게 됩니다. 이는 에러를 방지하는 아주 좋은 안전장치가 됩니다.

색상 조정 함수 예제 (Color adjust functions example)

저희가 만든 color-adjust-functions 예제(소스 코드 보기)에서 --transparent() 함수가 실제로 작동하는 모습을 보실 수 있습니다.

이 예제에는 --transparent()와 비슷하게 동작하지만, 색상을 더 밝게 하거나 어둡게 만든 버전을 반환하는 --lighter()--darker()라는 함수도 포함되어 있습니다:

@function --transparent(--color <color>, --alpha <number>: 0.8) returns <color> {
  result: oklch(from var(--color) l c h / var(--alpha));
}

@function --lighter(--color <color>, --lightness-adjust <number>: 0.2) returns
  <color> {
  result: oklch(from var(--color) calc(l + var(--lightness-adjust)) c h);
}

@function --darker(--color <color>, --lightness-adjust <number>: 0.2) returns
  <color> {
  result: oklch(from var(--color) calc(l - var(--lightness-adjust)) c h);
}

이런 함수 라이브러리를 만들어 두면, 단 하나의 기본 색상을 바탕으로 전체 색상 테마(Color schemes)를 정의할 때 정말 유용하게 써먹을 수 있습니다:

:root {
  --base-color: #faa6ff;
}

section {
  background-color: --transparent(var(--base-color));
  border: 3px solid --lighter(var(--base-color), 0.1);
  color: --darker(var(--base-color), 0.55);
}

복잡한 로직 포함하기 (Including complex logic)

함수 내부에 @media @규칙이나 if() 함수 같은 구조를 사용해서 훨씬 더 복잡한 논리를 담아낼 수 있습니다.

responsive-narrow-wide 예제(소스 코드 보기)에는 --narrow-wide()라는 함수가 있습니다. 이 함수는 어떤 속성에 대해서든 두 가지 값의 선택지를 제공합니다. 뷰포트(화면) 너비가 특정 중단점(breakpoint)보다 작을 때는 이 값을, 클 때는 저 값을 세팅해 주는 역할을 하죠.

--narrow-wide() 함수는 --narrow--wide라는 두 개의 매개변수를 받습니다. 기본적으로 반환되는 result--wide 값입니다. 하지만 뷰포트 너비가 700px 미만이라면, @media 규칙이 작동하여 result--narrow 값으로 덮어써져서 반환됩니다. (앞서 배웠듯이 CSS 함수는 가장 마지막에 정의된 result를 반환하기 때문이죠!)

@function --narrow-wide(--narrow, --wide) {
  result: var(--wide);
  @media (width < 700px) {
    result: var(--narrow);
  }
}

이제 이 함수를 사용하면 다양한 맥락에서 값들을 아주 손쉽게 반응형으로 만들어 줄 수 있습니다:

body {
  display: grid;
  /* 화면이 넓으면 3단, 좁으면 1단 그리드 */
  grid-template-columns: repeat(--narrow-wide(1, 3), 1fr);
  /* 화면이 넓으면 20px 갭, 좁으면 0 갭 */
  gap: --narrow-wide(0, 20px);
  padding: 0 20px;
}

h2 {
  /* 화면이 넓으면 2rem, 좁으면 2.5rem */
  font-size: --narrow-wide(2.5rem, 2rem);
}

p {
  font-size: --narrow-wide(1.4rem, 1rem);
  line-height: 1.5;
}

💡 강사의 실무 포트폴리오 팁!
이 코드를 보세요. 미디어 쿼리를 매번 따로 작성하느라 CSS 파일이 길어지는 고통에서 완전히 벗어날 수 있습니다! 리액트(React)에서 Custom Hook을 만들어서 재사용성을 높이는 것처럼, CSS에서도 나만의 Custom Function을 만들어 코드를 극한으로 압축하고 가독성을 높일 수 있습니다.

`if()` 함수 사용하기 (Using an `if()` function)

위에서 만든 --narrow-wide() 함수는 @media 규칙 대신 CSS의 내장 if() 함수를 사용해서 다음과 같이 더 간결하게 다시 작성할 수도 있습니다:

@function --narrow-wide(--narrow, --wide) {
  result: if(media(width < 700px): var(--narrow) ; else: var(--wide));
}

복잡한 문법을 한 번만 쓰고 재사용하기 (Writing complex syntax once, then reusing it)

CSS 함수의 가장 핵심적인 사용 사례는 복잡한 문법을 띠는 덩어리를 한 번만 정의해 두고, 아주 간단한 함수 호출만으로 여러 번 재사용하는 것입니다.

저희가 준비한 gradient-function 예제(소스 코드 보기)가 이를 잘 보여줍니다. 이 예제에는 --shippo-pattern()이라는 함수가 있는데, 길이나 색상 인자를 받아서 여러 겹의 radial-gradient()로 이루어진 아주 복잡하고 어지러운 background 값을 한 방에 계산해서 반환해 줍니다:

@function --shippo-pattern(--size <length>, --tint <color>) {
  result:
    radial-gradient(closest-side, transparent 98%, rgb(0 0 0 / 0.3) 99%) 0 0 /
      var(--size) var(--size),
    radial-gradient(closest-side, transparent 98%, rgb(0 0 0 / 0.3) 99%)
      calc(var(--size) / 2) calc(var(--size) / 2) / var(--size) var(--size)
      var(--tint);
}

이 무시무시한 패턴을 함수로 한 번 정의해두고 나면, 색조(tint)나 동그라미의 크기를 요리조리 바꿔가면서 다양한 배경을 아주 쉽게 찍어낼 수 있습니다:

#one {
  background: --shippo-pattern(100px, #ddeeff);
}

#two {
  background: --shippo-pattern(3.5rem, lime);
}

#three {
  background: --shippo-pattern(10vw, purple);
}

같이 보기 (See also)


MDN 개선에 참여하기 (Help improve MDN)

이 페이지가 도움이 되셨나요?

기여하는 방법 알아보기 (Learn how to contribute)

이 페이지는 MDN 기여자들에 의해 2025년 11월 7일에 마지막으로 수정되었습니다 (MDN contributors).

profile
프론트에_가까운_풀스택_개발자

0개의 댓글