Shadcn 라디오 버튼의 과도한 복잡성
(paulmakeswebsites.com)- 웹 브라우저에 기본 내장된 라디오 버튼이 단순한 HTML 요소임에도, Shadcn UI 라이브러리에서는 이를 여러 계층의 React 컴포넌트로 재구성함
- Shadcn의
<RadioGroup>과<RadioGroupItem>은 Radix UI의 컴포넌트를 다시 감싸며, lucide-react 아이콘과 수십 개의 Tailwind 클래스를 사용 - Radix는 접근성과 커스터마이징을 위해 ARIA 속성을 활용하지만, 실제로는 기본
<input type="radio">대신 버튼 요소를 재활용함 - 단순한 CSS로도 동일한 스타일링이 가능함에도, 이 구조는 수백 줄의 코드와 여러 종속성을 추가해 불필요한 복잡성을 초래
- 기본 HTML 요소를 재사용하지 않음으로써 성능 저하와 유지보수 부담이 커지고, 웹 개발의 단순함이 훼손됨
Shadcn 라디오 버튼 구조 분석
- Shadcn은
<RadioGroup>과<RadioGroupItem>두 컴포넌트를 통해 라디오 버튼을 구현- 각 컴포넌트는
@radix-ui/react-radio-group에서 가져온 프리미티브를 감싸며,lucide-react의 CircleIcon을 사용 - 45줄 이상의 코드와 3개의 외부 import가 포함되어 있으며, 30개 이상의 Tailwind 클래스로 스타일 지정
- 각 컴포넌트는
- 단순한 원형 표시를 위해 SVG 아이콘 라이브러리를 불러오는 구조
- CSS의
border-radius나<circle>요소로 대체 가능한 기능임
- CSS의
Radix UI의 역할
- Shadcn이 사용하는 Radix는 접근성과 커스터마이징 중심의 저수준 UI 컴포넌트 라이브러리
- Radix의 라디오 그룹 구현은 약 215줄의 React 코드와 7개의 파일을 import
- Radix는
<button>요소에 ARIA 속성을 추가해 라디오 버튼처럼 동작하도록 구성- 그러나 W3C의 ARIA 사용 제1원칙은 “가능한 경우 기본 HTML 요소를 사용할 것”으로 명시
- Radix는 이 원칙을 따르지 않고,
<input>대신 버튼을 재활용함
-
<form>내부에서만 숨겨진<input type="radio">를 추가하는 구조로, 일관성이 떨어짐
CSS로 가능한 단순한 대안
- 기본 HTML 라디오 버튼은
appearance: none,::before,:checked,border-radius등으로 손쉽게 스타일링 가능- 예시 코드에서는 의존성, 자바스크립트, ARIA 속성 없이 완전한 커스터마이징 구현
- 동일한 효과를 Tailwind로도 구현 가능
- “라디오 버튼 스타일링은 어렵다”는 인식은 과거의 문제이며, 현재는 순수 CSS만으로 충분한 제어 가능
복잡성의 누적 문제
- Shadcn과 Radix를 함께 사용하면 두 개의 라이브러리와 수백 줄의 코드를 이해해야 함
- 단순한 라디오 버튼 하나를 위해 수 KB의 자바스크립트가 추가 로드됨
- 사용자는 버튼 토글을 위해 JS 파싱과 실행을 기다려야 함
- 이러한 구조는 인지 부하 증가, 버그 가능성 확대, 웹 성능 저하로 이어짐
단순함으로의 회귀
- 브라우저는 이미 라디오 버튼을 기본 제공하며,
<input type="radio" name="beverage" value="coffee" />한 줄로 충분 - 불필요한 추상화와 중첩된 라이브러리 사용은 웹 개발의 본래 단순성과 효율성을 해침
- 작은 UI 요소라도 기본 기능을 재활용하는 설계가 유지보수성과 성능 모두에 유리함
지루하고 현학적:
function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
return (
<RadioGroupPrimitive.Root
data-slot="radio-group"
className={cn("grid gap-3", className)}
{...props}
/>
);
}
...
금방 끝남 오래 기억에 남음:
<input type="radio" name="beverage" value="coffee" />
제가 frontend 분야라 오랫동안 겪은 문제인데, 뭐랄까 해결이 정말 어려운 문제이긴 합니다. 구현은 시대에 따라 계속 바뀌고 있지만 input type으로 해결하지 않는 건 어느 시대나 똑같네요...
웹브라우저의 라디오 체크박스 버튼 동작 흉내내보겠다고 접근성 관련 스펙을 따로 구현하는 건 대체 뭐하자는건지... 모르겠어요... 본문에 있듯 css로도 지금은 대안이 있는데 죽어도 컴포넌트로 구현하겠다고 하는 걸 보면 좀 웃기긴 합니다.
Hacker News 의견들
-
프론트엔드를 자주 다루지는 않지만, React가 주류가 되던 시점부터 복잡성이 커질 조짐이 보였음
다른 추상화 계층은 단순화되는 경향이 있지만, React는 그 기반 기술보다 훨씬 복잡한 추상화를 만들어냄
React만 아는 개발자들이 점점 더 높은 레이어로 쌓아 올리면서 과도하게 설계된 결과를 낳는다고 느낌- 이제는 모두가 React가 기본값이라고 당연히 여기는 분위기가 더 문제라고 생각함
예를 들어 Shadcn이나 Radix는 React 전용 UI 라이브러리인데, 마케팅 문구만 보면 일반적인 UI 라이브러리처럼 보임 - 나는 15년 넘게 순수 JavaScript와 DOM API로 UI를 만들어왔음
규모가 커지면 결국 나만의 프레임워크를 만들거나 기존 프레임워크에 불만을 품게 됐는데, React는 그 문제를 어느 정도 해결해줌
React 자체보다 생태계의 과잉 복잡성이 문제라고 봄. React만 잘 다루면 여전히 즐겁게 쓸 수 있음 - 이건 React만의 문제가 아니라, 사람들이 현대 CSS를 배우려 하지 않는 게 더 큰 문제라고 생각함
Tailwind 같은 도구로 CSS를 우회하려고만 함. 나는 React로 상태를 관리하지만, 스타일링은 CSS로 직접 하는 걸 선호함
팀원들에게 CSS를 배우게 설득하는 게 가장 어려운 일임 - 추상화는 본래 복잡성을 감추는 철학적 도구여야 하는데, 요즘은 오히려 더 복잡하게 만드는 경우가 많음
나는 이런 “현대적” 프레임워크를 피하고, 가능한 한 기본 기술을 선호함 - React의 핵심은 컴포넌트 추상화임
React는 단지 “박스”를 제공하고, 그 안에 무엇을 넣을지는 개발자가 결정함. 그게 React의 진짜 힘임
- 이제는 모두가 React가 기본값이라고 당연히 여기는 분위기가 더 문제라고 생각함
-
이 라디오 버튼 예시는 웃기면서도 인상적임
결과물은 기본 CSS 라디오 버튼과 구분이 안 되는데, 왜 이렇게 복잡하게 만드는지 의문임
큰 규모의 사이트 중 이런 불필요한 복잡성 없이 만들어진 사례가 있는지 궁금함- McMaster-Carr 사이트가 좋은 예시로 자주 언급됨. 관련 Hacker News 스레드도 있음
- 15년 전 비디오 협업 웹앱을 만들었는데, 프론트엔드는 거의 jQuery 기반의 바닐라 구조였음
지금보다 코드량은 많았지만, 인터페이스는 즉각 반응하는 속도감이 있었음
Takeoff 프로젝트 코드 참고 가능 - “이 사이트는 어때?” — 바로 이 Hacker News 자체가 그런 예시일지도 모름
- 회사 규모가 커질수록 관리자는 표준화된 스택을 원함
“React를 선택해서 잘린 사람은 없다”는 말처럼, 안전한 선택이 되어버림 - UI는 모두가 볼 수 있고 의견이 많기 때문에, 복잡성이 공공재의 비극처럼 커지는 구조임
-
개발자는 디자인 요구사항에 항상 반박할 수 있음을 기억해야 함
React Native에서 단순한 레이아웃 문제로 4시간을 낭비하던 개발자에게 “디자인을 조금 바꿔도 되냐”고 물어보라 했더니, 10분 만에 해결됨- 나는 요즘 JS 없는 UI 프레임워크들(Pico.CSS, Skeleton, Bulma, Tailwind/daisyUI)을 선호함
CSS만 잘 써도 충분히 좋은 결과를 얻을 수 있음. 혹시 이런 접근 써본 사람들의 추천이 궁금함
- 나는 요즘 JS 없는 UI 프레임워크들(Pico.CSS, Skeleton, Bulma, Tailwind/daisyUI)을 선호함
-
2025년에 가장 큰 실수는 Shadcn을 선택한 것이었음
Radix를 계속 import하는 걸 보고 첫 번째 경고, radio 컴포넌트를 보고 두 번째 경고를 느낌
이미 프로젝트가 진행 중이라 포기하고 Copilot으로 수정했지만, 결과적으로 AI 의존도 마음에 들지 않았음
이전 POC는 훨씬 단순하고 효율적이었음. 언젠가 전부 바닐라 HTML로 다시 만들고 싶음- React+NextJS+Tailwind+Shadcn 조합은 복잡함의 끝판왕임
Remix나 React Router 7은 그래도 웹 표준에 가깝게 유지하려는 시도가 있었음
Tailwind에서 “이건 아니다” 싶었고, 친구들이 리팩터링 후에도 좋다고 하면 그때 다시 볼 생각임 - 사실 Tailwind와 React는 잘 맞지 않음
React에는 props 기반의 스타일링이 있는데 굳이 CSS 클래스 덩어리를 쓸 이유가 없음
접근성 중심이라면 Radix UI만 써도 충분함
- React+NextJS+Tailwind+Shadcn 조합은 복잡함의 끝판왕임
-
브라우저의
<input>요소, 특히 radio와 select는 커스터마이징이 어렵다는 게 문제임
기본 라디오 버튼은 모바일에서 사용성이 떨어짐- 사실 네이티브 컨트롤도 CSS로 충분히 스타일링 가능함
모바일에서 어떤 문제가 있었는지 더 구체적으로 알고 싶음 - 글에서도 CSS로 라디오 버튼을 꾸미는 방법을 설명함. 그게 문제인가?
-
<label>로 감싸고 패딩을 주면 모바일에서도 충분히 쓸 만함 - “select”만큼은 여전히 스타일링이 까다롭지만, 나머지는 꽤 유연하게 커스터마이징 가능함
- 사실 네이티브 컨트롤도 CSS로 충분히 스타일링 가능함
-
대부분의 프로젝트는 좋은 의도로 시작하지만, 어느새 200줄짜리 라디오 버튼 코드와 7개의 import로 가득 차게 됨
이렇게 코드 부패(code rot) 가 시작됨 -
최근 daisyUI를 써봤는데 꽤 마음에 듦
순수 CSS 기반이라 브라우저의 새로운 기능(dialog 등) 을 잘 활용할 수 있음 -
글의 요지에는 공감하지만, 디자이너가 Figma에서 만든 정확한 스타일을 모든 브라우저에서 동일하게 구현하려면 바닐라 CSS로 가능한지 궁금함
Radix의 데모 같은 걸 완전히 재현할 수 있을까?- 약간의 수정만으로도 꽤 비슷하게 만들 수 있음
CodePen 예시 참고 가능 - 결국 그 복잡한 프레임워크 아래에서도 CSS가 핵심임
CSS만 추출해서 간단한 React 컴포넌트에 붙이면 충분함 - 이 예시처럼 바닐라 input과 동일한 CSS를 적용하면 브라우저 호환성도 좋음
- 글쓴이 본인이 직접 등장해, “기본 예시를 단순히 Shadcn 스타일에 맞춘 것일 뿐, 원하면 얼마든지 커스터마이징 가능하다”고 밝힘
- 하지만 어디까지 완벽함을 추구할지 고민됨
약간의 시각적 불일치를 피하려고 수십 KB의 코드와 유지보수 부담을 추가하는 게 과연 가치 있는가?
남춘백(백남준)의 말처럼 “너무 완벽하면 신이 화낸다”는 생각이 듦
- 약간의 수정만으로도 꽤 비슷하게 만들 수 있음
-
진짜 비용은 코드가 아니라 온보딩 시간임
새로 합류한 개발자가 Radix 기반의 47줄짜리 라디오 버튼을 이해하려면 몇 주가 걸림
반면 바닐라 방식은 하루면 만들고 20분이면 설명 가능함
물론 Figma나 Linear처럼 접근성과 키보드 내비게이션이 중요한 제품이라면 복잡성이 정당화됨- 좋은 라이브러리는 내부 구조를 몰라도 쓸 수 있게 해야 하지 않을까 하는 의문이 있음
-
많은 댓글이 Shadcn을 비판하지만, 나는 오히려 컴포넌트 구조와 재사용성을 잘 장려한다고 생각함
Shadcn의 핵심은 “컴포넌트를 직접 소유하고 수정하라”는 철학임
이는 기존 UI 라이브러리와는 근본적으로 다른 접근임