虎の穴ラボ技術ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

Tailwind CSS と Atomic Design で実現する効率的な Web 開発の事例

この記事は虎の穴ラボ Advent Calendar 2024の 20 日目の記事です。
こんにちは、虎の穴ラボの古賀です。

今回の記事は React や Vue などモダンな環境に対する Tailwind CSS や Atomic Design の導入の知見が得られる内容となっていますので、よろしければご覧ください。

目次

はじめに

とらのあなのサークルポータルは、さらなる利便性の向上と優れたユーザ体験をとらのあなユーザにお届けするために2024 年 4 月 24 日に全体的なリニューアルを行いました。

その際にフロントエンドのコンポーネント設計手法として Atomic Design、CSSのフレームワークとして Tailwind CSS を採用することで効率的な開発を実現しましたが、その背景や実際の開発ガイドラインなどについて今回の記事にまとめています。

採用理由

それぞれのCSSフレームワークや設計手法を選んだ理由としては、次のとおりです。

Tailwind CSS

新しいサークルポータルのデザインは優れたユーザ体験をお届けしようとすると、特化した UI が必要になることが事前の調査で分かっていました。
そのため、既存の CSS や UI のフレームワークではなく、カスタマイズされたオリジナルのUIが作りやすいTailwind CSS を採用しました。Tailwind CSS は任意のデザイントークンが作りやすいので、Atomic Design や特化した UI との相性が良いという点も採用理由の 1 つです。

たとえば、最近リリースされた「下書き機能」や「やることリスト機能」の UI も Tailwind CSS で作成しています。

下書き保存された作品のイメージ

やることリスト機能のイメージ

その他の React や Next.js を選定した経緯は次の資料にまとめていますので、よろしければご覧ください。

speakerdeck.com

Atomic Design

Atomic Design は、一番の目的は開発メンバーとデザイナーさんの連携を強化するために採用しました。
エンジニアが独自にコンポーネントを作成してしまうと、デザイナーさんが編集できないコードになってしまいます。そのため、Atomic Design を採用し、デザイナーさんとエンジニアが同じ言語で効率よくコミュニケーションを取れるように導入しました。 (虎の穴ラボのデザイナーの一部は、React や JSX のコードを改修してデザインを調整することがあります。)

その他の狙いとしては、次の 4 つがあります。

  1. 保守性と拡張性を良くするため
  2. 一定のルールに則るのでコードが読みやすくなる
  3. 影響範囲が循環やスパゲティ的に複雑に絡み合わないように考慮しやすい
  4. 開発効率を良くするため
  5. 再利用可能な UI 要素を作成する → 他の場所でも利用できる(他のシステムでも)
  6. コードの重複が削減され、新しい機能の開発時間が短縮される
  7. ユニットテストを書きやすくする
  8. 独立したコンポーネントとなり、それぞれ個別にテストできる
  9. チーム開発を円滑にすすめるため
  10. 開発者が標準的なガイドラインにしたがって作業できる、共通言語ができる
  11. コンポーネント分割をする方法に困らない

Atomic Design を進めるにあたって、ガイドラインを作成し、Atomic Design のルールをチーム全体で共有しました。その結果、Atomic Design の運用がスムーズに進み、開発効率が向上しました。

ガイドラインについては次の「虎の穴ラボの薄い本。vol.6」の「Atomic Design を実践してみよう!」にまとめていますので、よろしければご確認ください。

techbookfest.org

開発ガイドライン

開発を進めるにあたって Atomic Design と Tailwind CSS について、詳細な開発のガイドラインも作成しました。
次にその一部を抜粋します。

コンポーネント単位の外部 CSS は作らない

CSS は Tailwind CSS でコンポーネントに className で書き、HTML と CSS を分離しない。

HTML と CSS を分離するとレビューのときや修正のときに UI の全体像が見えにくくなります。そのため、コンポーネント単位でまとめて書くことで、コンポーネントの全体像が見えやすくなり、HTML と CSS を合わせてイメージし易くなります。

global.css には基本何も書かない

global.css に書くのは、次のような例外パターンのみです。基本は @import でファイル分割して書いています。

  • 全体に適用する必要がある日本語フォントの設定
  • 一部の UI ライブラリやフレームワーク(datepicker など)の CSS の上書き
  • 既存レガシーシステムからの移行で Tailwind CSS への変換が難しい(難易度が高い、工数がかかる)もの

