ハクソク

世界を動かす技術を、日本語で。

Shadcnラジオボタンの過剰な複雑さ

概要

  • ShadcnRadixのReactコンポーネントを使ったラジオボタンの実装事例
  • ネイティブHTML要素カスタムUIライブラリの比較
  • ARIA属性Tailwind CSSによる複雑化の指摘
  • CSSだけでのシンプルなカスタマイズ方法の紹介
  • Web開発の複雑化とパフォーマンス低下への懸念

ShadcnとRadixによるラジオボタン実装の現状

  • 仕事でWebアプリのラジオボタンのデザイン更新を依頼された体験談
  • 伝統的な**<input type="radio">**要素のシンプルさの強調
  • 実際のコードベースではShadcnのReactコンポーネント(RadioGroup, RadioGroupItem)を使用
  • Shadcnは従来のUIフレームワークと異なり、npm installではなくコードを直接プロジェクトにコピーする運用
  • コード内でRadixLucide Reactなど複数の外部ライブラリ、30以上のTailwindクラスを利用
  • SVGアイコンや複雑なクラス指定による肥大化と依存性の増加
    • 例:CircleIconのためだけに外部アイコンライブラリを導入

Radixの仕組みと複雑化の原因

  • ShadcnRadix Primitivesをベースにスタイルを追加
  • Radix自体はアクセシビリティやカスタマイズ性重視の「素の」UI部品群を提供
  • 実際にはボタン要素SVGARIA属性でラジオボタンを再実装
  • ARIA属性で「ボタンをラジオボタンとして解釈させる」手法
  • First Rule of ARIA(ネイティブ要素を優先)の原則に反する設計
  • <form>内でのみ**隠し<input type="radio">**を追加する特殊な挙動

ラジオボタンのスタイリングは本当に難しいのか

  • Radixがカスタム実装を選ぶ理由は「スタイリングの簡便化」と推測
  • 近年はappearance: noneや**:checked**、::beforeなどのCSSだけで十分なカスタマイズが可能
  • 例:CSSのみで丸いラジオボタン+選択時のドット表示を実現
    • 依存ライブラリ・JS・ARIA不要
  • Tailwindでも同様の実装が可能
  • Shadcnのコンポーネントも結局は大量のクラス指定が必要

シンプルなHTML+CSSの例

  • ネイティブの**<input type="radio">**とシンプルなCSSで十分な表現力
  • 追加の依存や複雑なロジック不要
  • 例:
    input[type="radio"] {
      appearance: none;
      margin: 0;
      border: 1px solid black;
      background: white;
      border-radius: 50%;
      display: inline-grid;
      place-content: center;
    }
    input[type="radio"]::before {
      content: "";
      width: 0.75rem;
      height: 0.75rem;
      border-radius: 50%;
    }
    input[type="radio"]:checked::before {
      background: black;
    }
    

ライブラリ利用の是非とWeb開発の複雑化

  • コンポーネントライブラリ利用の動機は「手軽さ」と「問題解決への集中」
  • ただし「できるだけネイティブ要素を使う」ことの重要性を強調
  • 小さな最適化でも積み重なると複雑化・パフォーマンス低下・バグ増加の要因
  • JS依存による初期表示遅延やファイルサイズ増加の懸念
  • 結論:「ラジオボタンはシンプルでいい」「過剰な抽象化・ライブラリ依存は慎重に」

まとめ:本質を見失わないWeb開発

  • ラジオボタンのような基本UIはブラウザの機能を活用
  • 理解しやすさ・保守性・パフォーマンスを重視
  • 小さな選択の積み重ねが全体の品質に影響
  • シンプルなHTMLが持つ美しさと合理性

おまけ

  • 「Tiled Words」という無料の単語パズルゲームの紹介

Hackerたちの意見

