LoginSignup
5
1

ライブラリに依存したコンポーネント開発から脱却するshadcn/ui

Posted at

Shadcn UI

Shadcn UIRadix UITailwind CSSを用いたコンポーネントをプロジェクトに導入してくれるツールです。
ライブラリではなくツールとして活用できるのでプロジェクトと依存関係にはならず、作成したコンポーネントに関するあれこれをすべて手元の環境で行うところに利点があります。
このツールはNextjsやVite、RemixにAstroなど様々なプロジェクトで活用できます。
最近ではVercelが開発するAIがコンポーネントを生成してくれるv0.devの生成結果としても活用されています。

この記事ではNextjsを使う前提で記述しますが、本質的な部分は変わりません。

導入

Nextjsでプロジェクトを開始して、コンポーネントを作成します。

まずはnpx create-next-appでアプリケーションの準備を行います。実行後の質問は以下のように答えました。Nextjsのバージョンは14.0.3です。
スクリーンショット 2023-12-06 22.21.07.png
そして、npx shadcn-ui@latest initでShadcn UIの初期化を行います。
スクリーンショット 2023-12-06 22.33.39.png
基本的には全部デフォルトで答えましたが、global CSSの場所をsrc/app/globals.cssに、Tailwind CSSの設定ファイルの拡張子をtsに変更しました。

これによってtailwind.config.tspackage.jsonpackage.lock.jsonsrc/app/globals.cssの更新が行われ、components.jsonsrc/lib/utils.tsが作成されます。

tailwind.config.tsはJavaScriptでCommon JSの記法に書き直されます。そして、contentで記述される内容も少し雑に書き換えられるので修正するのが良いと考えています。他の変更は今後作るUIのため設定を行ってます。

package.jsonpackage-lock.jsonでは生成するUIで利用するパッケージの準備をします。clsxtailwind-mergeなどが利用されます。

src/app/globals.cssでは生成するコンポーネントの色や角丸などのデザイントークンを生成されます。自由にカスタマイズして開発内容にあった内容に調整しましょう。

components.jsonはShadcn UIに関する設定が詰め込まれています。プロジェクト中で唯一Shadcn UIに関連するファイルとなります。このファイルが他と依存関係を結ぶことはないのでShadcn UIを利用しなくなったときはこのファイルを削除するだけで脱却できます。

src/lib/utils.tsはクラス名をつけるにあたって便利な関数を提供してくれます。clsxtailwind-mergeをいい感じに使えるようにしてくれています。

次にsrc/app/layout.tsxtailwind.config.tsを触ってフォントの設定とShadcn UIのデフォルトのレイアウト設定を行います。

src/app/layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { cn } from '@/lib/utils'

const inter = Inter({
  subsets: ['latin'],
  variable: "--font-sans"
})

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={cn(
          "min-h-screen bg-background font-sans antialiased",
          inter.variable
        )}>{children}</body>
    </html>
  )
}

フォントのvariables化とsrc/lib/utils.tsに追加されたcnを用いて基本的なスタイルを追加しています。

tailwind.config.ts
const { fontFamily } = require("tailwindcss/defaultTheme")

/** @type {import('tailwindcss').Config} */
module.exports = {
  // ...
  theme: {
    extend: {
      fontFamily: {
        sans: ["var(--font-sans)", ...fontFamily.sans],
      },
    },
  },
  // ...
}

tailwindの方にもフォントの設定を反映します。

これで環境の準備は完了です。npx shadcn-ui@latest add buttonのように実行することでボタンコンポーネントをsrc/components/uiに生成してくれます(CLIを用いない場合でも公式サイトで対象のコンポーネントのコードをコピーして利用できます)。

src/components/ui/button.tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

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:
          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        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",
    },
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : "button"
    return (
      <Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      />
    )
  }
)
Button.displayName = "Button"

export { Button, buttonVariants }

Nextjsの命名に則ってkebab-caseでファイルを作ってくれます。スタイリングはTailwind CSSをベースにclass-variance-authorityで変数ごとに出し分けしながら定義しています。そして、Radix UIのSlotを用いてasChildを渡したときは要素をchildrenに合わせるようなコンポーネントを定義しています。

設定ファイル

設定ファイルは初期化によって生成されたcomponents.jsonを指します。このファイルはCLIを用いたコンポーネント生成の際に用いられるファイルです。設定ファイルのJSON Schemaは公式から提供されています。
スタイリングのベースとなる設定を行ったり、tailwindの設定ファイルを参照させたり、RSC・TSXの有無を設定したり、コンポーネントや便利関数をまとめるファイルの保存先を設定したりします。
基本的には初期化時に聞かれる内容が記述されているだけなので触ることはあまりないかもしれません。

おわりに

Shadcn UIを紹介しました。多くのUIコンポーネントライブラリは利用するアプリケーションがそれ自体に依存してしまい移行などがネックとなってしまいます。Shadcn UIではこの点を払拭して安定したコンポーネント開発が行えるのでぜひ試してみてはいかがでしょうか。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1