カスタムコンポーネントに className 属性を使わない

コンポーネントの Props に React の className は追加しないようにし、色やサイズなどを TypeScript 化して型安全にトークン化します。

export type Size = "lg" | "md" | "sm" | "xs" | "2xs" | "none"; // 例外:noneは値を指定しない、親から例外的にTailwind CSSの任意のセレクタで指定したいときに使います

export const Button = memo(function Button({
  size = "md",
}: Props): JSX.Element {
  // 省略
  return (
    <button
      className={clsx(
        `rounded-full font-bold tracking-wide no-underline`,
        size === "lg" && `h-auto min-h-0 px-8 py-5 text-lg leading-none`,
        size === "md" && `h-auto min-h-0 px-6 py-3.5 text-base`,
        size === "sm" && `h-auto min-h-0 px-4 py-2.5 text-sm`,
        size === "xs" && `h-auto min-h-0 px-3 py-1.5 text-xs`,
        size === "2xs" && `h-auto min-h-0 px-2 py-1 text-2xs`
      )}
    >
      {children}
    </button>
  );
});

任意の値は制限しない

Tailwind CSS の任意の値は嫌われることもありますが、効率的に開発を行うために前向きに向き合うことが重要です。

任意の値を積極的に使用し解説付きでメンバーにコードレビューを依頼し、またメンバーからのコードレビューの依頼に対して任意の値を使った改善提案を積極的に行うことで、全体への浸透を図りました。

次のような任意の値は記述が特殊なのでレビュー時にも気づきやすいですし、React や Vue などの JavaScript で動的に作った場合と比べてブラウザのネイティブな部分で動くのでパフォーマンスの面で有利です。
ちなみにTailwind CSS は事前ビルドですべての CSS を作成するので、動作時に JavaScript で動的に CSS を生成することはありません。

書き方 効果
[&>*]:block 子要素に対してのみ block が適用される
checked:[--tglbg:theme(colors.gray.500)] チェックボックスなどをチェックした場合のみ、CSS 変数が適用される
before:content-[attr(data-checked-message)] before の content に data-checked-message 属性の値を入れる
md:[scrollbar-color:theme(colors.primary.900)_theme(colors.transparent)] ウィンドウサイズが md 以上の場合のみスクロールバーの色を変える

tailwind.config.js について

tailwind.config.js は theme にデザイナーさんの Figma のデザイントークンのみを設定します。

  • colors、fontSize、textColor、screens はデザイナーさんの Figma に合わせる。デザイントークンがあればそれに、なければ Figma から近い値を設定していく
    • まだ、固まってなかったら徐々に固めていく
      • デザイナーさんと協力していく
    • どうしても許容できない値や相対の位置指定(fixed、relative の位置など)は任意の値で rem か px で指定
  • animation、keyframes
    • デザイン段階でわかっているものを入れる
    • 基本、トランジション、アニメーションで作って、複雑なアニメーションが必要な場合は別途検討
/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      fontSize: { "3xl": "2rem", "2xs": "0.6875rem", "3xs": "0.625rem" },
      colors: {
        gray: {
          50: "#F7F7F7",
          100: "#F1F1F1",
          200: "#EBEBEB",
          300: "#E0E0E0",
          400: "#CCCCCC",
          500: "#AEAEAE",
          600: "#777777",
          700: "#444444",
          800: "#222222",
          900: "#000000",
        },
        // 他の色も同様に設定
      },
      textColor: {
        mono: {
          black: "#444444",
          white: "#FFFFFF",
        },
        link: "#0077E6",
      },
      stroke: {
        mono: {
          black: "#444444",
          white: "#FFFFFF",
        },
        link: "#0077E6",
      },
      fill: {
        mono: {
          black: "#444444",
          white: "#FFFFFF",
        },
        link: "#0077E6",
      },
      screens: {
        xs: "360px",
      },
      animation: {
        "slide-right": "slide-right 10s linear 1s infinite",
        "slide-left": "slide-left 10s linear 1s infinite",
        fadeIn: "0.5s ease-in-out 0s 1 normal both running fadeIn",
        fadeOut: "0.5s ease-in-out 0s 1 normal both running fadeOut",
      },
      keyframes: {
        "slide-right": {
          from: { transform: "translateX(0%)" },
          to: { transform: "translateX(-380%)" },
        },
        "slide-left": {
          from: { transform: "translateX(380%)" },
          to: { transform: "translateX(0%)" },
        },
        fadeIn: {
          "0%": { opacity: 0, visibility: "hidden" },
          "100%": { opacity: 1, visibility: "visible" },
        },
        fadeOut: {
          "0%": { opacity: 1, visibility: "visible" },
          "100%": { opacity: 0, visibility: "hidden" },
        },
      },
    },
  },
};

