🏅

Tailwind CSSを安全に上書きして補完・ソートもできる自作関数を作ってみた

2022/04/23に公開

自己紹介

Toshikiといいます。
普段はNext.js, React Nativeを中心にフロントエンド開発をしています。

こんな悩みありませんか...?

  • Tailwind CSSのクラス名が上書き出来ない時があって困っている
  • コンポーネントの外からTailwind CSSのクラスをあてたい
  • 自作関数の実引数でTailwind CSS Intellisenseで補完を効かせたい
  • 並べ替えもしたい!!

今回は上記の悩みを解決すべく、私なりに調べた結果をまとめようと思います!!

問題の確認(クラス名上書きの失敗例)

// デフォルトで赤色のタイトルを表示するコンポーネント
type TitleProps = {
  className?: string
  children: string
}
const Title: FC<TitleProps> = ({ className, children }) => {
  return (
    <h2 className={clsx(className, 'text-5xl font-bold text-red-500')}>
      {children}
    </h2>
  )
}

// 複数のタイトルを表示するコンポーネント
const TitleListPage: NextPage = () => {
  return (
    <>
      <Title>赤色のタイトル</Title>
      <Title>赤色のタイトル</Title>
      <Title>赤色のタイトル</Title>
      <Title className='text-blue-500'>青色のタイトル</Title>
      <Title>赤色のタイトル</Title>
      <Title>赤色のタイトル</Title>
      <Title>赤色のタイトル</Title>
      <Title>赤色のタイトル</Title>
      <Title>赤色のタイトル</Title>
    </>
  )
}
export default TitleListPage

結果↓↓
失敗例

結果を見て分かる通り、Titleコンポーネントを呼び出す際のtext-blue-500がうまくいっていません。原因としては、元々のtext-red-500text-blue-500が重複しているため、Tailwind CSS側で先に定義されているtext-red-500が優先されてしまっていると考えられます。

解決策

後ろのクラス名を優先して重複を排除してくれるこちらのライブラリを使います!
https://github.com/richardgill/tailwind-override

使い方

example
import { overrideTailwindClasses } from 'tailwind-override'

overrideTailwindClasses('pt-2 pt-4')
// => 'pt-4'

overrideTailwindClasses('text-pink-200 text-blue-200')
// => 'text-blue-200'

overrideTailwindClasses('text-pink-200 pt-2')
// => 'text-pink-200 pt-2' (don't clash)

overrideTailwindClasses('orange apple')
// => 'orange apple' (not tailwind classes)

overrideTailwindClasses('dark:md:text-pink-200 dark:md:text-blue-200')
// => 'md:text-pink-200 md:text-blue-200'

overrideTailwindClasses('text-pink-500 !text-[#ffaa11]/25')
// => '!text-[#ffaa11]/25'

とてもシンプルで分かりやすいですね!!
ついでにclsxの書き心地で書けるようにしておきましょう!

utils/classNames.ts
import clsx, { ClassValue } from 'clsx'
import { overrideTailwindClasses } from 'tailwind-override'

export const cn = (...classNames: ClassValue[]) => {
  return overrideTailwindClasses(clsx(...classNames.reverse()))
}

何回も呼び出すことになるので関数名をcnとしました。
overrideTailwindClassesは後ろのクラス名を優先するので、...classNames.reverse()とすることで前に書いたクラス名を優先するようにしました。

動作確認

先ほどのコードのclsx部分を自作したcnに置き換えます。

一部抜粋
const Title: FC<TitleProps> = ({ className, children }) => {
  return (
-   <h2 className={clsx(className, 'text-5xl font-bold text-red-500')}>
+   <h2 className={cn(className, 'text-5xl font-bold text-red-500')}>
      {children}
    </h2>
  )
}

結果↓↓
成功例

成功しました!検証ツールで開いても綺麗に上書き出来ているのが確認できます。

でもまだ問題が残っています。
cnの呼び出す際の引数にはTailwind Intellisenseもソートも効きません。
これらも解決していきます!


自作関数cnの実引数にTailwind CSSクラスの補完を効かせる

自作関数の実引数にTailwind CSSクラスの補完を効かせるにはsettings.jsonに下記の設定を追加します。
参考Issue
https://github.com/tailwindlabs/tailwindcss/issues/7553#issuecomment-735915659

.vscode/settings.json
{
   "tailwindCSS.experimental.classRegex": [["cn\\(([^)]*)\\)", "'([^']*)'"]]
}

自作関数cnの実引数でソートできるようにする

まず下記のサイトを参考にeslint-plugin-tailwindcssを導入します。
https://morioh.com/p/6d2276c5731c

導入ができたらeslintの設定に下記を追加します。

.eslintrc
{
  "settings": {
    "tailwindcss": {
      "callees": ["classnames", "clsx", "ctl", "cn"],
    }
  }
}

さいごに

今回は備忘録も含めTailwind CSSクラスの上書き、補完、ソートについてまとめてみました!
少しでもこの記事がお役に立てれば嬉しいです!!
Tailwind CSS最高!!!!!

Discussion