🎐

Utility-FirstなCSS,UIフレームワークを比較してみた(TailwindCSS, Chakra UI, MUI)

2021/12/05に公開

こんにちはきよまるです@kiyokiyoabc

最近話題のUtility-First。皆さんも一度は耳にしたことがあるはずです。去年CSS設計完全ガイド
が発売されたばかり(?)にも関わらず、この本が指南するCSSと人類の格闘の成果を無為にするような思想を持っています。(めっちゃいい本です)

Utility-Firstの思想その一

クラス名とそのCSSをあらかじめ用意しておこう


              終
            制作・著作
            ━━━━━
             ⓃⒽⓀ




まあその一といいましたがUtility-Firstに関しては一つの思想しかないです(多分)。
要するにクラス名を自分で考えてそれにCSSをつけるのではなく、あらかじめ用意されたユーティリティクラス(汎用的で実用的なクラス)を使って装飾しましょうということです。

Utility-Firstの何がいいのか

今まで模範とされてきたCSS設計のコードと、Utility-Firstなコード(例としてTailwindCSSを使用)を見比べてみましょう

従来

//HTML
<div class="chat-notification">
  <div class="chat-notification-logo-wrapper">
    <img class="chat-notification-logo" src="/img/logo.svg" alt="ChitChat Logo">
  </div>
  <div class="chat-notification-content">
    <h4 class="chat-notification-title">ChitChat</h4>
    <p class="chat-notification-message">You have a new message!</p>
  </div>
</div>

//CSS
<style>
  .chat-notification {
    display: flex;
    max-width: 24rem;
    margin: 0 auto;
    padding: 1.5rem;
    border-radius: 0.5rem;
    background-color: #fff;
    box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
  }
  .chat-notification-logo-wrapper {
    flex-shrink: 0;
  }
  .chat-notification-logo {
    height: 3rem;
    width: 3rem;
  }
  .chat-notification-content {
    margin-left: 1.5rem;
    padding-top: 0.25rem;
  }
  .chat-notification-title {
    color: #1a202c;
    font-size: 1.25rem;
    line-height: 1.25;
  }
  .chat-notification-message {
    color: #718096;
    font-size: 1rem;
    line-height: 1.5;
  }
</style>

Utility-First

//HTML
<div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md flex items-center space-x-4">
  <div class="flex-shrink-0">
    <img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo">
  </div>
  <div>
    <div class="text-xl font-medium text-black">ChitChat</div>
    <p class="text-gray-500">You have a new message!</p>
  </div>
</div>

出典: tailwindcss

良いところその一:HTMLだけ見ればコンポーネントの構造がわかる

個人的にはこれが一番良い点だと考えています。というのも、以前vueでコードを書いている際に、HTMLの部分とCSSの部分、そしてJSの部分を同一ページ内で行ったり来たりするのが苦痛で仕方なかったからです。HTMLの構造を頭に記憶してからCSSに潜って修正してまたHTMLに浮上して潜って...という繰り返しが時間の大幅なロスだと思います。

良いところその二:命名規則を考えなくて良い

上の例でもそうですが、.chat-notification-〇〇というクラス名を考えるのがまずめんどくさいです。また、BEM記法などを用いる場合にも、学習コストと正しい命名規則の運用コスト(どこまでの範囲をグルーピングするかなど)がかかるのがネックだと考えています。
Utility-Firstの思想を採用すれば、単純明快なクラス名と対応した簡潔なCSSで学習コストが少なく、かつ運用のコストが発生しないです。

この二つがUtility-Firstなライブラリを採用する上で他のライブラリより優れた点だと思います。

でも、これってinline stylesと何が違うの?

要素に直接styleを書き込むのと何が違うのかというのは当然の疑問だと思いますので先に書いておきます。

inline stylesとの相違点

  • 制約の下でのスタイリングができる
    • インラインスタイルの場合、全ての値はマジックナンバーになってしまいますが、Utility-Firstはデザインシステムに則ってstyleを適用できるためデザインの一貫性が保たれやすいです。
  • レスポンシブデザインやホバー、フォーカス時などの挙動に対応している
    • インラインスタイルではメディアクエリやhover,focusなどの状態を扱うことはできませんが、大体のUtility-Firstのライブラリは対応しています

毎回たくさんのクラスを指定するのが大変じゃない?

実はクラスをまとめて一つのクラスにする機能も搭載されています。

<button class="btn-indigo">
  Click me
</button>

<style>
  .btn-indigo {
    @apply py-2 px-4 bg-indigo-500 text-white font-semibold rounded-lg shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-400 focus:ring-opacity-75;
  }
</style>

このように複数のクラスを書くのが面倒な場合はまとめられます。

