🎨

MUI vs Chakra UI vs shadcn/ui vs daisy UI ~React開発に最適なUIライブラリは?~

2024/09/17に公開

はじめに

Reactでアプリを作るとき、UIライブラリ選びって結構迷いますよね?👀
たくさんのオプションがある中で、どれが自分のプロジェクトに合うのか、正直悩むことも多いはずです。
そこで今回は、よく耳にする「MUI」「Chakra UI」「shadcn/ui」「daisy UI」をざっくり比較してみました!

MUI vs Chakra UI vs shadcn/ui vs daisy UI

まずはそれぞれを比較した表をご覧ください(9/17時点)。

MUI chakra UI shadcn/ui daisy UI
週間ダウンロード数 4,195,558 589,640 - 270,511
Last Publish 6 days ago 16 days ago 2 days ago 8 days ago
Qiita 195 156 29 48
Zenn 185 121 120 20
ライブラリの種類 (React) component UI library (React) component UI library Headless UI component UI library
Base emotion(styled-components) emotion Radix UI×TailwindCSS TailwindCSS
componentの数
カスタマイズ性
その他 最も人気で、10年間安定している chakraはナルトの『チャクラ』が由来 JavaScriptエコシステムの中で最も注目されている 公式ドキュメントが日本語に対応しており、ものすごく読みやすい

シェア率

MUIが圧倒的でした。
shadcn/uiはnpm経由で利用することはないので、ダウンロード数は不明でした。

サポート&ドキュメント数

今のところ、コミュニティや開発者からのサポート面はどのライブラリも問題なさそうです。

ドキュメントはMUI、Chakra UIが豊富でした。
shadcn/uiはトレンドなので、これからどんどん増えていきそうです。
対してdaisy UIは少ないので公式ドキュメントを熟読する必要がありそうです。

MUI

MUIは最も人気なUIライブラリです。
コンポーネントはemotionでスタイルを構築されていますが、styled-componentsのスタイルエンジンに切り替えることもできます。

良い点

  • Google's Material Designを標準で実装している。
  • Reactのエコシステムの中で最も大きいコミュニティを持っている。
  • 10年間継続的にメンテナンスされているため、安定性は抜群。
  • 高性能で複雑なコンポーネントも使用できる(一部有料)。

悪い点

import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button";

export const MuiButton = () => {
  return (
    <Stack spacing={2} direction="row">
      <Button variant="text">Text</Button>
      <Button
        variant="contained"
        sx={{
          borderRadius: "30px",
        }}
      >
        Contained
      </Button>
      <Button
        variant="outlined"
        sx={{
          "&:hover": {
            backgroundColor: "black",
          },
        }}
      >
        Outlined
      </Button>
    </Stack>
  );
};

chakra UI

Chakra UIは、シンプルにモジュール化されたUIライブラリです。
MUIと同様、emotionがベースとなっております

良い点

  • MUIと比べてよりシンプルで柔軟性の高いコンポーネントを提供している。
  • styleをpropsを用いてオーバーライドできるように設計されている。

悪い点

  • emotionをベースと使用しているため、パフォーマンスが少し悪い。
import { Stack, Button } from "@chakra-ui/react";

export const ChakraButton = () => {
  return (
    <Stack spacing={3} direction="row" align="center">
      <Button variant="ghost" colorScheme="teal">
        TEXT
      </Button>
      <Button variant="solid" colorScheme="teal" borderRadius="30px">
        CONTAINED
      </Button>
      <Button variant="outline" colorScheme="teal" _hover={{ bg: "#111111" }}>
        OUTLINED
      </Button>
    </Stack>
  );
};

shadcn/ui(シャドシーエヌ)

shadcn/uiはRadix UIとTailwind CSSをベースに開発された、Headless UIライブラリです。
Headless UIとは機能のみ提供するコンポーネントであり、見た目は開発者がカスタマイズします。
また、JavaScript ライジングスター 2023(年間のGitHubスターの増加数をランキングにしたサイト)の総合ランキングで1位になっており、最近のReactプロジェクトではかなり採用されているみたいです!

