들어가며: 왜 CSS로 계산을 해야 할까?
웹 개발을 하다 보면 할인율과 원가를 이용해 할인가를 계산해서 화면에 표시해야 하는 경우가 많습니다. 보통은 JavaScript로 계산해서 DOM에 넣는 것이 일반적이죠. 하지만 계산 결과를 표시하는 용도라면, 굳이 JavaScript를 사용하지 않고도 CSS만으로 충분히 해결할 수 있습니다.
이 글에서 소개하는 방식은 아직 모든 브라우저에서 지원되는 것은 아니지만, attr() 함수의 발전과 CSS 수학 함수(mod(), round())의 등장으로 가능해진 미래 지향적인 기법입니다. 이 코드를 통해 CSS가 단순한 스타일링을 넘어 '계산'까지 할 수 있다는 가능성을 확인해 보시길 바랍니다.
핵심 아이디어: data-* 속성과 CSS 변수 조합
이 기법의 핵심은 아래와 같습니다.
- HTML 요소에
data-price와data-discount속성으로 데이터를 저장합니다. - CSS의
attr()함수를 사용해 이 속성 값을number타입으로 읽어옵니다. - CSS 수학 함수(
calc(),mod(),round())로 할인가를 계산합니다. - 계산된 값을
counter()와content속성으로 화면에 표시합니다.
기본 마크업 구조
<div class="ott">
<input type="checkbox" class="is-ott-discounted" id="discount-toggle">
<label for="discount-toggle">학생 할인 적용</label>
<div class="ott-price"
data-price="7.99"
data-discount="20">
$7.99
</div>
</div>
data-price: 원래 가격data-discount: 할인율 (%)
본격적인 CSS 계산 로직
이제 실제 CSS 코드를 살펴보겠습니다. 먼저 할인 토글이 체크되었을 때 원래 가격에 취소선을 긋고, 새로운 가격을 계산해 표시합니다.
/* 할인 토글이 체크되었을 때 */
.ott:has(.is-ott-discounted:checked) {
.ott-price {
/* 원래 가격에 취소선 */
text-decoration: line-through;
/*
할인가 계산:
원래 가격 * (1 - 할인율/100)
*/
--n: calc(attr(data-price number) * (1 - attr(data-discount number) / 100));
/* 계산된 가격 표시 */
&::after {
display: inline-block;
/*
--n 변수를 두 개의 카운터로 분리:
'a'는 달러(정수 부분), 'b'는 센트(소수점 이하)
*/
counter-set:
a calc(round(down, var(--n)))
b calc((mod(var(--n), 1)) * 100);
/* 출력: 공백, $, 정수부, 소수점, 소수부 */
content: "\2000\2000quot; counter(a) "." counter(b, decimal-leading-zero);
}
}
}
코드 상세 설명
attr(data-price number):data-price속성 값을 숫자(number) 타입으로 읽어옵니다. 기존attr()함수가content속성에서만 제한적으로 사용되었던 것에 비해, 이제는 모든 CSS 속성에서 사용 가능하며 타입 지정도 가능해졌습니다.calc(): 읽어온 값으로 사칙연산을 수행합니다.7.99 * (1 - 20/100) = 6.392가 됩니다.round(down, var(--n)):--n값을 내림하여 정수 부분(달러)만 추출합니다. 즉,6을 얻습니다.mod(var(--n), 1):--n값을 1로 나눈 나머지, 즉 소수점 이하 부분(0.392)을 얻습니다. 여기에 100을 곱해 센트(39.2)로 변환합니다.counter-set및counter(): 계산된 숫자를 CSS 카운터에 설정하고,content속성으로 문자열을 조합해 화면에 출력합니다.
실무 적용 시 주의사항 및 한계
이 기법은 매우 흥미롭지만, 몇 가지 주의할 점이 있습니다. 😅
- 브라우저 지원: 이 글에서 사용한
attr()함수의 타입 지정 문법은 아직 표준(Baseline)으로 채택되지 않았습니다. 최신 Chrome이나 Edge에서는 동작할 수 있지만, Firefox나 Safari에서는 지원되지 않을 가능성이 높아요. 실제 프로덕션 환경에 적용하기 전에 반드시 타겟 브라우저 지원 여부를 확인해야 합니다. - 복잡한 로직 지양: CSS는 본래 레이아웃과 스타일을 위한 언어입니다. 매우 복잡한 계산이나 조건 분기가 필요한 경우에는 JavaScript를 사용하는 것이 유지보수에 더 유리할 수 있습니다. 이 기법은 간단한 계산을 스크립트 없이 처리하고자 할 때 빛을 발합니다.
- 접근성:
content속성으로 생성된 텍스트는 스크린 리더와 같은 보조 기기가 읽지 못할 수 있습니다. 중요한 가격 정보는 HTML에 실제 텍스트로 포함시키는 것이 좋습니다.
결론: CSS의 미래를 엿보다
이번 예제는 attr() 함수의 진화와 CSS 수학 함수의 가능성을 보여주는 좋은 사례입니다. 아직 실무에서 바로 사용하기에는 이르지만, 브라우저 지원이 확대된다면 간단한 계산 로직을 JavaScript 없이 처리하는 데 유용하게 사용될 수 있습니다.
- 다음 단계 학습 방향:
@property를 사용한 커스텀 속성 등록,container query와의 결합, 그리고 CSS Houdini API에 대해 학습해 보세요. CSS가 단순 스타일링을 넘어 더 많은 역할을 할 수 있다는 점을 깨닫게 될 것입니다.
함께 보면 좋은 글:
- 메신저 속 악성 링크, 프라이버시는 지키고 위험은 알린다 — Meta의 Advanced Browsing Protection 완전 해부
- Ray on TPU, GKE로 10분 만에 시작하는 실전 가이드 (Feat. 슬라이스 배치)

