😸

Chakra-UIのEmotionで生成されるCSSをレイヤーに入れてみる

2024/10/01に公開

はじめに

業務でわけあってChakra-UIPanda CSSを併用することになりました。

もともとPanda CSSをJSXスタイルでない形で書いていたため、JSXスタイルに統一するにも移行作業もそれなりに発生してしまうため、それならレイヤーで囲ってを優先度を下げてみようという試みになります。

調べてみたところ以下のissueが転がっていたので、それを試してみます。
https://github.com/emotion-js/emotion/issues/3134

やってみる

サンプルとしてInputコンポーネントを配置してcssを確認してみます。
CacheProvider@chakra-ui/next-jsからも利用可能ですが、開発環境では同じstyleタグが2度作成され視認性が悪いため、今回は@emotion/reactからエクスポートされているものを利用します。

provider.ts
"use client";

import { ChakraProvider } from "@chakra-ui/react";
import createCache from "@emotion/cache";
import { CacheProvider } from "@emotion/react";

import { type StylisPlugin } from "@emotion/cache";

const wrapInLayer: (layerName: string) => StylisPlugin =
  (layerName) => (node) => {
    // parentを見ている場合、疑似要素等の一部の要素がレイヤーに囲われないことがあります。
    // その場合はissueのコメントでもあるように`node.root`を見るように修正してみてください。
    if (node.root) return;

    const child = { ...node, parent: node, root: node };
    Object.assign(node, {
      children: [child],
      length: 6,
      parent: null,
      props: [layerName],
      return: "",
      root: null,
      type: "@layer",
      value: `@layer ${layerName}`,
    });
  };

const emotionCache = createCache({
  key: "emotion-css-cache",
  stylisPlugins: [wrapInLayer("chakra")],
});

export const Providers = ({ children }: React.PropsWithChildren) => {
  return (
    <CacheProvider value={emotionCache}>
      <ChakraProvider>{children}</ChakraProvider>
    </CacheProvider>
  );
};
page.ts
import { Input } from "@chakra-ui/react";
import { Providers } from "./provider";

export default function Home() {
  return (
    <Providers>
      <Input />
    </Providers>
  );
}

最後にレイヤーの順序を入れ替えれば完成です。
レイヤーの順序はお好みで設定ください。

global.css
@layer reset, base, chakra, tokens, recipes, utilities;

出力

出力されたcssを確認してみると無事レイヤーに囲われていることが確認できます。

出力されたcss
@layer chakra {
  .emotion-css-cache-1cjy4zv {
    width: 100%;
    height: var(--input-height);
    font-size: var(--input-font-size);
    padding-inline-start: var(--input-padding);
    padding-inline-end: var(--input-padding);
    border-radius: var(--input-border-radius);
    min-width: 0px;
    outline: 2px solid transparent;
    outline-offset: 2px;
    position: relative;
    appearance: none;
    transition-property: var(--chakra-transition-property-common);
    transition-duration: var(--chakra-transition-duration-normal);
    --input-font-size: var(--chakra-fontSizes-md);
    --input-padding: var(--chakra-space-4);
    --input-border-radius: var(--chakra-radii-md);
    --input-height: var(--chakra-sizes-10);
    border: 1px solid;
    border-color: inherit;
    background: inherit;
  }
}
@layer chakra {
  .emotion-css-cache-1cjy4zv:disabled,
  .emotion-css-cache-1cjy4zv[disabled],
  .emotion-css-cache-1cjy4zv[aria-disabled="true"],
  .emotion-css-cache-1cjy4zv[data-disabled] {
    opacity: 0.4;
    cursor: not-allowed;
  }
}
@layer chakra {
  .emotion-css-cache-1cjy4zv:hover,
  .emotion-css-cache-1cjy4zv[data-hover] {
    border-color: var(--chakra-colors-gray-300);
  }
}
@layer chakra {
  .emotion-css-cache-1cjy4zv[aria-readonly="true"],
  .emotion-css-cache-1cjy4zv[readonly],
  .emotion-css-cache-1cjy4zv[data-readonly] {
    box-shadow: var(--chakra-shadows-none) !important;
    user-select: all;
  }
}
@layer chakra {
  .emotion-css-cache-1cjy4zv[aria-invalid="true"],
  .emotion-css-cache-1cjy4zv[data-invalid] {
    border-color: #e53e3e;
    box-shadow: 0 0 0 1px #e53e3e;
  }
}
@layer chakra {
  .emotion-css-cache-1cjy4zv:focus-visible,
  .emotion-css-cache-1cjy4zv[data-focus-visible] {
    z-index: 1;
    border-color: #3182ce;
    box-shadow: 0 0 0 1px #3182ce;
  }
}

まとめ

今回はEmotionで出力されるCSSをレイヤーに囲うことで優先度を変更する方法を紹介しました。
少し無理やりな感じですが、応用できそうな箇所がいくつかあるのでお役に立てればうれしいです。

Chakra-UIはv3のプレビュー版が公開中ですが、今のところv2と同じ形式のようですし、以下の記事によると破壊的変更を減らしたいとのことで変わらない可能性が高いかなと感じます。

機能的にはark-uiを取り込んでかなり豊富になっている印象を受けるので、リリースされたら使ってみたいなと思いました。

https://www.adebayosegun.com/blog/chakra-panda-ark-whats-the-plan

https://next.chakra-ui.com

Discussion