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 요소라도 기본 기능을 재활용하는 설계가 유지보수성과 성능 모두에 유리함
댓글과 토론
제가 frontend 분야라 오랫동안 겪은 문제인데, 뭐랄까 해결이 정말 어려운 문제이긴 합니다. 구현은 시대에 따라 계속 바뀌고 있지만 input type으로 해결하지 않는 건 어느 시대나 똑같네요...
웹브라우저의 라디오 체크박스 버튼 동작 흉내내보겠다고 접근성 관련 스펙을 따로 구현하는 건 대체 뭐하자는건지... 모르겠어요... 본문에 있듯 css로도 지금은 대안이 있는데 죽어도 컴포넌트로 구현하겠다고 하는 걸 보면 좀 웃기긴 합니다.
지루하고 현학적:
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" />
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 라이브러리와는 근본적으로 다른 접근임