クラスのカスタマイズ、拡張はどうやるの?

全体に適用するthemeを拡張することでアプリケーションごとの基準値に対応しています。
例えば先ほどのコードでpy-2というクラスがありましたが、こちらは数字が2の時のpaddingの基準値をあらかじめcustom themeとして定めておいて、もし基準値の変更があった際も一貫したレイアウトの担保ができる仕組みとなっています。
こちらはChakra UIでのcustom themeの例となります

import { extendTheme } from "@chakra-ui/react"
import { createBreakpoints } from "@chakra-ui/theme-tools"

const colors = {
  primary: {
    main: "#33A668",
  },
  secondary: {
    main: "#F6DA6B",
  },
  warning: {
    main: "#D4A367",
  },
  text: {
    main: "#212161",
  },
  grey: {
    dark: "#757565",
    main: "#BDBD6D",
    light: "#E0E6E0",
  },
  background: {
    main: "#F7F767",
    dark: "#C4C464",
    paper: "#fff6ff",
  },
}

const breakpoints = createBreakpoints({
  sm: "30em",
  md: "48em",
  lg: "62em",
  xl: "80em",
  "2xl": "96em",
})

const textStyles = {
  h1: {
    fontSize: "24px",
    lineHeight: "34.75px",
    fontWeight: "700",
    color: "#212121",
  },
  h4: {
    fontSize: "16px",
    fontWeight: "700",
    lineHeight: "23.17px",
    letterSpacing: "2%",
    color: "#212121",
  },
  p: {
    fontSize: "16px",
    lineHeight: "23.17px",
    fontWeight: "400",
    letterSpacing: "2%",
    color: "#212121",
  },
  placeholder: {
    fontSize: "16px",
    lineHeight: "23.17px",
    fontWeight: "400",
    letterSpacing: "2%",
    color: "#BDBDBD",
  },
}



export const theme = extendTheme({
  colors,
  breakpoints,
  textStyles,
})

使いたくなってきた?

長々と事前知識を書いてきましたが、どうでしょうか。Utility-Firstなライブラリを使ってみたくなったのではないでしょうか。

今回はUtility-Firstに対応しているライブラリのうち三つを紹介して比較検討してみようと思います。

  1. TailwindCSS
  2. Chakra UI
  3. MUI(旧Material UI)

選定基準としてはUtility-Firstといえばコレ!というライブラリと、地味に最近Utility-Firstぽいことをより簡単にできるようになったMUIも含めてみました

比較の基準としては、

  1. 汎用性
  2. 効率性
  3. 人気度

の三つの観点から考えてみます。

TailwindCSS

Utility-Firstといえばコレ!というCSSフレームワークです
https://tailwindcss.com/

特徴としてはクラスとCSSの要素が一対一の関係になっているところです。
ここまでの導入だけだと、何を言ってるんだとなると思いますが、後述の二つのフレームワークではReactのPropsでCSS適用を管理しているため、クラスとしては一つの形でoutputされます。

Tailwind CSSでは一つのクラスにつき一つのCSS要素ですので、chromeの検証などで確認する際に、大量のクラスに少しの要素が適用された状態で表示されます。これは見づらい場合もあると思います。

また、UIコンポーネントのライブラリではないため、特にあらかじめUIが用意されているわけではないというのも特徴でしょうか。そのため汎用性はある意味無限大だともいえます。しかし、逆に効率性の観点からは1からコンポーネントを作り上げる必要があるため微妙かもしれません。
さらにいうと、クラス名が独特で覚えるのにコストがかかる+vscodeでのクラス名の補完がうまく表示された場合が多かったのでその辺りも効率性の観点からは微妙です。

人気度の観点はどうでしょうか。GithubのStar数を確認してみると49.7kとなっています。他二つのStar数を見てからこちらは判断となります。

Chakra UI

これまたナウいThe Utility-Firstという感じのUIコンポーネントライブラリです。こちらは前述のTailwindCSSとは違って、クラスではなくコンポーネントを提供するUtility-Firstなライブラリとなっています。
https://chakra-ui.com/

ChakraUIの特徴としては、Propsを用いてUtility-Firstを実現する点です。

import { Box, Button } from '@chakra-ui/react'
  <Box m={4}>
    <Button colorScheme="green" w="386px">Chakra UIのボタン</Button>
  </Box>

class="~~"という表記ではなく、ReactのPropsがStyle Propsとしてあらかじめ用意されていて、それに代入する形でCSSの値を設定していきます。

class名ではなくPropsで入力することの良いところは、IDEの補完がしっかりと効くところでしょうか。また、Typescriptによる型補完もしっかり効いているため、間違った値を入れることはあまりないです。

