💬

Next.js、Mantine、Tailwind CSSを一緒に使ってみたらボタンが表示されない問題の解決方法を少し工夫してみた

2022/05/25に公開約15,200字

📌 きっかけ

Next.js + Mantine + Tailwind CSSを組み合わせて開発をしていて、久々にnpmパッケージのバージョンアップをしたところ、Tailwind CSSのリセットCSSにMantineのボタンコンポーネントのスタイルが打ち消されて消えてしまう以下の画像のような問題に遭遇しました

img
引用元:
Next.js、Mantine、Tailwind CSSを一緒に使ってみたらボタンが表示されない問題を解決

幸い、この問題が発生する可能性があることを知人から聞いていたため、以下の記事を参考にしながら無事解消することができました!

https://zenn.dev/elletech/articles/mantine-tailwindcss

ただ、Tailwind CSSを拡張していない場合は、今回の対応のためだけにglobals.cssを用意しないといけないため、他の方法がないか検証してみましたのでその内容を記事にまとめました!

📋 原因と対処方法

原因

上記サイトの通り、Tailwind CSSのリセットCSSとMantineが競合してしまう
(buttonのbackground-colorにtransparentが当たってしまう)

具体的な調査内容をご覧になりたい方はこちら

実際に適用されているスタイルを確認したところ、
以下のHTML要素にtransparentが設定されていました。

Buttonコンポーネント
<button class="mantine-Button-filled mantine-Button-root mantine-1nfplx1" type="button">
  <div class="mantine-3xbgk5 mantine-Button-inner">
    <span class="mantine-qo1k2 mantine-Button-label">test</span>
  </div>
</button>

そのため、background-colorが設定されているスタイルを確認したところ...

Tailwind CSSのリセットCSSのクラス定義
button, [type='button'], [type='reset'], [type='submit'] {
    -webkit-appearance: button;
    background-color: transparent;
    background-image: none;
}

button[type='button']が該当するため、詳細度0.0.1.1

Mantineのクラス定義
.mantine-1nfplx1 {
    border: 1px solid transparent;
    background-color: #228be6;
    background-image: #228be6;
}

クラス名のみのため詳細度0.0.1.0

リセットCSSの方が詳細度高いww

Tailwind CSSのリセットCSSのクラス定義を変更
button {
    -webkit-appearance: button;
    background-color: transparent;
    background-image: none;
}

input[type='button'], input[type='reset'], input[type='submit'] {
    -webkit-appearance: button;
    background-color: transparent;
    background-image: none;
}

[type='button']だとclassが詳細度が同じため、
試しにbuttonタグとinputタグの一部のtypeにリセットCSSのスタイルを設定し、
Mantineとinputタグを配置し動作を確認

<Button size="lg">test</Button>
<input type="button" className="bg-gray-400" value="テストボタン" />

Mantineはうまく動作しましたが、inputタグのTailwindのスタイルが適用されなくなりました...

そのため、Tailwindの優先設定を行ったところ、inputタグでもスタイルが適用されるようになったため、今回の対応内容を共有することにしました!

対策

つまり、Mantineのコンポーネントに対しては、Tailwind CSSのリセットCSSをかけないようにするだけで対策としては十分では?!という発想に行きつきました。(2022.6.1 更新)

Mantineのようにbutton[type='button']が一緒に設定されている場合、
リセットCSSが優先されてしまうため、buttoninput[type='button']のスタイル設定を
分離してしまえばいいのではないかと仮定しました!

その上で、Tailwind CSSのスタイルが優先されるように設定すれば、意図した挙動をするのではないかと思い、今回の対策を実施してみました!

他にもいろいろなサイトやコメントを参照するとTailwind CSSのリセットCSSを無効化する方法や、リセットCSSを無効化した上で、MantineProviderのwithGlobalStyles withNormalizeCSSプロパティを追加する方法もあることもわかりました。

現状の問題点

Tailwind CSSのリセットCSSを無効化する方法 -> 🙆‍♂️ 採用

Tailwind CSSのリセットCSSはmodern-normalizeを適用していてh1~h6タグなどのサイズなども完全にリセットしてくれるため、Tailwind CSSを使う上では残しておきたいため、globals.cssを使用しない場合に対応するためにデフォルトのリセットCSSを無効化するための手段として採用することにしました!

https://github.com/sindresorhus/modern-normalize

