はじめに
「shadcn/uiは良いけど、Reactプロジェクトじゃないと使えないんだよな」
この悩み、同じように感じている人も多いんじゃないでしょうか。個人的にはshadcn/uiのデザインが好きで、でもPHPやDjango、素のHTMLプロジェクトでも同じクオリティのUIを使いたい場面って結構あるんですよ。
そこで見つけたのがBasecoat。キャッチコピーは「All of the shadcn/ui magic, none of the React」。これ、意外と刺さりました。
GitHubで3,200スター以上を獲得しているこのライブラリ、実際に使ってみたら期待以上だったので紹介します。
説明を読むより、まずは触ってみた方が早いと思います。.cardだけで組んだログインフォームに、Basecoatのテーマ切り替えAPIを乗せたサンプルを用意したので、先に動かしてみたい方はこちらからどうぞ。
Basecoatとは
BasecoatはTailwind CSSで構築されたUIコンポーネントライブラリです。shadcn/uiと同等の美しいデザインを、Reactなしで実現できるのが最大の特徴。
公式サイトでは「A components library built with Tailwind CSS that works with any web stack」と説明されています。つまり、PHPでもPythonでも、Go製のテンプレートエンジンでも、どんな環境でも動く。
現在のバージョンは0.3.6で、MITライセンスのオープンソース。40以上のコンポーネントを備えています。
正直なところ、「React使わない人向けのshadcn/ui」という理解で間違いないですね。
特徴・メリット
1. ランタイムJavaScriptがほぼ不要
これが個人的に一番刺さったポイント。Basecoatは基本的にCSSベースで動作するので、JavaScriptのバンドルサイズを気にする必要がない。
ドロップダウンメニューやポップオーバーなど、インタラクティブな6コンポーネントだけJavaScriptが必要ですが、それ以外は純粋なCSSで完結します。
30代になって思うのは、パフォーマンスへの意識って年々高まってくるんですよね。コスパ的に、軽量なライブラリはありがたい。
2. フレームワーク非依存
Laravel、Django、Rails、WordPress、素のHTMLサイト。どれでも使えます。
「このプロジェクトはReactじゃないから...」という理由で妥協したUI選択をしなくて済む。これ、意外と大きいです。
3. shadcn/uiテーマと完全互換
shadcn/uiで作ったカスタムテーマをそのまま使えます。チームでReactプロジェクトとそれ以外が混在している場合でも、デザインの一貫性を保てる。
@import "tailwindcss";
@import "basecoat-css";
@import "./theme.css"; /* shadcn/uiテーマをそのまま流用 */
4. クラス名がシンプル
Tailwindの長いクラス名地獄から解放される。btnとかinputとか、直感的なクラス名でスタイリングできます。
<!-- これだけでボタンが完成 -->
<button class="btn">Click me</button>
5. ダークモード標準対応
Tailwind CSSの設定を尊重するので、既存のダークモード実装とシームレスに連携できます。
インストール方法
CDNで手軽に始める(おすすめ)
HTMLファイルの<head>に2行追加するだけ。
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@0.3.6/dist/basecoat.cdn.min.css">
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@0.3.6/dist/js/all.min.js" defer></script>
これだけで全コンポーネントが使えるようになります。時短になるので、プロトタイプや小規模プロジェクトにはこれ一択ですね。
特定のコンポーネントだけ使う
バンドルサイズを削りたい場合は、必要なJavaScriptだけ読み込む方法も。
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@0.3.6/dist/basecoat.cdn.min.css">
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@0.3.6/dist/js/basecoat.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@0.3.6/dist/js/dropdown-menu.min.js" defer></script>
npm / pnpm / yarn / bunでインストール
本格的なプロジェクトなら、パッケージマネージャーで管理した方がいいでしょう。
# npmの場合
npm install basecoat-css
# pnpmの場合
pnpm add basecoat-css
# bunの場合
bun add basecoat-css
CSSへのインポート
@import "tailwindcss";
@import "basecoat-css";
JavaScriptのインポート(ESM対応プロジェクト)
// 全コンポーネント
import 'basecoat-css/all';
// 個別インポート
import 'basecoat-css/basecoat';
import 'basecoat-css/popover';
import 'basecoat-css/tabs';
Basecoatのサンプルを動かす
Basecoatの.cardコンポーネントは、<div class="card">の直下に<header>・<section>・<footer>をそのまま並べるだけでレイアウトが完成します。card-headerやcard-titleのような専用クラスを別途足す必要はありません。ボタンの見た目はclass="btn"にdata-variant="outline"のような属性を足して切り替える仕組みで、shadcn/uiのようにバリアントごとにクラス名を増やさずに済むのが特徴です。
下のサンプルでは.cardでログインフォームを組み、data-variant="outline"のボタンからbasecoat.theme.toggle()を呼び出しています。これはBasecoatのJavaScript(basecoat.min.js)が公開しているAPIで、呼び出すたびに<html>要素へdarkクラスを付け外しし、選んだテーマをlocalStorageにも保存します。
要点だけ抜き出すと、こんな見た目です。
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.2/dist/basecoat.cdn.min.css">
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.2/dist/js/basecoat.min.js" defer></script>
<div class="card">
<header>
<h2>ログイン</h2>
<p>メールアドレスでサインインします</p>
</header>
<section>
<label class="label" for="email">メールアドレス</label>
<input class="input" type="email" id="email" placeholder="email@example.com">
</section>
<footer>
<button class="btn">ログイン</button>
<button class="btn" data-variant="outline" onclick="basecoat.theme.toggle()">
ダークモード切り替え
</button>
</footer>
</div>
実際に動かせるのが下のサンプルです。ボタンを押すたびにカード・入力欄の配色が反転し、ページを再読み込みしても直前に選んだテーマが保持されます。
data-variant="outline"をdata-variant="secondary"やdata-variant="destructive"に書き換えると、ボタンの配色そのものが変わることも確認できます。またinputのplaceholderやheader内の見出しテキストを書き換えれば、フォームの文言もその場で反映されます。ダークモード対応が後付けのCSSではなく、コンポーネント自体とJavaScript APIに組み込まれていることが実感できるはずです。
基本的な使い方
ボタン
<!-- 基本のボタン -->
<button class="btn">Default</button>
<!-- バリエーション -->
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-outline">Outline</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-destructive">Destructive</button>
<button class="btn btn-link">Link</button>
クラス名を追加するだけでスタイルが変わる。シンプルでいいですよね。
サイズ指定
<button class="btn btn-sm">Small</button>
<button class="btn">Default</button>
<button class="btn btn-lg">Large</button>
カード
<div class="card">
<div class="card-header">
<h3 class="card-title">プロジェクト作成</h3>
<p class="card-description">新しいプロジェクトを作成します</p>
</div>
<div class="card-content">
<p>プロジェクトの詳細を入力してください。</p>
</div>
<div class="card-footer">
<button class="btn">作成</button>
</div>
</div>
フォーム入力
<div class="space-y-4">
<div>
<label class="label" for="email">メールアドレス</label>
<input type="email" id="email" class="input" placeholder="email@example.com">
</div>
<div>
<label class="label" for="password">パスワード</label>
<input type="password" id="password" class="input">
</div>
<button class="btn w-full">ログイン</button>
</div>
バッジ
<span class="badge">Default</span>
<span class="badge badge-secondary">Secondary</span>
<span class="badge badge-outline">Outline</span>
<span class="badge badge-destructive">Error</span>
ドロップダウンメニュー(JS必須)
<div class="dropdown-menu">
<button class="btn btn-outline dropdown-menu-trigger">
メニューを開く
</button>
<div class="dropdown-menu-content">
<div class="dropdown-menu-item">プロフィール</div>
<div class="dropdown-menu-item">設定</div>
<div class="dropdown-menu-separator"></div>
<div class="dropdown-menu-item">ログアウト</div>
</div>
</div>
実践的なユースケース
管理画面のサイドバー
<aside class="sidebar">
<div class="sidebar-header">
<span class="font-semibold">管理画面</span>
</div>
<nav class="sidebar-content">
<a href="#" class="sidebar-item sidebar-item-active">
ダッシュボード
</a>
<a href="#" class="sidebar-item">
ユーザー管理
</a>
<a href="#" class="sidebar-item">
設定
</a>
</nav>
<div class="sidebar-footer">
<button class="btn btn-ghost w-full">ログアウト</button>
</div>
</aside>
ダイアログ(モーダル)
<div class="dialog">
<button class="btn dialog-trigger">ダイアログを開く</button>
<div class="dialog-content">
<div class="dialog-header">
<h2 class="dialog-title">確認</h2>
<p class="dialog-description">この操作を実行してもよろしいですか?</p>
</div>
<div class="dialog-footer">
<button class="btn btn-outline dialog-close">キャンセル</button>
<button class="btn">確認</button>
</div>
</div>
</div>
Tailwindクラスでのオーバーライド
Basecoatのスタイルはあくまでベース。Tailwindのクラスを追加すれば簡単にカスタマイズできます。
<!-- フォントを細くする -->
<button class="btn font-normal">Click me</button>
<!-- 角を丸くする -->
<div class="card rounded-2xl">...</div>
<!-- 影を強調 -->
<div class="card shadow-lg">...</div>
CLIでテンプレート生成(Jinja/Nunjucks向け)
npx basecoat-cli add dialog
このコマンドでJavaScriptとマクロテンプレートが自動生成されます。Jinja2やNunjucksを使っているプロジェクトでは重宝しますね。
Basecoatのボタン・バッジをdata-variant属性で切り替える
Basecoatのボタンやバッジは、Tailwind CSSライブラリにありがちなbtn-primary・btn-outlineのようなクラス名の並びではなく、data-variantというカスタム属性1つで見た目を切り替えます。管理画面でステータスに応じてボタンやバッジの色を動的に変えたい場合、JavaScriptからelement.dataset.variantを書き換えるだけで対応できます。
<button class="btn" data-variant="primary" id="btn">送信</button>
<span class="badge" data-variant="primary" id="badge">primary</span>
<script>
select.addEventListener('change', () => {
btn.dataset.variant = select.value
badge.dataset.variant = select.value
badge.textContent = select.value
})
</script>
セレクトボックスでdata-variantの値を切り替えると、.btnと.badgeの配色が連動して変わる様子を確認できます。
outlineやdestructiveを選ぶと、.btnと.badgeの両方が同じdata-variantの値を参照して同時に配色を変えることが分かります。削除ボタンだけdestructiveに、承認ステータスのバッジだけsecondaryに、といった実装がこの仕組み1つで完結するわけです。
Basecoatのダイアログをネイティブ<dialog>で開閉する
Basecoatのダイアログは独自のJavaScript制御を持たず、ブラウザ標準の<dialog>要素とshowModal() / close()メソッドをそのまま利用します。class="dialog"を付けるだけで、オーバーレイ表示・中央寄せ・開閉アニメーションがCSSだけで実現されるのがポイントです。
<button onclick="document.getElementById('profile-dialog').showModal()" class="btn">
ダイアログを開く
</button>
<dialog id="profile-dialog" class="dialog">
<div>
<header>
<h2>プロフィール編集</h2>
<p>変更後は保存ボタンを押してください</p>
</header>
<section>
<label class="label" for="name">名前</label>
<input class="input" id="name" value="山田太郎">
</section>
<footer>
<button data-variant="outline" onclick="this.closest('dialog').close()">キャンセル</button>
<button onclick="this.closest('dialog').close()">保存</button>
</footer>
</div>
</dialog>
実際に開閉できるサンプルが下です。「ダイアログを開く」ボタンはshowModal()を、フッターの2つのボタンはthis.closest('dialog').close()を呼んでいます。
showModal()は開くだけ、close()は閉じるだけのシンプルなAPIです。オーバーレイ(背景の暗い部分)をクリックしたときも閉じたい場合は、<dialog>にonclick="if (event.target === this) this.close()"を足すだけで対応できます。独自のモーダル用JavaScriptライブラリを追加しなくてよい分、バンドルサイズを増やさずにダイアログを実装できるわけです。
Basecoatのドロップダウンメニューを操作する
アカウントメニューや操作メニューには.dropdown-menuコンポーネントを使います。こちらは<dialog>と違い、クリックやキーボード操作(矢印キー・Escapeキーなど)をJavaScript側で制御する必要があるため、basecoat.min.js(または全部入りのall.min.js)の読み込みが必須です。読み込むとDOMContentLoadedのタイミングで、ページ内の.dropdown-menu要素が自動的に初期化されます。
<div id="account-menu" class="dropdown-menu">
<button id="account-menu-trigger" aria-haspopup="menu"
aria-controls="account-menu-menu" aria-expanded="false"
class="btn" data-variant="outline">メニューを開く</button>
<div id="account-menu-popover" data-popover aria-hidden="true">
<div role="menu" id="account-menu-menu" aria-labelledby="account-menu-trigger">
<div role="menuitem" data-value="profile">プロフィール</div>
<div role="menuitem" data-value="settings">設定</div>
<hr role="separator">
<div role="menuitem" data-value="logout">ログアウト</div>
</div>
</div>
</div>
メニュー項目をクリックすると、下に選択結果が表示されます。矢印キーでの移動やEscapeキーでの閉じる操作にも対応しているので、キーボードでも試してみてください。
メニュー項目をクリックすると、Basecoat自身のJavaScriptがポップオーバーを閉じつつ、独自に追加したクリックイベントで選択内容を画面下に表示しています。アクセシビリティ対応(role="menu"・aria-expanded・キーボード操作)が最初から組み込まれているので、ドロップダウンメニューを自前で実装し直す必要がないのが実務上のメリットです。
shadcn/uiとの使い分け
正直なところ、どちらを選ぶかは技術スタックで決まります。
| 項目 | Basecoat | shadcn/ui |
|---|---|---|
| 必須環境 | Tailwind CSS | React + Tailwind CSS |
| インストール | CDN / npm | CLIでコピー |
| JavaScript | 最小限 | React依存 |
| カスタマイズ | Tailwind / CSS変数 | ソースコード直接編集 |
| テーマ互換性 | shadcn/uiと互換 | 本家 |
| 向いている用途 | 非React環境 | Reactプロジェクト |
個人的には、Reactプロジェクトならshadcn/ui、それ以外ならBasecoat、という使い分けをしています。
デザインの統一感を保ちながら、技術スタックに合わせて選択できるのは嬉しいですね。
まとめ
Basecoatを導入して感じた変化:
- 技術選択の自由: React以外でもshadcn/ui品質のUIが使える
- 軽量: ランタイムJSがほぼ不要でパフォーマンスに優しい
- 学習コスト: Tailwind知っていればすぐ使える
- 互換性: 既存のshadcn/uiテーマをそのまま流用できる
- 導入の手軽さ: CDN2行で即使える
「Reactじゃないけど、きれいなUI欲しい」という人には刺さるはず。特にLaravel、Django、WordPressなどの伝統的なスタックで開発している人は、一度試してみる価値があると思いますよ。
40以上のコンポーネントが揃っているので、管理画面やLPなど、業務で必要になるUIパーツはほぼカバーできる印象です。
公式サイトにはキッチンシンクデモもあるので、各コンポーネントの見た目を確認してから導入を検討してみてください。