The CSS if() function: Conditional styling will never be the same - LogRocket


CSS if() 함수: 조건부 스타일링의 혁신

주요 개요

이 문서는 CSS Working Group에서 승인된 CSS if() 함수에 대한 완전한 가이드입니다. 이 함수는 기존의 우회적인 조건부 스타일링 방법들을 대체하여 직접적이고 명시적인 조건부 로직을 스타일시트에 도입합니다.

1. 배경 정보 및 문제점

기존 CSS의 한계

CSS는 오랫동안 조건부 로직의 부족으로 비판받아왔습니다. 과거에는 다음과 같은 우회적인 방법들을 사용해야 했습니다:

  • 미디어 쿼리: 반응형 브레이크포인트용
  • 커스텀 속성 토글: calc() 및 clamp()와 함께 사용
  • 컨테이너 스타일 쿼리: 부모 기반 조건용
  • 서로 다른 특이성을 가진 여러 선택자

if() 함수의 혁신

if() 함수는 이러한 패턴들을 대체하여 특정 조건에 기반한 다양한 값을 적용하는 더 나은 선언적 방법을 제공합니다. 별도의 규칙 블록이나 복잡한 계산 대신, 속성 선언 내에서 인라인으로 조건부 로직을 표현할 수 있습니다.

2. 기존 방식 vs if() 함수 비교

속성 기존 CSS (@media 블록) if() 함수
구문 @media (condition) { .selector { property: value; } } property: if(condition: value; else: fallback);
코드 조직 분산됨 - 로직이 여러 @media 블록에 흩어짐 중앙화됨 - 모든 조건부 로직이 한 곳에
유지보수성 변경시 여러 규칙 블록 업데이트 필요 속성당 단일 변경 지점
학습 곡선 개발자에게 친숙한 패턴 새로운 구문이지만 조건부 패턴 학습 필요
브라우저 지원 범용 - 모던 브라우저에서 지원 Chrome, Edge, Opera, WebView Android에서 지원

3. if() 함수 구문 이해

기본 구조

기본 구문은 다음 패턴을 따릅니다:

property: if(condition-1: value-1; condition-2: value-2; else: fallback);

중요한 구문 세부사항

  • if 다음에 즉시 여는 괄호가 오며 사이에 공백 없음
  • 각 조건-값 쌍은 쉼표가 아닌 세미콜론으로 구분
  • else 절은 조건이 맞지 않을 때의 대체값 역할

기본 예제

/* 입력 방법에 따른 다른 버튼 크기 적용 */
button {
  width: if(media(any-pointer: fine): 30px; else: 44px);
}

세 가지 쿼리 타입

style() 쿼리

현재 요소의 커스텀 속성 값이나 계산된 스타일을 확인:

.card {
  background: if(style(--variant): var(--primary); else: white);
}

media() 쿼리

기존 미디어 쿼리와 같지만 인라인으로 사용:

.sidebar {
  width: if(media(min-width: 768px): 300px; else: 100%);
}

supports() 쿼리

CSS 기능 지원을 테스트:

.grid-container {
  display: if(supports(display: grid): grid; else: flex);
}

구문 요구사항

올바른 구문

/* 정확함 */
width: if(media(min-width: 768px): 300px; else: 100px);

/* 정확함 - 세미콜론으로 구분 */
color: if(
  style(--theme: dark): white;
  style(--theme: light): black;
  else: gray
);

잘못된 구문

/* 틀림 - if와 괄호 사이에 공백 */
width: if (media(min-width: 768px): 300px; else: 100px);

/* 틀림 - 쉼표로 구분 */
color: if(
  style(--theme: dark): white,
  style(--theme: light): black,
  else: gray
);

4. 기존 CSS 접근법과의 상세 비교

if() vs 미디어 쿼리

기존 미디어 쿼리 접근법

/* 기존 미디어 쿼리 방식 */
button {
  width: 30px;
  height: 30px;
  padding: 8px;
}

@media (any-pointer: coarse) {
  button {
    width: 44px;
    height: 44px;
    padding: 12px;
  }
}

if() 함수 접근법

/* if() 방식 - 모든 로직이 한 곳에 */
button {
  width: if(media(any-pointer: coarse): 44px; else: 30px);
  height: if(media(any-pointer: coarse): 44px; else: 30px);
  padding: if(media(any-pointer: coarse): 12px; else: 8px);
}

if() 접근법은 각 속성의 반응형 로직을 인라인으로 유지하여 버튼이 다양한 조건에 어떻게 적응하는지 즉시 명확하게 보여줍니다.

if() vs 컨테이너 스타일 쿼리

컨테이너 스타일 쿼리 방식

/* 컨테이너 스타일 쿼리 방식 */
.theme-container {
  --theme: dark;
}