また、MantineのスタイルをTailwind CSSで上書きしたいパターンが多いため、
!importantを追加するより権限が緩くなるTailwind CSSで定義されている全てのクラスセレクタに#app#__nextを追記する以下の方法を採用することでスタイルが崩れたとしても修正しやすいようにすることにしました!

https://tailwindcss.com/docs/configuration#selector-strategy

MantineのNormalizeCSSを活用する方法 -> 不採用

こちらの方法は、MantineのバージョンアップによってTailwind CSSへの影響が出てしまう可能性があるため、今回は不採用としました。

Tailwind CSS のリセットCSSを上書きする方法 -> 🙆‍♂️ 変更点を見直し採用

既存の記事では、buttonの設定を直接書き換えていますが、今回影響が発生しているのは、Mantineのみのため、他のライブラリなどへの影響も考慮すると不必要に設定を削除する必要はないのでは?と今回の対処では影響が出ている箇所の対策のみに絞ることにしました!

対処方法

1. Tailwind CSSのリセットCSS(@tailwind base)の無効化

通常、Tailwind CSSでは、デフォルトの設定から機能拡張を行う際は、
globals.cssを用意して書き換えるパターンが多いと思いますが、
今回はその方法以外でも対応可能な方法で対策を行います。

まずは、tailwind.config.jsにリセットCSS(@tailwind base)の無効化用設定を追加

importantに設定を追加することで、Tailwind CSSのクラス定義全てに指定されたセレクタを追加して詳細度を上げることで優先的にスタイルを上書きできるようにします。

importantの設定については好みや作業環境によって変わってくると思いますので、必要に応じて設定してください!

  • リセットCSSの無効化とTailwind CSSの優先設定
tailwind.config.js
module.exports = {
  mode: 'jit',
  purge: ['./src/**/*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {},
  variants: {},
+  corePlugins: {
+    preflight: false, // リセットCSSの無効化
+  },
+ important: '#__next', // 全てのクラスセレクタの先頭に '#app' または '#__next'を追加する
  plugins: [],
};

https://tailwindcss.com/docs/preflight#disabling-preflight
ちなみにglobals.cssの記述方法についてはこちら

このファイルを直接編集することでTailwind CSSのテーマやスタイルを拡張することができる

styles/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
`important: true`の存在にお気づきの方はこちら

今回のimportantの設定を追加した場合とそうでない場合では、
以下のようにTailwind CSSのクラスの設定が変わることで詳細度の計算結果を調整することができます。

https://zero-plus.io/media/html-css/selector/

そのため、結果的に他のライブラリよりもTailwind CSSのスタイルが優先的に設定され、
インラインスタイルは上書きしない状態にできます!

  1. importantの設定をしていない場合
.w-full {
    width: 100%;
}
  1. importantidセレクタを設定した場合
tailwind.config.js
module.exports = {
+ important: '#__next', // 全てのクラスセレクタの先頭に '#app' または '#__next'を追加する
};
#__next .w-full {
    width: 100%;
}
  1. importantにtrueを設定した場合
tailwind.config.js
module.exports = {
+ important: true, // クラスの全てのスタイルに!importantを追加する
};
.w-full {
    width: 100% !important;
}

上記をまとめると以下の表のようになります

対応内容 詳細度 備考
importantの設定をしていない場合 0.0.1.0 他のクラス定義と容易に競合してしまう
importantidセレクタを設定した場合 0.1.1.0 ← 優先にしつつ上書きしやすい設定値
importantにtrueを設定した場合 1.0.1.0 インラインスタイルまで上書きしてしまう
インラインスタイル 1.0.0.0 参考値
MantineやTailwind 標準設定 0.0.1.0 参考値

このことからidセレクタを設定する方法で実装することにしました!

2. Mantine用にリセットCSSを編集したCSSファイルを準備

Tailwind CSSの標準のリセットCSSを以下のように変更し、_app.jsへのインポート用に別ファイルを作成、保存する

以下のコードは変更点のみ(2022.6.1に更新)

styles/mantineBase.css