最も他のライブラリと異なる点は、npm経由でインストールを行わないところです。
代わりにCLIを通じてコードを取得します。
イメージがつきにくいと思うので、環境構築の手順を説明していきます。

  1. TailwindCSSの環境構築を行う
  2. 以下コマンドを実行し、質問に答えます。
$ npx shadcn-ui@latest init
1. Would you like to use TypeScript (recommended)? no/yes
TypeScriptを使いたいか?

2. Which style would you like to use? › Default
デフォルトかNew Yorkどちらのスタイルを使いたいか?

3. Which color would you like to use as base color? › Slate
ベースカラーは何にするか?(後から変更不可)

4. Where is your global CSS file? › › app/globals.css
グローバルCSSファイルはどこに置くか?

5. Do you want to use CSS variables for colors? › no / yes
CSS変数で色を設定するか?(後から変更不可)

6. Where is your tailwind.config.js located? › tailwind.config.js
tailwind.config.jsをどこに置くか?

7. Configure the import alias for components: › @/components
コンポーネントをインポートのエイリアスは?

8. Configure the import alias for utils: › @/lib/utils
utilsのimportのエイリアスは?

9. Are you using React Server Components? › no / yes
React Serverのコンポーネントを使うか?

  1. 使いたいコンポーネントをnpxで実行(コードをコピー)します。
    今回はbuttonにします。
$ npx shadcn-ui@latest add button
  1. 3でコピーしたコンポーネントはcomponents/ui(2を実行した時点で勝手にできてる)配下にbutton.tsxとして作成されます。
// 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 };

なぜ、npmでインストールを行わないのでしょうか。
公式ではインストールを行わない理由について以下のように述べています。

この背後にある考え方は、あなたにコードの所有権と制御権を与え、コンポーネントの構築方法とスタイルを決定できるようにすることである。
まず、いくつかの理にかなったデフォルトを設定し、それからあなたのニーズに合わせてコンポーネントをカスタマイズしてください。
コンポーネントをnpmパッケージにすることの欠点の一つは、スタイルが実装と結合してしまうことです。コンポーネントのデザインは、実装とは切り離して考えるべきです。

npmでインストールするコンポーネントは既にスタイルが組み込まれているため、カスタマイズ性に欠けます。
そこで、shadcn/uiはコンポーネントをcomoponents/ui配下に直接コピーすることで、スタイルを完全に開発者へ委ねます。
あなたが書いたコードということにしてあげるから、好きにしてねってことですね。

良い点

  • カスタマイズ性に優れている
  • TailwindCSSとの相性が良い
  • コピーした時点で自分のコードとなるので、shadcnのバージョンを気にする必要がない(アップデートの影響を受けない)。

悪い点

  • ファイルが増える
  • 場合によってはディレクトリ構成の定義を考え直す必要が出てくる
  • 意図しないライブラリ(icon等)が勝手にinstallされる

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

function App() {
  return (
    <div className="flex space-x-4">
      <Button variant="ghost">TEXT</Button>
      <Button className="rounded-[30px]">CONTAINED</Button>
      <Button variant="outline" className="hover:bg-sky-200">
        OUTLINE
      </Button>
    </div>
  );
}

export default App;

daisy UI

daisy UIはTailwind CSSのプラグインとして、UIコンポーネントを提供しているライブラリです。
また、JavaScript ライジングスター 2023のCSSライブラリ部門ランキングで1位になっています(shadcnUIはCSSライブラリではないみたい)。

Tailwind CSSのプラグインなので、TailwindCSSでスタイルを書くようにコンポーネントを利用できます!

  1. daisyUIをインストール
npm i -D daisyui@latest
  1. tailwind.config.jsファイルにdaisyUI を追加します
module.exports = {
  //...
  plugins: [require("daisyui")],
}
  1. 使用したいコンポーネントをclassNameに記述する。
 //btnと記述するだけでbuttonのコンポーネントを使用できる
 <button className="btn">CONTAINED</button>

