shadcn/uiを使って簡単なWebページを作ってみた
はじめに
こんにちは、D2Cのフロントエンドエンジニアをやっている廣瀬です。
私が担当しているプロジェクトでは、Vite
・React
・MUI
を主に使用しています。
最近、フロントエンド系の技術を調査してみたところ、shadcn/ui
というものを見つけました。
今回、このshadcn/ui
をVite
で試してみましたので、その内容を記事にしたいと思います。
今回の記事では簡単なWebページを作ってみたいと思います。
shadcn/uiとは?
Radix UI
とTailwind CSS
をベースに開発された、比較的低レイヤーなUIコンポーネントの集まりです。
ここで注意なのが、shadcn/ui
はMUI
やChakraUI
のようなUIライブラリではなく、npmパッケージとしては提供されていません。ですので、依存関係としてインストールすることはできません。
環境の準備
実際に、以下のような手順でshadcn/ui
を利用できるよう環境を作ります
プロジェクトの立ち上げ
$ yarn create vite
? Project name: > practice-shadcnui
? Select a framework: > React
? Select a variant: > TypeScript + SWC
Tailwindをインストールし、設定ファイルを生成
$ cd practice-shadcnui
$ yarn
$ yarn add --dev tailwindcss postcss autoprefixer
$ yarn tailwindcss init -p
tsconfig.json
を編集
{
"compilerOptions": {
...
...
↓追記
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
...
...
}
}
@types/node
をインストールし、vite.config.ts
を編集
$ yarn add --dev @types/node
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
shadcn-ui
のCLIを追加し、components.json
を設定
以下の操作を行うことによって、shadcn/ui
が用意するコンポーネントをyarn
コマンドのCLIで追加することができます。
$ yarn add shadcn-ui
以下のコマンドを打つと、いくつか質問されるので、以下の通りに回答していく
$ yarn shadcn-ui init
? Would you like to use TypeScript (recommended)? > yes
? Which style would you like to use? > Default
? Which color would you like to use as base color? > Slate
? Where is your global CSS file? > src/index.css
? Would you like to use CSS variables for colors > yes
? Where is your tailwind.config.js located? > tailwind.config.js
? Configure the import alias for components: > @/components
? Configure the import alias for utils: > @/lib/utils
? Are you using React Server Components? > no
? Write cofiguration to components.json. Procced? > yキーを押下
これで、 shadcn/ui
を利用できる環境の構築が完了しました!
プロジェクトディレクトリ配下を確認してみると、
components.json
やcomponents/
、lib/utils.ts
が生成されていることを確認できるかと思います。
practice-shadcnui/
├── src/
│ ├── components/
│ └─── lib/
│ └── utils.ts
└─── components.json
開発
上の手順で、開発環境は構築できましたので
早速、shadcn/ui
を使用して開発に入っていきましょう!
ヘッダーの実装
このセクションでは、shadcn/ui
が用意しているbutton
を使用するので、以下のコマンドで追加していきます。
$ yarn shadcn-ui add button
src/components/
配下を確認してみるとui/
ディレクトリが生成され、さらにその配下にbutton.tsx
が生成されていると思います。このように、インストールしたコンポーネントは自動的にui/
配下に生成されていきます。
src/
└─ components/
└─ ui/
└─ button.tsx
それでは、src/components/
配下にヘッダーコンポーネントを作成していきましょう。
$ mkdir src/components/Header && touch src/components/Header/index.tsx
// Header/index.tsx
import { Button } from "@/components/ui/button";
export const Header = () => {
return (
<div className="fixed flex justify-between px-8 w-screen h-16 bg-teal-400 items-center drop-shadow-2xl border-b border-gray-300 shadow-md">
<h1 className="font-bold text-2xl">shadcn-ui TUTORIAL</h1>
<div className="flex gap-3">
<Button variant="outline">
<a href="https://ui.shadcn.com/docs">公式 Document</a>
</Button>
<Button>menu</Button>
</div>
</div>
);
};
// App.tsx
import { Header } from "@/components/Header";
function App() {
return (
<div>
<Header />
</div>
);
}
export default App;
結果↓
修正
しかし、このままでは「公式 Document」のボタンのフォントが少し細い感じがしますね。(個人的に)
なので、少しbutton.tsx
を直接編集してみましょう。
// src/components/ui/button.tsx
...
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
// ↓ outlineに'font-bold'を追記!
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground font-bold",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
...
これで、variant
をoutline
に指定したButton
コンポーネントすべてのフォントは太字になります。
結果(修正後)↓
カードの実装
このセクションでは、shadcn/ui
が用意しているcard
を使用するので、以下のコマンドで追加していきます。
$ yarn shadcn-ui add card
それでは、src/components/
配下にカードコンポーネントを作成していきましょう。
$ mkdir src/components/CardDemo && touch src/components/CardDemo/index.tsx
// CardDemo/index.tsx
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { VFC } from "react";
type Props = {
cardTitle: string;
cardDescription: string;
cardContent: string;
cardFooter: string;
};
export const CardDemo: VFC<Props> = (props) => {
return (
<Card>
<CardHeader className="h-32">
<CardTitle>{props.cardTitle}</CardTitle>
<CardDescription>{props.cardDescription}</CardDescription>
</CardHeader>
<CardContent>
<Button variant="outline" className="border-solid border-2 border-gray-700">
<a href={props.cardContent} target="_blank">
Usage {props.cardTitle}
</a>
</Button>
</CardContent>
<CardFooter>
<p>{props.cardFooter}</p>
</CardFooter>
</Card>
);
};
次に、複数のカードを用意するために、それっぽいデータを他で用意しましょう。
$ mkdir src/components/constants && touch src/components/constants/data.ts
// constants/data.ts
export const cardData = [
{
title: "Accordion",
description:
"A vertically stacked set of interactive headings that each reveal a section of content.",
content: "https://ui.shadcn.com/docs/components/accordion",
footer: "shadcn/ui",
},
{
title: "Alert",
description: "Displays a callout for user attention.",
content: "https://ui.shadcn.com/docs/components/alert",
footer: "shadcn/ui",
},
{
title: "Avatar",
description: "An image element with a fallback for representing the user.",
content: "https://ui.shadcn.com/docs/components/avatar",
footer: "shadcn/ui",
},
{
title: "Badge",
description: "Displays a badge or a component that looks like a badge.",
content: "https://ui.shadcn.com/docs/components/badge",
footer: "shadcn/ui",
},
{
title: "Button",
description: "Displays a button or a component that looks like a button.",
content: "https://ui.shadcn.com/docs/components/button",
footer: "shadcn/ui",
},
{
title: "Card",
description: "Displays a card with header, content, and footer.",
content: "https://ui.shadcn.com/docs/components/card",
footer: "shadcn/ui",
},
{
title: "Checkbox",
description: "A control that allows the user to toggle between checked and not checked.",
content: "https://ui.shadcn.com/docs/components/checkbox",
footer: "shadcn/ui",
},
{
title: "Collapsible",
description: "An interactive component which expands/collapses a panel.",
content: "https://ui.shadcn.com/docs/components/collapsible",
footer: "shadcn/ui",
},
{
title: "Date Picker",
description: "A date picker component with range and presets.",
content: "https://ui.shadcn.com/docs/components/date-picker",
footer: "shadcn/ui",
},
];
// App.tsx
import { Header } from "@/components/Header";
import { CardDemo } from "@/components/CardDemo";
import { cardData } from "@/constants/data";
function App() {
return (
<div>
<Header />
<section className="container flex pt-32 grid grid-cols-2 gap-10 xl:grid-cols-3">
{cardData.map((data) => (
<CardDemo
cardTitle={data.title}
cardDescription={data.description}
cardContent={data.content}
cardFooter={data.footer}
/>
))}
</section>
</div>
);
}
export default App;
結果↓
修正
しかし、これではカードの枠が少し寂しい感じがします。
これもcard.tsx
を直接編集してみます。
...
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
// shadow-smを'shadow-2xl'に変更!
"rounded-lg border bg-card text-card-foreground shadow-2xl w-96",
className
)}
{...props}
/>
)
);
Card.displayName = "Card";
...
結果(修正後)↓
タブの実装
このセクションでは、shadcn/ui
が用意しているtabs
,input
,textarea
を使用するので、追加していきます。
$ yarn shadcn-ui add tabs input textarea
それでは、src/components/
配下にタブコンポーネントを実装していきましょう。
$ mkdir src/components/TabsDemo && touch src/components/TabsDemo/index.tsx
// TabsDemo/index.tsx
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
export const TabsDemo = () => {
return (
<Tabs defaultValue="about" className="w-[800px] h-[400px]">
<TabsList className="w-full">
<TabsTrigger value="about" className="w-1/2">
About
</TabsTrigger>
<TabsTrigger value="contact" className="w-1/2">
Contact
</TabsTrigger>
</TabsList>
<TabsContent value="about" className="w-full h-full">
<Card className="w-full h-full flex flex-col justify-between">
<CardHeader className="text-center">
<CardTitle>About</CardTitle>
<CardDescription>
Dolor voluptatibus eum dolores blanditiis cumque eaque! Laboriosam neque
illum ab tempore quae sapiente? Culpa repellat facilis accusamus maiores
quibusdam consectetur quidem expedita Deleniti tempore voluptates
aliquid perferendis incidunt! Rem.
</CardDescription>
</CardHeader>
<CardContent className="w-full flex items-center justify-center">
<div className="w-2/3">
<div className="">
<div>
<strong className="text-2xl">
Consectetur eveniet magnam debitis dolorum iste Quam
sequiquisquam
</strong>
<br />
<br />
doloribus sed eos In quod sunt delectus voluptatibus a
</div>
</div>
<div className="">
<div>
Consectetur exercitationem asperiores nihil vel autem Explicabo
ipsa corrupti vitae accusantium nam modi, repellat. Aliquid
temporibus
<br />
<strong className="text-md">
consectetur neque fugit quasi Cupiditate aliquam hic
</strong>
</div>
</div>
</div>
</CardContent>
<CardFooter className="flex justify-end">
<Button>Detail</Button>
</CardFooter>
</Card>
</TabsContent>
<TabsContent value="contact" className="w-full h-full">
<Card className="w-full h-full flex flex-col justify-between">
<CardHeader className="text-center">
<CardTitle>Contact</CardTitle>
<CardDescription>
Sit omnis libero facere rem reprehenderit Quis quasi dolor itaque
blanditiis repellendus? Explicabo beatae numquam eum unde deserunt
voluptates perferendis totam modi sint libero. Laborum sint nihil
corporis aliquid delectus.
</CardDescription>
</CardHeader>
<CardContent className="w-full flex items-center justify-center">
<div className="w-2/3">
<div className="">
<div className="w-full flex flex-col items-center">
<Input
type="text"
placeholder="Title"
className="transition duration-500 w-[400px] mb-4"
/>
<Textarea
className="transition duration-500 w-[600px] h-[150px]"
placeholder="detail"
/>
</div>
</div>
</div>
</CardContent>
<CardFooter className="flex justify-end">
<Button>Send</Button>
</CardFooter>
</Card>
</TabsContent>
</Tabs>
);
};
結果↓
最終調整
最終的に軽くTop部分も作り、調整しました。
ディレクトリ構成とApp.tsxは以下のようになりました。
src
├── App.css
├── App.tsx
├── assets
│ └── react.svg
├── components
│ ├── CardDemo
│ │ └── index.tsx
│ ├── Header
│ │ └── index.tsx
│ ├── TabsDemo
│ │ └── index.tsx
│ ├── Top
│ │ └── index.tsx
│ └── ui
│ ├── button.tsx
│ ├── card.tsx
│ ├── input.tsx
│ ├── tabs.tsx
│ └── textarea.tsx
├── constants
│ └── data.ts
├── index.css
├── lib
│ └── utils.ts
├── main.tsx
└── vite-env.d.ts
// App.tsx
import { Header } from "@/components/Header";
import { CardDemo } from "@/components/CardDemo";
import { TabsDemo } from "@/components/TabsDemo";
import { Top } from "@/components/Top";
import { cardData } from "@/constants/data";
function App() {
return (
<div>
<Header />
<section className="pt-16 w-full container">
<Top />
</section>
<section className="container flex pt-32 pb-32 grid grid-cols-2 gap-10 xl:grid-cols-3">
{cardData.map((data) => (
<CardDemo
cardTitle={data.title}
cardDescription={data.description}
cardContent={data.content}
cardFooter={data.footer}
/>
))}
</section>
<section className="pt-[120px] pb-[200px] flex justify-center bg-gray-900">
<TabsDemo />
</section>
</div>
);
}
まとめ&感想
shadcn/ui
は比較的低レイヤーなUIコンポーネントを揃えています。
また、スタイリングのベースがTailwind
であるため、Tailwind
にあまり抵抗感がなく、操作に慣れている方にとっては、カスタマイズしやすい且つ柔軟性が非常に高く感じられるのではと思いました。
終わりに
今回は簡単なWebページを作ってみましたが、これからしっかりしたものも作ってみたりして、学習を続けていきたいと思います。
また、shadcn/ui
の公式ドキュメントは比較的シンプルで読みやすいものになっていると思いますので、ご興味のある方はぜひ読んでみると面白いかもしれません。
最後までお読みいただきありがとうございました。
参考
https://ui.shadcn.com/docs
https://zenn.dev/mottox2/articles/react-shadcn-ui
https://reffect.co.jp/react/shadcn-react
株式会社D2C d2c.co.jp のテックブログです。 D2Cは、NTTドコモと電通などの共同出資により設立されたデジタルマーケティング企業です。 ドコモの膨大なデータを活用した最適化を行える広告配信システムの開発をしています。
Discussion