핵심 코드 예제
위에서 설명한 전체 코드를 한곳에 모았습니다. 아래 코드를 복사해 바로 사용해 볼 수 있습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS 할인가 계산 예제</title>
<style>
.ott {
font-family: sans-serif;
}
.ott-price {
font-size: 2rem;
font-weight: bold;
}
/* 할인 토글이 체크되었을 때 */
.ott:has(.is-ott-discounted:checked) {
.ott-price {
/* 원래 가격에 취소선 */
text-decoration: line-through;
/*
할인가 계산:
원래 가격 * (1 - 할인율/100)
*/
--n: calc(attr(data-price number) * (1 - attr(data-discount number) / 100));
/* 계산된 가격 표시 */
&::after {
display: inline-block;
/*
--n 변수를 두 개의 카운터로 분리:
'a'는 달러(정수 부분), 'b'는 센트(소수점 이하)
*/
counter-set:
a calc(round(down, var(--n)))
b calc((mod(var(--n), 1)) * 100);
/* 출력: 공백, $, 정수부, 소수점, 소수부 */
content: "\2000\2000quot; counter(a) "." counter(b, decimal-leading-zero);
}
}
}
</style>
</head>
<body>
<div class="ott">
<input type="checkbox" class="is-ott-discounted" id="discount-toggle">
<label for="discount-toggle">학생 할인 적용</label>
<div class="ott-price"
data-price="7.99"
data-discount="20">
$7.99
</div>
</div>
</body>
</html>
이 코드는 할인 토글을 체크하면 $7.99가 취소선으로 표시되고, 뒤에 할인된 가격 $6.39가 자동으로 붙는 것을 확인할 수 있습니다.
![]()
심화 팁: 가격 반올림 처리
만약 할인가가 소수점 셋째 자리까지 나오는 경우, 가격을 반올림해서 표시하고 싶을 수 있습니다. 이때는 round() 함수를 활용할 수 있습니다.
/* 가격을 가장 가까운 정수로 반올림 */
.ott-price::after {
counter-set: price calc(round(nearest, var(--n)));
content: "quot; counter(price);
}
round(nearest, var(--n))는 --n 값을 가장 가까운 정수로 반올림합니다. 소수점 이하를 버리고 싶다면 round(down, ...)을, 올리고 싶다면 round(up, ...)을 사용하면 됩니다.
브라우저 지원 현황 및 폴리필 전략
현재(2024년 기준) 이 글에서 사용한 attr() 함수의 타입 지정 문법은 주요 브라우저에서 완전히 지원되지 않습니다. 이 때문에 실무에서 바로 사용하기는 어렵습니다. 하지만 다음과 같은 전략을 고려할 수 있습니다.
- 점진적 향상 (Progressive Enhancement): 기본 가격은 HTML에 그대로 두고, CSS 계산이 지원되는 브라우저에서만 할인가가 표시되도록
@supports규칙을 사용할 수 있습니다. - JavaScript 폴리필: 아직 표준이 아니므로, JavaScript로 유사한 기능을 구현해 두는 것도 방법입니다. 하지만 이 글의 목적은 CSS의 가능성을 탐구하는 것이므로, 브라우저 지원이 확대될 때까지 기다리는 것도 좋은 선택입니다.

결론: 실무 적용을 위한 조언
오늘 살펴본 CSS 계산 기법은 아직 실험적이지만, CSS가 단순 스타일링을 넘어 로직을 처리할 수 있는 방향으로 진화하고 있다는 것을 보여줍니다.
실무에서 바로 도입하기보다는, 아래와 같은 관점에서 접근해 보시길 권합니다.
- 기술 트렌드 파악: 최신 CSS 기능을 학습하고 브라우저 지원 현황을 주기적으로 확인하세요.
- 간단한 프로젝트에 적용: 개인 프로젝트나 토이 프로젝트에서 이 기법을 사용해 보고, 장단점을 몸소 체험해 보세요.
- 가독성과 유지보수성 고려: CSS로 복잡한 계산을 구현하면 코드가 난해해질 수 있습니다. 팀 프로젝트라면 팀원들과 충분한 논의가 필요합니다.
이 글이 CSS의 새로운 가능성을 발견하는 데 도움이 되었기를 바랍니다. 궁금한 점이나 다른 좋은 아이디어가 있다면 댓글로 공유해 주세요!