良い点

  • TailwindCSSの環境下のあれば、どんなフレームワークでも使用可能
  • TailwindCSSのユティリティクラスがすべて使用できる
  • JavaScriptを必要としない

悪い点

  • クラス名が長くなる
  • サンプルが少ない

function App() {
  return (
    <div className="flex space-x-4">
      <button className="btn btn-ghost">TEXT</button>
      <button className="btn">CONTAINED</button>
      <button className="btn btn-outline btn-accent">OUTLINED</button>
    </div>
  );
}

export default App;

React Hook Formとの相性

個人的にReact Hook Formをよく使用するので検証します。

MUI

基本的にはControllerでラップする必要があります。
TextFieldはregisterでも問題なく動作しますが、サンプルとしてControllerを使用しています。

import { useForm, Controller } from "react-hook-form";

import { TextField } from "@mui/material";
import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button";

export const MuiForm = () => {
  const { handleSubmit, control } = useForm<{ name: string }>({
    mode: "onBlur",
    defaultValues: {
      name: "aiful",
    },
  });
  const onSubmit = (data: { name: string }) => {
    console.log(data);
  };
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller<{ name: string }>
        name="name"
        control={control}
        rules={{ required: "入力してください" }}
        render={({ field, fieldState }) => {
          return (
            <TextField
              {...field}
              sx={{
                marginBottom: "10px",
              }}
              error={fieldState.error ? true : false}
              helperText={fieldState.error?.message}
            />
          );
        }}
      />
      <Stack spacing={2} direction="row">
        <Button
          variant="outlined"
          type="submit"
          sx={{
            "&:hover": {
              backgroundColor: "black",
            },
          }}
        >
          SUBMIT
        </Button>
      </Stack>
    </form>
  );
};

Chakra UI

MUIと比べたら少しめんどくさいです。
InputコンポーネントのpropshelperTextがないため、エラーメッセージ用にFormErrorMessageというコンポーネントを使用する必要があります。
参考:Chakra UI + React Hook Form

import { useForm, Controller } from "react-hook-form";
import { Input, FormControl, FormErrorMessage } from "@chakra-ui/react";

import { Stack, Button } from "@chakra-ui/react";

export const ChakraButton = () => {
  const { control, handleSubmit } = useForm<{ name: string }>({
    mode: "onBlur",
    defaultValues: {
      name: "aiful",
    },
  });
  const onSubmit = (data: { name: string }) => {
    console.log(data);
  };
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <Controller<{ name: string }>
        name="name"
        control={control}
        rules={{ required: "入力してください" }}
        render={({ field, fieldState, formState }) => {
          return (
            //MUIと比べて若干記述量が多くなる
            <FormControl isInvalid={formState.errors.name ? true : false}>
              <Input {...field} marginY="10px" width="200px" />
              <FormErrorMessage color="red">
                {fieldState.error?.message}
              </FormErrorMessage>
            </FormControl>
          );
        }}
      />
      <Stack spacing={3} direction="row" align="center">
        <Button
          variant="outline"
          type="submit"
          colorScheme="teal"
          _hover={{ bg: "#111111" }}
        >
          OUTLINED
        </Button>
      </Stack>
    </form>
  );
};

shadcn/ui

shadcn/uiにはReact Hook Formを使用することを前提にしたコンポーネントがあります。

npx shadcn-ui@latest add form

これを使用してFormを構築しますが、shadcn/ui独自の書き方であるため学習コストがかかります。

参考:React Hook Form - Building forms with React Hook Form and Zod.

import { useForm } from "react-hook-form";

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

// formコンポーネント
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormMessage,
} from "./components/ui/form";
import { Input } from "./components/ui/input";