@container style(--theme: dark) {
  .card {
    background: #333;
    color: white;
  }
}

if() 함수 방식

/* if() 방식 - 자체 포함된 로직 */
.card {
  background: if(
    style(--theme: dark): #333;
    style(--theme: light): white;
    else: #f5f5f5
  );
  color: if(
    style(--theme: dark): white;
    style(--theme: light): black;
    else: #666
  );
}

if() 함수는 카드의 테마 동작을 자체 포함적으로 만듭니다.

if() vs 커스텀 속성 토글

커스텀 속성 토글 방식

/* 커스텀 속성 토글 방식 */
.button {
  --is-primary: ; /* 기본적으로 정의되지 않음 */
  --primary-bg: var(--is-primary, transparent);
  --primary-color: var(--is-primary, currentColor);

  background: var(--primary-bg, var(--blue-500));
  color: var(--primary-color, white);
}

.button--primary {
  --is-primary: initial; /* 정의됨, 토글 트리거 */
}

if() 함수 방식

/* if() 방식 - 명확한 조건부 로직 */
.button {
  background: if(
    style(--variant: primary): var(--blue-500);
    else: transparent
  );
  color: if(
    style(--variant: primary): white;
    else: currentColor
  );
}

if() 함수는 의도를 명확하게 표현합니다: “변형이 primary라면 이 색상들을 사용하고, 그렇지 않으면 기본값을 사용하라”

5. 실용적인 구현 패턴

디자인 시스템 테마

다중 테마 지원

/* if()를 사용한 테마 인식 컴포넌트 */
.card {
  background: if(
    style(--theme: ocean): #f0f8ff;
    style(--theme: forest): #f5fffa;
    else: #ffffff
  );
  border: 2px solid if(
    style(--theme: ocean): #0077be;
    style(--theme: forest): #228b22;
    else: #333333
  );
  color: if(
    style(--theme: ocean): #0077be;
    style(--theme: forest): #228b22;
    else: #333333
  );
}

.button {
  background: if(
    style(--theme: ocean): #0077be;
    style(--theme: forest): #228b22;
    else: #333333
  );
  color: if(
    style(--theme: ocean): #f0f8ff;
    style(--theme: forest): #f5fffa;
    else: #ffffff
  );
  border: none;
  padding: 12px 24px;
  border-radius: 6px;
}

테마 활성화

.ocean-theme { --theme: ocean; }
.forest-theme { --theme: forest; }

이 접근법은 강력한 캐스케이드 효과를 생성합니다. 컨테이너에 --theme: ocean을 설정하면 모든 하위 요소가 CSS 커스텀 속성 시스템을 통해 자동으로 ocean 테마 색상을 받습니다.

반응형 컴포넌트

다중 컨텍스트 적응 내비게이션

/* 다중 컨텍스트에 반응하는 적응형 내비게이션 */
.nav-item {
  /* 기본 크기가 상호작용 방법에 적응 */
  min-height: if(
    media(any-pointer: coarse): 44px;    /* 터치 친화적 최소값 */
    media(any-pointer: fine): 32px;      /* 마우스 최적화 */
    else: 36px                           /* 안전한 대체값 */
  );

  /* 패딩이 사용 가능한 공간과 입력 방법에 따라 조정 */
  padding: if(
    media(any-pointer: coarse) and media(min-width: 768px): 16px 24px;
    media(any-pointer: coarse): 12px 16px;
    media(min-width: 768px): 8px 16px;
    else: 6px 12px
  );

  /* 폰트 크기가 화면 공간과 가독성 요구사항 모두 고려 */
  font-size: if(
    media(min-width: 1024px): 16px;
    media(min-width: 768px): 15px;
    media(any-pointer: coarse): 16px;    /* 터치 가독성을 위해 더 큼 */
    else: 14px
  );
}

/* 사용자 선호도를 존중하는 호버 상태 */
.nav-item:hover {
  background: if(
    media(hover: hover): var(--primary-light);  /* 호버 가능한 디바이스에서만 */
    else: transparent
  );
}

이 내비게이션 컴포넌트는 조건부 스타일링을 통한 점진적 향상을 보여줍니다.

상태 기반 스타일링

검증 상태 기반 폼 입력

/* 포괄적인 시각적 피드백을 제공하는 상태 인식 폼 입력 */
.form-input {
  /* 기본 스타일링은 일관성 유지 */
  padding: 12px 16px;
  border-radius: 6px;
  font-size: 16px;
  transition: all 0.2s ease;

  /* 테두리 색상이 검증 상태를 반영 */
  border: 2px solid if(
    style(--state: error): #dc3545;
    style(--state: success): #28a745;
    style(--state: warning): #ffc107;
    style(--state: focus): #007bff;
    else: #dee2e6
  );

  /* 배경색이 미묘한 상태 표시 제공 */
  background: if(
    style(--state: error): #fff5f5;
    style(--state: success): #f0fff4;
    style(--state: warning): #fffbf0;
    else: #ffffff
  );

  /* 아이콘 색상이 상태와 조화 */
  --icon-color: if(
    style(--state: error): #dc3545;
    style(--state: success): #28a745;
    style(--state: warning): #ffc107;
    else: #6c757d
  );
}