+ @layer mantineBase {
  /*
  1. Correct the inability to style clickable types in iOS and Safari.
  2. Remove default button styles.
  */

  /* 
  Default button styles
  */

-   button,
-   [type='button'],
-   [type='reset'],
-   [type='submit'] {
-     -webkit-appearance: button;
-     background-color: transparent;
-     background-image: none;
-   }

  /* 
  Mantine button styles
  */
+   button {
+     -webkit-appearance: button; /* 1 */
+     background-color: transparent; /*  2 */
+     background-image: none; /*  2 */
+   }

+   input[type='button'],
+   input[type='reset'],
+   input[type='submit'] {
+     -webkit-appearance: button; /* 1 */
+     background-color: transparent; /*  2 */
+     background-image: none; /*  2 */
+   }

+ }

実際に作成する際は以下のコードをコピーして作成してください!

mantineBase.cssの全体
styles/mantineBase.css
@layer mantineBase {
  /*
  1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
  2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
  */

  *,
  ::before,
  ::after {
    box-sizing: border-box; /* 1 */
    border-width: 0; /* 2 */
    border-style: solid; /* 2 */
    border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */
  }

  ::before,
  ::after {
    --tw-content: '';
  }

  /*
  1. Use a consistent sensible line-height in all browsers.
  2. Prevent adjustments of font size after orientation changes in iOS.
  3. Use a more readable tab size.
  4. Use the user's configured `sans` font-family by default.
  */

  html {
    line-height: 1.5; /* 1 */
    -webkit-text-size-adjust: 100%; /* 2 */
    -moz-tab-size: 4; /* 3 */
    tab-size: 4; /* 3 */
    font-family: theme(
      'fontFamily.sans',
      ui-sans-serif,
      system-ui,
      -apple-system,
      BlinkMacSystemFont,
      'Segoe UI',
      Roboto,
      'Helvetica Neue',
      Arial,
      'Noto Sans',
      sans-serif,
      'Apple Color Emoji',
      'Segoe UI Emoji',
      'Segoe UI Symbol',
      'Noto Color Emoji'
    ); /* 4 */
  }

  /*
  1. Remove the margin in all browsers.
  2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
  */

  body {
    margin: 0; /* 1 */
    line-height: inherit; /* 2 */
  }

  /*
  1. Add the correct height in Firefox.
  2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
  3. Ensure horizontal rules are visible by default.
  */

  hr {
    height: 0; /* 1 */
    color: inherit; /* 2 */
    border-top-width: 1px; /* 3 */
  }

  /*
  Add the correct text decoration in Chrome, Edge, and Safari.
  */

  abbr:where([title]) {
    text-decoration: underline dotted;
  }

  /*
  Remove the default font size and weight for headings.
  */

  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    font-size: inherit;
    font-weight: inherit;
  }

  /*
  Reset links to optimize for opt-in styling instead of opt-out.
  */

  a {
    color: inherit;
    text-decoration: inherit;
  }

  /*
  Add the correct font weight in Edge and Safari.
  */

  b,
  strong {
    font-weight: bolder;
  }

  /*
  1. Use the user's configured `mono` font family by default.
  2. Correct the odd `em` font sizing in all browsers.
  */

  code,
  kbd,
  samp,
  pre {
    font-family: theme(
      'fontFamily.mono',
      ui-monospace,
      SFMono-Regular,
      Menlo,
      Monaco,
      Consolas,
      'Liberation Mono',
      'Courier New',
      monospace
    ); /* 1 */
    font-size: 1em; /* 2 */
  }

  /*
  Add the correct font size in all browsers.
  */

  small {
    font-size: 80%;
  }

  /*
  Prevent `sub` and `sup` elements from affecting the line height in all browsers.
  */

  sub,
  sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }

  sub {
    bottom: -0.25em;
  }

  sup {
    top: -0.5em;
  }

  /*
  1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
  2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
  3. Remove gaps between table borders by default.
  */

  table {
    text-indent: 0; /* 1 */
    border-color: inherit; /* 2 */
    border-collapse: collapse; /* 3 */
  }

  /*
  1. Change the font styles in all browsers.
  2. Remove the margin in Firefox and Safari.
  3. Remove default padding in all browsers.
  */

  button,
  input,
  optgroup,
  select,
  textarea {
    font-family: inherit; /* 1 */
    font-size: 100%; /* 1 */
    line-height: inherit; /* 1 */
    color: inherit; /* 1 */
    margin: 0; /* 2 */
    padding: 0; /* 3 */
  }

  /*
  Remove the inheritance of text transform in Edge and Firefox.
  */

  button,
  select {
    text-transform: none;
  }

  /*
  1. Correct the inability to style clickable types in iOS and Safari.
  2. Remove default button styles.
  */

  /* 
  Default button styles

  button,
  [type='button'],
  [type='reset'],
  [type='submit'] {
    -webkit-appearance: button;
    background-color: transparent;
    background-image: none;
  } */

  /* 
  Mantine button styles
  */

  button {
    -webkit-appearance: button; /* 1 */
    background-color: transparent; /*  2 */
    background-image: none; /*  2 */
  }

  input[type='button'],
  input[type='reset'],
  input[type='submit'] {
    -webkit-appearance: button; /* 1 */
    background-color: transparent; /*  2 */
    background-image: none; /*  2 */
  }

  /*
  Use the modern Firefox focus style for all focusable elements.
  */

  :-moz-focusring {
    outline: auto;
  }

  /*
  Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
  */

  :-moz-ui-invalid {
    box-shadow: none;
  }

  /*
  Add the correct vertical alignment in Chrome and Firefox.
  */

  progress {
    vertical-align: baseline;
  }

  /*
  Correct the cursor style of increment and decrement buttons in Safari.
  */

  ::-webkit-inner-spin-button,
  ::-webkit-outer-spin-button {
    height: auto;
  }

  /*
  1. Correct the odd appearance in Chrome and Safari.
  2. Correct the outline style in Safari.
  */

  [type='search'] {
    -webkit-appearance: textfield; /* 1 */
    outline-offset: -2px; /* 2 */
  }

  /*
  Remove the inner padding in Chrome and Safari on macOS.
  */

  ::-webkit-search-decoration {
    -webkit-appearance: none;
  }

  /*
  1. Correct the inability to style clickable types in iOS and Safari.
  2. Change font properties to `inherit` in Safari.
  */

  ::-webkit-file-upload-button {
    -webkit-appearance: button; /* 1 */
    font: inherit; /* 2 */
  }

  /*
  Add the correct display in Chrome and Safari.
  */

  summary {
    display: list-item;
  }

  /*
  Removes the default spacing and border for appropriate elements.
  */

  blockquote,
  dl,
  dd,
  h1,
  h2,
  h3,
  h4,
  h5,
  h6,
  hr,
  figure,
  p,
  pre {
    margin: 0;
  }

  fieldset {
    margin: 0;
    padding: 0;
  }

  legend {
    padding: 0;
  }

  ol,
  ul,
  menu {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  /*
  Prevent resizing textareas horizontally by default.
  */

  textarea {
    resize: vertical;
  }

  /*
  1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
  2. Set the default placeholder color to the user's configured gray 400 color.
  */

  input::placeholder,
  textarea::placeholder {
    opacity: 1; /* 1 */
    color: theme('colors.gray.400', #9ca3af); /* 2 */
  }

  /*
  Set the default cursor for buttons.
  */

  button,
  [role='button'] {
    cursor: pointer;
  }

  /*
  Make sure disabled buttons don't get the pointer cursor.
  */
  :disabled {
    cursor: default;
  }

  /*
  1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
  2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
    This can trigger a poorly considered lint error in some tools but is included by design.
  */

  img,
  svg,
  video,
  canvas,
  audio,
  iframe,
  embed,
  object {
    display: block; /* 1 */
    vertical-align: middle; /* 2 */
  }

  /*
  Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
  */

  img,
  video {
    max-width: 100%;
    height: auto;
  }

  /*
  Ensure the default browser behavior of the `hidden` attribute.
  */

  [hidden] {
    display: none;
  }
}


3. _app.jsに作成したリセットCSSをインポート

Tailwind CSSのクラスをインポートする前に作成したリセットCSSをインポート

pages/_app.js
+ import 'styles/mantineBase.css';

// Tailwind CSSを拡張する必要がない場合
import 'tailwindcss/tailwind.css';

// globals.cssを作成して読み込んでいる場合
import 'styles/globals.css';

✏️ まとめ

こちらの記事を検証した結果としては、sugayutokyoさんの記事の結果と一緒になりますので、あまり参考にならないかもしれませんが、複数ある対策方法のひとつとして参考にしていただければ嬉しいです!

また、CSSの知識が浅いため、より簡単に実装できるCSSのカスケードレイヤーの上書き方法などがあればコメント頂けるととても嬉しいです!

拙い文章でしたが最後までお付き合いいただきありがとうございました!

GitHubで編集を提案

Discussion

ログインするとコメントできます