본문 바로가기
카테고리 없음

CSS 별점 위젯 직접 만들기 (HTML 구조, 형제 선택자, 반 별 구현)

by BOOST YOUR INFORMATION 2026. 4. 27.

CSS 별점 위젯 만들기 완벽 가이드 – JS 없이 순수 CSS로 리뷰 블로그에 별점 삽입하는 법 4단계 참조 이미지
JS 없이 순수 CSS로 리뷰 블로그에 별점 삽입하는 법 4단계

 

리뷰 글을 꾸준히 올리다 보면 한 번쯤 이런 생각이 드실 겁니다. "별점 하나 넣고 싶은데, 플러그인 쓰기엔 너무 무겁고 스타일은 또 따로 놀고." 저도 그 고민을 꽤 오래 했습니다. 결국 JavaScript 없이 CSS만으로 별점 위젯을 직접 만들어 붙였고, 그 과정에서 배운 것들을 정리해 봤습니다. 처음에는 쉬워 보였는데 실제로 해보니 생각보다 복잡한 개념들이 얽혀 있었습니다.

HTML 구조: 왜 별을 거꾸로 써야 할까요

처음 이 방법을 접했을 때 가장 당황했던 부분이 바로 HTML 마크업 구조였습니다. 별을 5번부터 1번 순서로 역순 배치한다는 게 직관과 정반대라서, 저도 처음엔 "이게 맞나?" 싶어 순서를 몇 번이나 바꿔가며 테스트했습니다. 역순이어야 한다는 걸 인지하고도 왜인지를 이해하는 데까지 시간이 걸렸습니다.

핵심은 CSS의 일반 형제 결합자(General Sibling Combinator, ~)에 있습니다. 여기서 ~ 선택자란 같은 부모를 가진 형제 요소 중 현재 요소 뒤에 오는 모든 형제를 선택하는 CSS 문법입니다. 즉, 앞에 있는 형제는 절대 잡지 못합니다(출처: MDN Web Docs).

그렇다면 왜 역순이어야 할까요? 3번 별을 클릭했을 때 1번과 2번 별도 함께 채워지려면, 선택된 요소보다 뒤에 위치한 형제를 잡는 ~ 선택자 특성상 낮은 번호의 별들이 HTML상에서 뒤쪽에 있어야 합니다. HTML에서 5→1 순서로 작성한 뒤 flex-direction: row-reverse로 시각적으로 뒤집으면, 화면에는 1→5로 정상 출력되면서 CSS 선택 논리도 함께 성립합니다. 브라우저에서 직접 확인하고 나서야 "아, 이래서 거꾸로 쓰는 거구나"가 납득됐고, 이해하고 나니 오히려 그 구조가 우아하게 느껴졌습니다. 처음엔 억지로 끼워 맞춘 것처럼 보였는데, CSS 선택자의 특성을 활용한 설계라는 걸 알고 나면 오히려 감탄이 나옵니다.

radio input과 label을 조합하는 이유도 여기에 있습니다. input에 :checked 의사 클래스(Pseudo-class)를 적용합니다. 여기서 :checked란 체크박스나 라디오 버튼이 선택된 상태를 CSS로 감지하는 선택자로, 이를 ~ 선택자와 결합하면 JavaScript 없이도 클릭 상태를 스타일에 반영할 수 있습니다(출처: MDN Web Docs). 이 조합이 작동한다는 걸 처음 봤을 때 꽤 신기했습니다. JavaScript가 없는데도 클릭 상태가 CSS에 전달된다는 게 직관적으로 잘 이해되지 않았거든요. input 요소의 상태 변화를 CSS가 감지한다는 개념이 낯설었습니다.

형제 선택자와 :checked로 별 채우기

구조를 이해하고 나면 실제 CSS 작성은 생각보다 단순합니다. 그런데 저는 여기서도 한 번 막혔습니다. 호버(hover) 상태와 체크된 상태가 동시에 적용될 때 CSS 우선순위, 즉 명시도(Specificity) 충돌이 일어났기 때문입니다. 여기서 명시도란 같은 요소에 여러 CSS 규칙이 겹칠 때 어떤 규칙을 우선 적용할지 결정하는 점수 체계입니다. 선택자가 구체적일수록 명시도 점수가 높아집니다.

문제는 마우스를 올렸다가 이미 클릭된 별 위에 커서를 얹으면 스타일이 뒤죽박죽이 되는 상황이었습니다. 해결하려면 :checked 상태의 선택자 명시도를 호버 상태보다 높게 잡거나, 호버 시에는 :checked 상태를 덮어쓰지 않도록 선택자 순서를 의도적으로 배열해야 합니다. 제 경험상 이 부분이 가장 손이 많이 가는 구간이었습니다. 선택자 조합이 길어지다 보면 실수도 잦아지고, 한 줄 바꿨더니 다른 상태가 틀어지는 경험을 몇 번 반복했습니다. 솔직히 이 부분은 처음 접하는 분한테 쉽다고 하기는 어렵습니다. 막히는 게 정상입니다.