Chakra UIは汎用性の観点からはTailwindに比べると当然ですが劣っています。また、個人的に気になったのは、MUIなどでは実装されているようなコンポーネントがChakra UIだとない場合があることです。例えばPaginationやDatePickerがコンポーネントとしてはありません。派生ライブラリや自作する必要があるので、汎用性の観点からは少し劣る部分がありそうです。

効率性はどうでしょうか。こちらはTailwindCSSに比べると、元からコンポーネントにある程度スタイルが当てられている点から、明らかに効率が上がっていることがわかりますね。また、クラスも一つにまとめられているので、検証の際の視認性も良いと思います。
強いていうなら、py-4などを指定したときにCSS Variablesを使用しているために値自体は検証に表示されないのが、学習コストが嵩む分若干見づらいところはあります。

人気度の観点は Githubのstar数は22.5KとTailwindCSSより半分ほど少ないですね。

MUI (Material UI)

最後はMaterial UIから最近改名したMUIです。実は普段の業務ではMUIを使うことが多いです。
こちらもChakra UIと同じく、ReactのUIコンポーネントを提供しているライブラリです。
https://mui.com/

こちらの特徴としては、Material Designに基づいた豊富なコンポーネント数があると思います。
普段使っていてコンポーネントの種類で物足りなくなることがあまりないです。
また、Chakra UIと違って、sxというPropsでUtility-Firstな実装をすることができます。

import { Box as MuiBox, Button as MuiButton } from '@mui/material'

  <MuiBox m={4}>
    <MuiButton
      sx={[{
        color: 'white',
        backgroundColor: 'primary.main',
        width: '386px',
        borderRadius: '6px',
      },
      {
        '&:hover': {
        backgroundColor: 'secondary.main'
        }
      }
    ]}>
      MUIのボタン
    </MuiButton>
  </MuiBox>

このようにsxというPropsの中でCSSプロパティを指定します。
sxはMUI v5から追加された機能ですが、使用のタイミングは少しのスタイルカスタマイズや1回きりのスタイル定義で使ってほしいようです。パフォーマンスも他のMUIでの記法に比べると劣るところがあるようです。

では、一回きりではなく、共通で使用したいプロパティはどうすればいいのでしょうか。
MUIはこの場合styled()の記法を用いて、オリジナルのコンポーネントをアップデートすることが推奨されています(他にもv4までは一般的だったmakeStylesを使用した記法もありますが、v5からは非推奨となっています)
先ほどのボタンの例では、

muiComponents/index.tsx

import React from 'react';
import Button, { ButtonProps } from '@mui/material/Button';
import { styled } from '@mui/system';

interface IButtonProps extends ButtonProps {}

const CustomButton = (props: IButtonProps): JSX.Element => (
  <Button {...props}>{props.children}</Button>
);

export const StyledButton = styled(CustomButton)({
    backgroundColor: 'green'
});

pages/index.tsx

    <MuiBox m={4}>
       <StyledButton
          sx={[{
            color: 'white',
            width: '386px',
            borderRadius: '6px',
          },
          {
            '&:hover': {
            backgroundColor: 'primary.main'
            }
          }
       ]}>
         StyledのMUIのボタン
       </StyledButton>
    </MuiBox>

このようにStyledを用いてあらかじめstyleを適用させておくこともできます。

ではMUIの汎用性について考えてみましょう。こちらはTailwindCSSよりは劣りますがChakra UIよりはコンポーネントの種類で上回っていると考えられます。
効率性に関しては、コンポーネントなのでChakra UIと同程度に効率は良いと考えられます。
また、defaultでcss variablesを使っているわけではないので(使用することはできます)視認性も優れていると思います。
懸念点としては、一つのコンポーネントにいくつものデフォルトのクラスがまとまって存在しているので若干分かりづらいところがあると思います。

人気度に関してはどうでしょうか。MUIはstar数73.4Kと最大規模となっています。

ちなみに、githubのstar数だけでなく、google trendで調べてみるとこのようになりました。

最近Tailwindが流行っていることがわかりますね。

まとめ

調査結果をまとめるとこんな感じになりました。

汎用性 効率性 人気度
TailwindCSS
Chakra UI
MUI

これをみるとUtility-FirstでやりたいならMUI一択かのように見えますね()
筆者の熟練度としてはMUI > Chakra UI > TailwindCSSなので、Chakra UIやTailwindCSSに関して考慮できていない点があるかもしれませんが、その際はコメントで教えていただけますと幸いです。

なお、本記事のコードが書いてある+これら三種類のフレームワークを初期設定なしで使用できるように設定したリポジトリを作りましたので、興味を持たれた方がいたらぜひこちらのリポジトリをクローンしてお試しください。
https://github.com/go-to-the-future/utility-first-example-app

Discussion