/* 조건부로 나타나는 상태별 아이콘 */
.form-input::after {
  content: if(
    style(--state: error): "❌";
    style(--state: success): "✅";
    style(--state: warning): "⚠️";
    else: ""
  );
  color: var(--icon-color);
  margin-left: 8px;
}

/* 애니메이션 스타일이 포함된 로딩 상태 */
.form-input {
  /* 로딩 상태용 애니메이션 테두리 */
  border-style: if(
    style(--state: loading): dashed;
    else: solid
  );

  /* 로딩 중 투명도 감소 */
  opacity: if(
    style(--state: loading): 0.7;
    else: 1
  );
}

JavaScript 상태 관리

// JavaScript 상태 관리
const input = document.querySelector('.form-input');

// 오류 상태 설정
input.style.setProperty('--state', 'error');

// 성공 상태 설정
input.style.setProperty('--state', 'success');

// 상태 지우기
input.style.removeProperty('--state');

이 패턴은 상태 로직(JavaScript가 관리)과 시각적 표현(CSS가 처리) 사이의 명확한 분리를 생성합니다.

6. 고급 기술

if() 함수 중첩

복잡한 다층 로직

/* 포괄적인 입력 적응을 위한 고급 중첩 if() */
.form-input {
  /* 여러 요소를 고려하는 복잡한 크기 로직 */
  font-size: if(
    /* 먼저 사용자가 큰 텍스트를 선호하는지 확인 */
    media(prefers-reduced-motion: no-preference) and style(--size: large): if(
      /* 큰 선호도 내에서 디바이스 타입 고려 */
      media(any-pointer: coarse): 20px;    /* 큰 터치 친화적 */
      else: 18px                           /* 크지만 터치용 아님 */
    );

    /* 큰 선호도가 아니라면 접근성 요구사항 확인 */
    media(prefers-reduced-motion: reduce): if(
      /* 모션 감소 사용자는 다른 크기가 필요할 수 있음 */
      media(any-pointer: coarse): 18px;    /* 보수적인 터치 크기 */
      else: 16px                           /* 표준 데스크톱 */
    );

    /* 기본 케이스는 표준 선호도 처리 */
    else: if(
      media(any-pointer: coarse): 17px;    /* 표준 터치 크기 */
      else: 15px                           /* 표준 데스크톱 */
    )
  );
}

중첩된 구조는 결정의 계층을 생성합니다. 외부 if() 함수는 주요 조건을 처리하고, 내부 if() 함수는 보조 조건을 처리합니다.

calc()와의 통합

등록된 커스텀 속성

/* 계산 참여를 위한 커스텀 속성 등록 */
@property --nav-items {
  syntax: '<number>';
  initial-value: 4;
  inherits: true;
}

@property --available-width {
  syntax: '<length>';
  initial-value: 100vw;
  inherits: false;
}

동적 계산이 포함된 고급 내비게이션

/* 계산된 간격이 있는 고급 내비게이션 */
.nav-container {
  /* 콘텐츠와 화면 크기에 기반한 최적 간격 계산 */
  --spacing-multiplier: if(
    media(min-width: 1200px): 1.5;        /* 큰 화면에서 여유로운 간격 */
    media(min-width: 768px): 1.2;         /* 중간 화면에서 적당한 간격 */
    media(any-pointer: coarse): 1.3;      /* 추가 터치 친화적 간격 */
    else: 1.0                             /* 컴팩트 간격 대체값 */
  );

  /* 조건에 반응하는 동적 갭 계산 */
  gap: calc(
    /* 기본 간격 단위 */
    16px * 
    /* 조건부 승수 */
    var(--spacing-multiplier) *
    /* 사용 가능한 공간에 기반한 반응형 요소 */
    if(
      media(min-width: 1200px): 1;
      media(min-width: 768px): 0.8;
      else: 0.6
    )
  );
}

.nav-item {
  /* 항목 수와 조건에 기반한 유연한 너비 계산 */
  flex-basis: calc(
    /* 사용 가능한 너비를 항목 수로 나눔 */
    (var(--available-width) / var(--nav-items)) *
    /* 화면 크기와 상호작용 방법에 기반한 조정 요소 */
    if(
      media(min-width: 1200px) and media(any-pointer: fine): 0.9;  /* 큰 데스크톱에서 더 작게 */
      media(min-width: 768px): 0.95;                               /* 태블릿에서 약간 더 작게 */
      media(any-pointer: coarse): 1.0;                             /* 터치에서 전체 너비 */
      else: 0.85                                                   /* 작은 화면에서 컴팩트 */
    )
  );
}

