shadcn/ui の Button コンポーネントを眺めてみる


こんにちは。Gaji-Labo 横田です。前回 shadcn/ui の導入について記事にしました。今回は shadcn/ui の Button コンポーネントのコードを眺めてみたいと思います。

Button コンポーネント

shadcn/ui のコンポーネントのインストールは CLI では add コマンドで行います。
Button コンポーネントの場合は以下となります。

npx shadcn-ui@latest add button

プロジェクトで使用したいコンポーネントだけを選んでインストールできるのがいいですね。

ちなみに公式ドキュメントから手動でコードをコピペし、任意のディレクトリに Button コンポーネントファイルを作成することも可能です。その場合は先に依存関係のある radix-ui/react-slot をインストールしておく必要があります。手順は以下の Manual タブをご確認ください。
https://ui.shadcn.com/docs/components/button#installation

インストールが完了すると、Next.js 環境では src/app/components/uibutton.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 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground shadow hover:bg-primary/90",
        destructive:
          "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
        outline:
          "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
        secondary:
          "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2",
        sm: "h-8 rounded-md px-3 text-xs",
        lg: "h-10 rounded-md px-8",
        icon: "h-9 w-9",
      },
    },
    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 }

buttonVariants の定義でボタンの色とサイズのスタイルが複数パターン指定されています。bg-primary など Tailwind CSS のデフォルトでは定義されていない class は、 tailwind.config.ts ファイル内で指定されています。
次に ButtonProps というインターフェースで Button コンポーネントの props を定義しています。React.ButtonHTMLAttributes<HTMLButtonElement> で HTML の button 要素の全ての標準属性(たとえば disabledonClick など)を含めています。VariantProps<typeof buttonVariants>buttonVariants に定義されたバリエーションの props を受け取ることができるようになっています。オプショナルなプロパティ asChildButton コンポーネントが子要素としてレンダリングされるべきかどうかを示しています。
最後に Button コンポーネントの定義で React.forwardRef を使用して ref を子コンポーネントに直接渡すことができます。asChild prop で radix-ui の Slot コンポーネントまたは button 要素をレンダリングします。

radix-ui の Slot:https://github.com/radix-ui/primitives/blob/main/packages/react/slot/src/Slot.tsx

Button コンポーネントを使用する

ページでの使い方は簡単です。たとえばサイズが lg で secondary のスタイルをあてたボタンは以下のように指定するだけです。

import { Button } from "../components/ui/button";

export default function Button() {
  return (
    <Button variant="secondary" size="lg">Secondary</Button>
  );
}
レンダリングされたbutton要素のコード
レンダリングされたbutton 要素

Button コンポーネントを a 要素(リンク)で使いたい時は以下のように asChild を true にしてリンクコンポーネントをネストできます。

import Link from "next/link"

import { Button } from "../components/ui/button"

export default function ButtonAsChild() {
  return (
    <Button asChild>
      <Link href="/login">Login</Link>
    </Button>
  )
}
レンダリングされたa要素のコード
レンダリングされた a 要素

shadcn/ui を使うメリット

コードを眺めてみると、前回お伝えした利点「コードを自由にカスタマイズすることができる」「拡張のしやすさから、自作した UI コンポーネントとも共存させやすい」が理解できると思います。
radix-ui を元にスタイルのバリエーションが複数用意されたコンポーネントのテンプレートのようなものなので、プロジェクトに合わせた見た目のカスタマイズを(Button コンポーネントであれば buttonVariants 定義で)Tailwind CSS を使って追加・削除・変更・拡張することが容易です。
HTML の標準属性を props で渡せるので、アクセシビリティ対応などで属性を追記するのも容易です。

そしてコンポーネントのコードがそのまま露出していますので、shadcn/ui のお作法に寄せて、自作コンポーネントを作ることも難しくなさそうです。ライブラリの使用と自作コードの整合性をとりやすいのは、開発者にとってスピードやメンテナンス性における大きなメリットになると思います。

終わりに

今回は Button コンポーネントのコードを眺めてみました。公式ドキュメントを見ると、PaginationCarousel など新しいコンポーネントが続々と追加されており、ますます導入しやすくなりそうです。

Gaji-Laboでは、React経験が豊富なフロントエンドエンジニアを募集しています

弊社ではReactの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?

もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!

求人応募してみる!

投稿者 Yokota Tomoko

運用やアクセシビリティに配慮したHTML/CSSの設計やコンポーネント作成、スタイルガイドの構築、コードレビュー、組み込み、要件の整理、社内進行管理、顧客とのコミュニケーションまで、ジョインしたチームを前に進めるためにあれこれ担当しています。子育てと仕事のバランスを楽しめるよう、日々模索しています。