なんか、こういうプロジェクトって、最初はいい意図で始まって、見た目も良くて、実装してそのまま放置されちゃうんだよね。で、ある日気づいたら、ラジオボタンのために200行以上のコードと7つのインポートがあって、もう全部を引っ張り出さないと戻れない状況になってる。これがコードの腐敗の始まりなんだよ。
shadcnのラジオボタンの実際の動きはこちら: https://ui.shadcn.com/docs/components/radio-group
このインタラクティビティ、確かにすごい効果を与えてるね。
これって、聞いたことのある10倍の開発者たちが開発したの?
プロのヒント:UIコントロールとラベルの間のスペースは、パディング(またはラベルのネストを使って)で作るべきだよ。そうすれば、全体がクリック可能になるから。[ x ] 長いラベル ꜛꜛꜛ ここにパディング、マージンや隙間じゃなくてね(コントロールとラベルの間をクリックしても何も起こらないから)。
Radixの元の作者たちに、なんでこうなってるのか聞いたのかな?
まさにその通り。OPは、こうなった理由を理解していないし、何千時間もこれに取り組んできた人が知らないことを知っているかもしれないってことを分かってないんだよ。
2020年には、ラジオボタンはすべての主流のブラウザで簡単にスタイルできなかったんだ。それが、いくつかのコンポーネントが過剰に設計される理由だよね。もちろん、すべてのブラウザが揃ったときに簡略化すべきだったけど、技術的負債は厄介なんだ。
普段はこの記事の意見に賛同するけど、ちょっと気になることがあるんだ。もし目標が、- デザイナーがfigmaファイルで送った通りにラジオボタンを実装すること(例えば、彼らがコメントしてるradixのデモみたいに: https://www.radix-ui.com/primitives/docs/components/radio-gr...) - すべてのブラウザで全く同じ見た目にすること だったとしたら、バニラCSSでどれくらい実現可能なの?彼らが示した例は黒白の円にレンダリングされてたけど、ほとんどのチームはそれを出荷しないよね。
でも、どこで線引きするの?ラジオボタンのちょっとした視覚的不一致を避けるために、どれくらいのキロバイトと将来のメンテナンス作業が必要なの?ネットワーク接続が悪い人たちを失う価値があるの?このアプローチをどこでも使ったら、ページの実際のコンテンツ(人々が求めてるものね)が影響を受けちゃう。思い出すのは、世界的に有名なビデオアーティスト、ナム・ジュン・パイクの言葉。「完璧になると、神は怒る」ってやつ。
ちょっとした修正だけでかなり近づけるよ。 ```css input[type="radio"] { appearance: none; margin: 0; width: 25px; height: 25px; background: white; border-radius: 50%; display: inline-grid; place-content: center; box-shadow: 0 2px 10px color(display-p3 0 0 0/0.5); &::before { content: ""; width: 11px; height: 11px; border-radius: 50%; } &:checked::before { background: color(display-p3 0.383 0.317 0.702); } } ``` これがどう見えるか、コードペンのリンクを貼っとくね。自分でレンダリングしなくても見れるよ: https://codepen.io/erikaja/pen/RNRVMyB
> - すべてのブラウザで全く同じに見えるようにしろ > バニラCSSでどれくらい実現可能なの? おしゃれなフロントエンドフレームワークや20個のインポート、1万行のTypeScriptじゃ無理だよ。「すべてのブラウザで全く同じに見えるようにしろ」ってのは、ウェブが本来どう機能するべきかと根本的に矛盾してる。 このshadcnのクソみたいなのは、arachneやladybird、netsurf、links、dillo、netscape 3でどれくらいレンダリングされるの? 最新のChromeでユーザースタイルを適用した場合はどう? 「全く同じ」って言う時、デザインが黒と白だけを使ってるってことだよね?だって、モノクロモニターの人もいるかもしれないし。 でも、アンバーオンブラックも使うつもりだよね?だって、アンバースクリーンのモニターの人もいるかもしれないし。 点字端末で全く同じに見えるようにするつもりはどうなってるの? もしかして、俺がバカみたいに思えるかもしれないけど、2026年にはモノクロモニターなんて誰も使ってないよね?だからそれを無視して「全く同じ」の横にアスタリスクを付けるのが安全だよね(そしてe-inkが存在することも忘れちゃう)。 (2006年には、誰もが800x600以上のディスプレイを持っていると仮定するのが安全だったのと同じように、480×320の解像度の画面を使う人が現れることはないと思ってたよね) 色が異なるモニターの種類やブランドで全く同じに見えるようにするために、どんな対策を講じたの? それとも、「全く同じ」の横にもう一つアスタリスクを付けるべき? どれくらいのアスタリスクがあれば「全く同じ」じゃなくなるの? 「すべてのブラウザで全く同じに見えるようにする」ってのが目標の一つなら、間違ってるよ。デザイナーがそう言ったら、彼らも間違ってる。もしクライアントにそれを提供すると言ったら、君も間違ってる。
> バニラCSSでどれくらい実現可能なの? フレームワークの複雑さの下でも、その特定の見た目はCSSで実現されてるよ。実際、彼らが使ってるCSSをほとんど修正せずに取り出して、サードパーティのインポートが不要な約5行のReactコンポーネントと組み合わせることができる。
このラジオ選択、バカみたいに素晴らしいよ。特に、最終的な結果がバニラCSSのラジオボタンと見分けがつかないからね。でも、なぜか人々は複雑なUIやインタラクティブなフレームワークに戻ってくる。これらの膨れ上がったものなしで作られた大きなウェブサイトの良い例ってある?小さなサイトは優雅さとシンプルさで何百も見たけど、大きなサイトはほとんど見たことない。チームの規模が大きくなると、誰かが狂気を持ち込むのは避けられないの?これらのツールは、私が見逃してる実際の問題を解決してるのかな?
特定の怪しいライブラリについては言えないけど、成長するにつれてデフォルトのスタイルが合わなくなったり、存在しないものが欲しくなったりするのは確かだよね。要は、こういうことはウェブサイトじゃなくてウェブアプリでよく起こるってこと。ウェブアプリはもっと複雑でパワフルだからね。でも、これはスペクトラムで、時にはウェブサイトがウェブアプリに成長することもあるから、初期段階で過剰に設計しちゃう人が多いのも分かる。
> 誰か、この無駄なものなしで作られた大きなウェブサイトの良い例を持ってる? このサイトはどう?
https://www.mcmaster.com 2022年の投稿について。1400ポイント。コメント約500件: https://news.ycombinator.com/item?id=32976978
ウェブ開発には関わってないけど、この記事を読んで思ったのは、そんなに複雑なフレームワークを使う必要があるのかな? HTML/CSSだけで十分じゃない? 「書かれていない行はバグにならない」ってみんな言うけど、その行をライブラリに移すのがその言葉の意図じゃなかったはず。
> HTML/CSSだけじゃ足りないの? もちろん足りないよ。状態を持つ複雑なウェブアプリケーションを書いてるなら、データのローカル処理や非同期のやり取りがあるからね。JavaScriptが必要だよ。もしJavaScriptが特に複雑で、宣言的にしたいなら、フレームワークが必要かもね。JavaのTomcatが必要かって? 複雑なアプリには多分必要だけど、シンプルなプロトタイプにはいらないかな。データベースは必要? ファイルだけで足りるんじゃない? みたいな感じで。Shadcnは、インタラクティブなウェブアプリを開発するためのフレームワークだよ。もし必要なのがデータをウェブサービスに送信するだけの静的なフォームなら、フレームワークは多分いらない(ただし、必要な場合もあるけどね。例えば、すべてのブラウザでセレクトボックスが完全にスタイル可能じゃないから)。次の反論はだいたい「クライアントに複雑なアプリは必要?」ってことだよね。サーバーが制御する一連のシンプルなフォームにできないの? できる時もあれば、できない時もあるけど、もちろん自分が作るアプリ(または他の人に作ってもらうアプリ)の形や動作、複雑さ、見た目は自分で決めるから、よろしくね。そう言えば、ラジオボタンは少なくとも5~6年前から、すべての非レガシーブラウザでスタイル可能だったから、SVGで一から書き直す理由はないよ。
この複雑さの本当のコストはコード自体じゃなくて、オンボーディングなんだよね。プロジェクトに新しい開発者が参加すると、ラジオボタンがRadixのプリミティブやコンテキストプロバイダー、スタイル付きバリアントで47行のJSXが必要な理由を理解しなきゃいけない。 チームがコンポーネントライブラリの内部に慣れるまでに数週間かかるのを見てきたけど、同時に「シンプルな」バニラアプローチは構築するのに午後一日かかるかもしれないけど、説明するのに20分で済む。 とはいえ、FigmaやLinearのような、Radixが提供するアクセシビリティプリミティブやキーボードナビゲーションが本当に必要なものを作っているなら、その複雑さには見合う価値があるよ。でも、ほとんどのCRUDアプリには必要ないけどね。
> AじゃなくてBだよ。言葉の選び方が怪しいね。
最近はフロントエンドにあまり触れないけど、Reactが主流になって新しい開発者たちがその抽象化の中でしか働いていないのを見て、複雑さの兆しが見えてきた。 他の抽象化とは違って、Reactはその上に構築されている技術よりもずっと複雑なんだ。機能を有効にするために必要だからだけど、それでも誰かがReactや他のフレームワークしか知らないと、過剰に設計されてしまう。 もっとシンプルにできるって気づかなかったんだろうね。もっと高いレイヤーに登るんじゃなくて、一つ下げればよかったのに。
さらに悪いのは、Reactがシンプルだという誤解だよ。 それはキャッシュ無効化バグの終わりのない流れなんだ。リンターはこれを捕まえるのが上手くなってきてるけど、偽陽性もあるよ。
問題はアプリとドキュメントのインピーダンスミスマッチだね。CSSはドキュメントのようなページを楽にするけど、ドキュメントっぽいページもアプリっぽい良さが欲しいんだよね。アプリとして機能する必要があるなら、通常はフレームワークが必要で、そうなるとReactとかになるんだよ。でも、彼らは完全なコントロールを求めるから、ラジオボタンのようなものは2つの方法で実装されるんだ。
始めたときは良かったけど、useEffectとフックの追加がすべてを台無しにしたんだ。普段は関数型が好きだけど、Reactではクラスの方が100倍良かったよ。
状態を管理してDOMに手動で同期させるのは、React(または他の大きなフレームワーク)よりも、どんな非トリビアルなウェブアプリでもずっと難しいよ。反応的で、元々非同期で、イベント駆動のアプリケーションは簡単に複雑になるからね。
いい指摘だね。哲学的な観点から見ると、抽象化は複雑さを隠して、人間ユーザーにとって使いやすくするべきだと思う。ピラミッドみたいに、下の層が最も複雑で、上に行くほどシンプルになるべきなんだ。でも、今の多くの抽象化は過去の技術に基づいていて、その時代の制約から、もっと良くデザインされていてシンプルだったことが多い。今日の抽象化の複雑さと避けられない漏れのせいで、使いにくい「現代的な」フレームワークやツールがたくさんあって、開発者に精神的な負担をかけてる。要するに、そういうフレームワークは避けて、できるだけ古くて退屈な基本を使うようにしてるよ。
> 誰かがReactや他のフレームワークしか知らないと、過剰設計になっちゃう 次のレベルのイライラは、みんながReactをすべてのデフォルトだと思い込んでることだね。Shadcnのウェブサイトをチェックしてみて。ランディングページには、これがReact専用のUIライブラリだとは全く書いてないよ。Radixも同じ。マーケティングは一般的なUIライブラリのように聞こえる。これがReact専用だと気づくには、ちょっと掘り下げる必要がある。
ちなみに、ラジオボタンって今では(悲しいことに)忘れ去られたアートで、現代のブラウザでは無視されがちだよね。いろいろ問題があるから、みんな自分で実装し直してるんだ。
これは「過剰複雑」と言えるのはナイーブな見方からだね。ラジオボタンは他のUIコントロールと同様に、非常に複雑な内在的な特性を持っていて、要求がデフォルトのブラウザボタンの幸せな道を超えると、その複雑さが明らかになる。ピクセルパーフェクトなスタイリング、アニメーション、フォーカスの挙動、外部状態とのインタラクション、企業のエコシステムに合わせたコンポーネント化されたブランディングなど。基本的なパラダイムは、今でもウェブ開発が何十年も続いているのに、この複雑さを適切に扱うためのツールを提供するのに苦労してる。もちろん、デフォルトのブラウザボタンを使えばすべて解決するべきだという意見もあるけど、それも最適ではない。研究からも、ユーザーはデフォルトよりも「機能」を提供するカスタムボタンを好むことが明らかだからね。
> 研究からも、ユーザーはデフォルトよりも「機能」を提供するカスタムボタンを好むことが明らか。 「ソース」を求めるのは嫌だけど、どの研究?ラジオボタンにはどんな「機能」があるの?クリックしたら選択されるだけじゃん。アクセシビリティは「機能」と見なせるかもしれないけど、過剰なボタンはアクセシビリティが悪いんじゃないかと疑ってる。 > すべてのUIコントロールは、非常に複雑な内在的特性を持っている まあ、これはある意味で真実だけど、その複雑さをJS/HTMLで再実装することの良い理由にはならないよね。ブラウザの実装を使う方がずっといいと思う。
同意する、こういう複雑さには理由があるよね。コードベース内での使用において、すべてのケースを処理する複雑なコンポーネントがある方が、ちょっとしたハックや変更がたくさんあるよりもいいと思う。一つの複雑なコンポーネントを維持する方が、そのコンポーネントの多様な使い方を維持するよりもずっと楽だし、必要なければそんな複雑なコンポーネントライブラリを使う必要もない。小さなコードベースではオーバーキルになることが多いけど、大きなコードベースでは非常に価値のある投資だよ。