function App() {
  const form = useForm<{ name: string }>({
    mode: "onBlur",
    defaultValues: { name: "aiful" },
  });
  const onSubmit = (data: { name: string }) => {
    console.log(data);
  };
  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        <FormField
          control={form.control}
          name="name"
          rules={{ required: "入力してください" }}
          render={({ field, fieldState }) => (
            <FormItem>
              <FormControl>
                <Input {...field} className="w-[200px] my-[10px]" />
              </FormControl>
              <FormMessage>{fieldState.error?.message}</FormMessage>
            </FormItem>
          )}
        />
        <Button variant="outline" type="submit" className="hover:bg-sky-200">
          OUTLINE
        </Button>
      </form>
    </Form>
  );
}

export default App;

daisy UI

普通に書くだけです。
Controllerを使用していないので、恐らくパフォーマンス(再レンダリングの面で)が最も良いです。

import { useForm } from "react-hook-form";

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<{ name: string }>({
    mode: "onBlur",
    defaultValues: { name: "aiful" },
  });
  const onSubmit = (data: { name: string }) => {
    console.log(data);
  };
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div className="flex flex-col space-y-4 w-[200px]">
        <input
          type="text"
          className="input border border-gray-300 my-[10px]"
          {...register("name", {
            required: "入力してください",
          })}
        />
        {errors.name && <p className="text-red-500">{errors.name.message}</p>}
        <button type="submit" className="btn btn-outline btn-accent">
          SUBMIT
        </button>
      </div>
    </form>
  );
}

export default App;

ライブラリを選定する際に考慮すべきこと

自分の考えを述べているだけなので、参考程度でお願いします。

細かいUXやデザインを求めているか

細かいUXやデザインを求めている場合、カスタマイズがしやすいshadcn/uiを使用すべきかなと思います。

逆に最低限のUXとデザインで良いのであれば、コンポーネントの完成度が高いMUIを使用すべきです。

パフォーマンスを気にするか

SSGであればCSS-in-JSでもbuild時にCSSファイルを生成するので、パフォーマンスに支障が出ないです。
しかし、選択肢としてSSRかCSRしかない場合(例えば認証機能を備えたプロダクト)はパフォーマンスが落ちます。
実際にChakra UIの公式では以下のように述べています。

Chakra UIはCSS-in-JSを使用しているため(emotion + styled-system)、この柔軟性は実行時に小さな代償を伴います。この実行時のフットプリントは、styled-system によるスタイルの計算と、emotion による className の生成によって発生します。
Chakraの使用を決定する前に、他の選択肢と比較することをお勧めします。しかし、ほとんどの小規模または中規模のデータ駆動型アプリケーションでは、Chakra UIは完璧にフィットすると考えています。

まとめ

どのUIライブラリを選ぶかは、プロジェクトの規模や要件、そしてチームの好みによって大きく変わります。
それぞれのライブラリには一長一短があるので、自分に合ったものを見つけることが大切です。
トレンドに敏感であればshadcn/uiに挑戦してみるのもアリですし、実績のあるMUIやChakra UIで安定を取るのも悪くありません。
最終的には、実際に試してみてフィットするものを選ぶのが一番ですね笑
ちなみに私はdaisy UI推しです💫

参考資料

https://mui.com/
https://v2.chakra-ui.com/
https://ui.shadcn.com/
https://daisyui.com/
https://risingstars.js.org/2023/ja#section-all
https://gizumo-inc.jp/media/headless-ui/
https://tayori.com/blog/material-design/
https://zenn.dev/a_da_chi/articles/8fe30435792108
https://zenn.dev/morinokami/articles/anatomy-of-shadcn-ui
https://webty.jp/staffblog/production/post-5172/
https://zenn.dev/wado63/articles/09e09151d160f3
https://zenn.dev/ryotarohada/articles/aa2ee0bad05e2a
https://qiita.com/bee-yan/items/33214781b8dd991e2768
https://zenn.dev/pe_be_o/articles/maeta-187-articles_203f9f7ca10eaf
https://zenn.dev/remon/articles/8d6f840a1d10e8
https://dev.to/wonder2210/build-forms-using-react-hook-form-and-chakraui-4087
https://qiita.com/uehaj/items/969ef20ccef850d2e9b1
https://www.material-tailwind.com/blog/chakra-ui-vs-material-ui

Discussion