이 패턴은 조건부 로직이 수학적 계산을 어떻게 구동할 수 있는지 보여줍니다.

부분값 조건부

복잡한 속성값 내 조건부 수정

/* 조건부 단축 속성 수정이 있는 고급 카드 */
.card {
  /* 조건부 너비와 스타일, 일관된 색상을 가진 테두리 */
  border: if(
    style(--emphasis: high): 4px solid #dc2626;    /* 강조를 위한 두꺼운 빨간 테두리 */
    style(--emphasis: medium): 3px solid #f59e0b;  /* 중간 주황 테두리 */
    style(--emphasis: low): 2px dashed #10b981;    /* 점선 녹색 테두리 */
    else: 1px solid #6b7280                        /* 표준 회색 테두리 */
  );

  /* 조건부 블러와 확산이 있는 박스 그림자 */
  box-shadow: 
    0 
    if(style(--elevation: high): 12px; else: 4px)    /* 조건부 오프셋 */
    if(style(--elevation: high): 32px; else: 12px)   /* 조건부 블러 */
    if(style(--elevation: high): 8px; else: 2px)     /* 조건부 확산 */
    if(style(--elevation: high): rgba(220, 38, 38, 0.3); else: rgba(0, 0, 0, 0.1));

  /* 조건부 수평 및 수직 값이 있는 패딩 */
  padding: 
    if(style(--content: dense): 8px; else: 24px)     /* 조건부 수직 */
    if(style(--content: dense): 12px; else: 32px);   /* 조건부 수평 */

  /* 조건부 그라디언트 방향이 있는 배경 */
  background: linear-gradient(
    if(style(--direction: vertical): to bottom; else: to right),
    if(style(--gradient: subtle): #fef3c7; else: #ffffff),
    if(style(--gradient: subtle): #dbeafe; else: #ffffff)
  );
}

/* 조건부 로직을 상속하고 확장하는 카드 내 버튼 */
.card .button {
  /* 카드와 버튼 상태를 모두 고려하는 변형 */
  transform: 
    scale(if(style(--state: active): 1.05; else: 1.0))
    translateY(if(
      style(--elevation: high): -2px;     /* 높은 고도 카드에서 더 많이 올림 */
      style(--state: active): -1px;      /* 활성 상태에서 약간 올림 */
      else: 0px
    ));
}

이 기술은 if() 함수가 복잡한 속성 값 내에 어떻게 삽입되어 매우 미묘한 조건부 스타일링을 생성할 수 있는지 보여줍니다.

7. 실용적인 팁 및 주의사항

언제 if() 함수를 선택할 것인가

if() 함수 사용 권장 상황

  • 속성 수준의 조건부 동작이 필요한 경우
  • 테마 시스템, 컴포넌트 상태, 또는 로직을 인라인으로 유지하는 것이 유익한 반응형 속성
  • 자체 포함된 컴포넌트 생성시

기존 방법 사용 권장 상황

  • 포괄적인 레이아웃 변경: 미디어 쿼리 사용
  • 여러 자식 요소를 조정해야 하는 경우: 컨테이너 쿼리 사용

핵심 장점

  • 우회적인 방법과 별도 규칙 블록에서 인라인, 명시적 로직으로 이동
  • 더 지능적이고 자체 포함된 컴포넌트 생성 가능
  • 외부 조정 없이 컨텍스트에 적응할 수 있는 시스템

브라우저 지원 현황

현재 Chrome, Edge, Opera, WebView Android에서 CSS if() 함수가 지원됩니다.

8. 결론

CSS if() 함수는 조건부 스타일링을 수행하는 방식에서 차세대 접근법을 나타냅니다. 우회적인 방법과 별도의 규칙 블록에서 인라인, 명시적 로직으로의 전환을 의미합니다.

실제 적용 가치

if() 함수의 진정한 적용 가치는 외부 조정 없이 컨텍스트에 적응할 수 있는 더 지능적이고 자체 포함된 컴포넌트를 생성하는 데 있습니다. 중첩 조건, calc()와의 통합, 부분값 조건부의 예제들은 CSS가 정적 스타일링에서 사용자 요구, 디바이스 기능, 애플리케이션 상태에 사려깊게 반응하는 동적이고 컨텍스트 인식 시스템으로 어떻게 진화할 수 있는지를 보여줍니다.

미래 전망

이 함수는 CSS를 더욱 강력하고 표현력 있는 언어로 만들어 개발자들이 더 정교하고 적응적인 사용자 인터페이스를 구축할 수 있게 합니다.

1개의 좋아요