그래도 이 방법의 장점은 분명합니다. 플러그인을 쓸 때는 블로그 전체 폰트와 컬러 팔레트 위에 완전히 다른 스타일의 위젯이 올라와 있어서 볼 때마다 어색했는데, CSS를 직접 작성하면 블로그 디자인 시스템과 완벽하게 통일이 됩니다. 제가 직접 써봤는데, 실제 리뷰 포스팅에 붙였을 때 전체 색상과 어울리는 별점 위젯이 자리 잡히는 걸 보니 플러그인으로는 절대 못 느끼는 만족감이 있었습니다. 불필요한 JavaScript 로드도 없어지니 성능 측면에서도 깔끔합니다.

반 별 구현: 정적 별점의 한계와 현실

인터랙티브 별점보다 더 까다로운 건 정적 별점, 즉 클릭 없이 고정된 점수를 표시하는 방식에서의 반 별 구현이었습니다. CSS의 ::before 의사 요소(Pseudo-element)를 활용합니다. 여기서 ::before란 특정 요소의 내용 앞에 가상의 콘텐츠를 삽입하는 CSS 문법으로, 실제 HTML을 수정하지 않고도 시각적 요소를 추가할 수 있습니다.

반 별을 표현할 때는 ::before에 width: 50%와 overflow: hidden을 조합해 별 모양의 왼쪽 절반만 채색하는 클리핑(Clipping) 방식을 씁니다. 여기서 클리핑이란 요소의 보이는 영역을 특정 범위로 잘라내는 기법입니다. 그런데 저는 여기서 position: absolute를 제대로 잡아두지 않아서 별이 엉뚱한 위치에 튀어나오는 문제를 겪었습니다. 부모 요소에 position: relative를 빠뜨린 게 원인이었고, 두세 번 수정하고 나서야 반 별이 정확하게 왼쪽 절반만 채워지는 걸 확인했습니다. 솔직히 이건 예상 밖이었습니다. 원리는 간단해 보이는데 실제 구현에서 포지션 한 줄이 전체를 흔들어 놨으니까요. 이 실수는 앞서 blockquote 가상 요소 작업에서도 똑같이 했던 실수라서, 두 번이나 같은 부분에서 걸린 게 좀 부끄럽습니다.

다만 이 클리핑 방식에는 분명한 한계가 있습니다. 50% 단위의 반 별은 표현할 수 있지만, 4.3점이나 3.7점처럼 소수점 단위 점수는 시각적으로 정확하게 재현하기 어렵습니다. 이 점에서는 보완이 필요한 방식이라고 봅니다. 정밀한 소수점 표시가 필요한 경우라면 CSS만으로는 한계가 있고, 이 부분에서만큼은 JavaScript나 SVG를 활용하는 게 더 적합합니다. 도구마다 잘 맞는 용도가 다르고, CSS만으로 모든 것을 해결하려는 것도 과욕이라고 생각합니다.

또한 Schema.org의 Review 마크업을 함께 적용하면 검색 결과에서 리치 스니펫(Rich Snippet) 형태로 별점이 노출될 수 있는데, 이 경우 Google Rich Results Test로 마크업이 올바르게 인식되는지 반드시 검증해야 합니다. 여기서 리치 스니펫이란 검색 결과 페이지에서 제목·설명 외에 별점, 가격, 리뷰 수 등 추가 정보가 시각적으로 표시되는 형태를 말합니다. 별점 위젯을 만들었는데 구조화 데이터까지 챙기지 않으면 검색 결과에서의 이점을 절반은 못 쓰는 겁니다.

접근성 측면도 짚고 넘어가야 합니다. radio input을 opacity: 0으로 숨기면 스크린 리더는 여전히 읽을 수 있습니다. 하지만 키보드만으로 별점을 선택하는 UX는 생각보다 불편합니다. 탭 키로 포커스를 이동할 때 숨겨진 input이 포커스를 받고 있는지 사용자가 시각적으로 확인하기 어렵기 때문입니다. 구조를 이해하고 나면 강력한 방법이지만, 접근성 보완 없이 그대로 쓰기엔 아쉬운 부분이 있습니다. 완성도를 높이려면 포커스 스타일을 visible하게 처리하는 작업이 추가로 필요합니다. 이 부분은 빠뜨리기 쉬운 항목인데, 처음 만들 때 함께 챙겨두는 게 나중에 다시 건드리는 것보다 훨씬 편합니다.

JavaScript 없이 CSS만으로 별점 위젯을 구현하는 건 분명 배울 게 많은 작업입니다. 플러그인이 주는 편함과 달리, 직접 만든 코드는 블로그 전체 스타일과 완벽하게 어울리고 불필요한 스크립트 로드도 없습니다. 처음 시도하신다면 인터랙티브 별점부터 시작해 구조를 익히고, 정적 반 별은 그 다음 단계로 시도해 보시길 권합니다. 접근성 보완은 포커스 스타일 visible 처리부터 작게 시작하면 됩니다. 한번에 완벽하게 만들려 하지 말고, 단계별로 쌓아가는 방식이 제 경험상 훨씬 효율적이었습니다.


참고:
MDN Web Docs – CSS :checked: https://developer.mozilla.org/ko/docs/Web/CSS/:checked
MDN Web Docs – General sibling combinator(~): https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator
Schema.org Review 마크업: https://schema.org/Review
Google Rich Results Test: https://search.google.com/test/rich-results


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 ⚡ 정보 부스터 🚀