前置き
ここでご紹介するButtonコンポーネントの構成はあくまで複数あるであろうベストプラクティスの一つとして存在できたらいいなと思っています。
そのため、考慮漏れなどがあるかもしれませんので、その際はコメントなどでご指摘いただけると幸いです!
この記事で知れること
- 複数のスタイルに対応できるButtonコンポーネントの作り方
結論
僕はいつも下記のようなButtonコンポーネントを用意し、プロジェクトで使いまわしています。
import React from 'react';
import { cn } from '@/libs/utils';
import { VariantProps, cva } from 'class-variance-authority';
export const buttonVariants = cva(
`flex h-10 items-center justify-center gap-2 rounded-full px-4 text-textL_medium transition disabled:opacity-50 disabled:pointer-events-none transition`,
{
variants: {
variant: {
primary: `bg-accent text-primaryWhite hover:opacity-75`,
outline: `bg-white text-accent border border-accent hover:opacity-75`,
icon: `bg-transparent hover:bg-bg-gray rounded-full p-2 h-fit`,
},
},
defaultVariants: {
variant: 'primary',
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{ className, variant, children, disabled, type = 'button', ...props },
ref
) => {
return (
<button
type={type}
className={cn(buttonVariants({ variant, className }))}
ref={ref}
disabled={disabled}
{...props}
>
{children}
</button>
);
}
);
Button.displayName = 'Button';
import { twMerge } from 'tailwind-merge';
import { type ClassValue, clsx } from 'clsx';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
実際の使い方
このコードでは、一つのButtonコンポーネントからプライマリー、セカンダリー、アイコンの3種類のボタンを生成しています。
import { Button } from '@/components/Button/Button';
import { HiPlus } from 'react-icons/hi';
export default function HomePage() {
return (
<div>
<div className="m-14 flex w-fit flex-col gap-4">
{/* Primary */}
<Button>プライマリーボタン</Button>
{/* Secondary */}
<Button variant="outline">セカンダリーボタン</Button>
{/* Icon */}
<Button variant="icon" className="h-10 w-10 p-0">
<HiPlus size={32} />
</Button>
</div>
</div>
);
}
コンポーネントの解説
このButtonコンポーネントで意識していることは、一つのコンポーネントで複数のスタイルに対応できるということです。
これを実現するために、utils.ts
でcn
というユーティリティ関数を別途作成し、Buttonコンポーネントに適用しています。
Buttonコンポーネントでvariant
を指定することで、対応したスタイルがボタンに適用され、複数の用途で使用することができるようになります。
buttonVariants
でvariants
を指定することができます。もし他のvariant
を追加したい場合は、ここにスタイルを追加するだけでButtonコンポーネントにスタイルを適用できるようになります。
export const buttonVariants = cva(
// ベースのボタンスタイル
`flex h-10 items-center justify-center gap-2 rounded-full px-4 text-textL_medium transition disabled:opacity-50 disabled:pointer-events-none transition`,
{
variants: {
variant: {
// 各種ボタンスタイル
primary: `bg-accent text-primaryWhite hover:opacity-75`,
outline: `bg-white text-accent border border-accent hover:opacity-75`,
icon: `bg-transparent hover:bg-bg-gray rounded-full p-2 h-fit`,
},
},
defaultVariants: {
// デフォルトのボタンスタイル
variant: 'primary',
},
}
);
utils.ts
で定義したcn
は、button
タグにclassNameを適用する際に使用しており、こうすることでTailwindのクラス名をいい感じに結合してくれるtwMerge
と、classNameを動的に生成する時に便利なclsx
の両方の恩恵を受けられるようにしています。
<button
type={type}
className={cn(buttonVariants({ variant, className }))}
ref={ref}
disabled={disabled}
{...props}
>
{children}
</button>
インラインスタイル的なカスタマイズ
下記コードのように、ButtonコンポーネントにclassNameを適用することができ、指定したスタイルで上書きをすることができます。そのため、一部のボタンのサイズを変えたいなどの要望にも柔軟に対応できるような構成になっています。
{/* Icon */}
<Button variant="icon" className="h-10 w-10 p-0">
<HiPlus size={32} />
</Button>
まとめ
個人的に、Next.js、Tailwind、TypeScriptで開発を行うことが多いため、この技術スタックを使う時におすすめのButtonコンポーネントの作り方ということでご紹介しました。
もし「こうしたらさらに使い勝手良くならない?」などのご意見があったらどんどんください!
Buttonコンポーネントの最適解をアップデートしましょう。