基本はエンジニアだけで追加してしまうとデザイン的に意図しないものができてしまうので、デザイナーさんと協力して設定していくことが重要です。

開発環境のTips

開発の際には、Tailwind CSS 向けに開発環境を整えました。その中から一部を紹介します。

Tailwind CSS 公式の Prettier ルール

  • className を自動で並び替えることで、長い className が見やすく
    • 並び順は一言で表すのが難しいので詳しくは省きますが、一定のルールで並び替えされる

github.com

Inspect - Export to HTML, React, TailwindCSS プラグイン

  • Figma のデザインを JSX のコードで取得するために利用
    • 自動生成で粗いコードのこともありますが、基本的な構造は作成できるため初期のコンポーネント作成が楽に
    • エンジニアが無料プランでも自分の編集可能なプロジェクトにコピーすることで利用可能

https://www.figma.com/community/plugin/1049994768493726219/

まとめ

今は初期リリースの開発が終わって新機能の開発を行なっている状況ですが、フロントエンド周りの開発運用は次のアンケートのようにだいたい成功しています。

今後もリニューアル系のプロジェクトがあれば Atomic Design と Tailwind CSS を活用して、より効率的な開発を進めていきたいと思います。

アンケート結果

効果測定的に開発メンバーやデザイナーへ開発工数の面でアンケートを取り、次のような結果が得られました。アンケート結果からは、想定した効果が得られていることが確認できました。一方、説明不足による課題も見つかったため、今後改善していきたいと考えています。

開発工数的に良かった点

  • コンポーネントの再利用がしやすい
  • global.css から望んでいる style があたった class を探さなくても良い
  • AtomicDesign を採用したことで、実装の分担もしやすかった
  • Tailwind CSS は簡単ですんなり実装に入っていけました
    • クラス名を考える手間が省けるのも助かりました
  • Tailwind CSSは慣れてしまえばstyleを書くようにクラスを当てられるのでcssを構築するより断然楽
  • Figmaのデザインからクラスを抽出できるのである程度その通りに実装してからレスポンシブに調整できる
  • Atomic Designは新規参入してもパーツが一覧となっており、デザインパーツの学習コストは低かったのではないかと思う

開発工数的に悪かった点

  • Tailwind CSS の任意の値が難しい
  • Atomic Design のコンポーネントの分け方の正解が難しい
    • organism が多くなりがち
    • atoms or molecules どっちがただしい?
  • 通常の CSS と比べて記述量自体は増えている気がする
  • デザインの問題でもありますが、コンポーネントに引数増やしすぎて管理が大変
  • デザイン全体を把握していないとどこまでコンポーネントにして良いか判断するのが難しかった
  • Atoms, Molecules, Organisms, Templates, Pagesのどれに属するか分解して考えることに慣れておらず、難しさを感じた

他のシステムの CSS 運用と比較して

  • xxx(他のレガシーなシステム)よりもずっと開発しやすい
  • エンジニアが CSS も書く現場では使いやすい、デザイナーのみが書く現場では React も覚える必要がでて学習コストが上がったりするかもしれない
  • Bootstrapを使い慣れていると、Tailwindの方が自由度が高くて便利と感じる面と、コンポーネント化がされていなくてスタイルを当てるのが大変だなという面があった
  • 機能や表示に例外が多くてコンポーネントでやりくりするにはかなり無理がある部分も多かったのかなと思う

採用情報

虎の穴ラボでは一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧ください。
c Kotlin / Next.js / TypeScript /Tailwind CSS 等、モダン環境でのシステム開発に携わることができます。
toranoana-lab.co.jp