LoginSignup
4
3

@scope時代に備えたコンポーネントでのCSSの書き方

Last updated at Posted at 2023-11-18

CSS @scope記法の登場

CSSに@scopeが登場したようです。といっても2023年11月現在はまだ対応ブラウザはChromeとEdgeだけですが、今後Safariも対応するようです。
@scopeのなにがすごいのかというと、今まで悩まされていたCSSのグローバル問題が解決されます。BEM記法だのOOCSS記法だのが必要なくなります。もはやCSS in JSやTailwindCSSなどのユーティリティライブラリも必要なくなってくるんじゃないかと思っています。
この記事では、ReactやVueなどUIライブラリ、フレームワークにおいてそんな@scopeが全ブラウザで対応された時代に備えてどうCSSを書くべきかを述べていきます。あくまでも自分自身がこう思うって感じです。
例としては自分の得意なNext.jsで書きますが、Next.js特有の機能を使うわけでもないので各々のライブラリ、フレームワークに置き換えてください。

今回は以下のような簡単な入力フォームのUIを作ることを考えます。
スクリーンショット 2023-11-19 0.57.37.png

色合いは少し奇妙ですが、スタイルの適用をわかりやすくするためにしたものなのでご容赦ください。

reset cssの適用

HTML標準で適用されるスタイルをすべて初期化するため、reset cssを準備します。これはお好きなものをお使いください。

これをreset.cssとしておきます。ファイルの場所はわかりやすいところでいいです。

共通定数の定義

global.cssを用意して、その中に色などの定数を定義します。変数で定義しておけばのちに簡単に色の変更が可能になります。--[変数名]で変数を定義できます。

global.css
:root {
  --color-black: #555555;
  --color-success: #0f9ee0;
  --color-link: #04ac2b;
  --color-blue: #345ef3;
  --color-red: #dd3e3e;
}

これもreset.cssと同様グローバルに読み込ませます。

基本スタイルの定義

h1,h2,h3やaタグなど、全体的に共通して適用したいスタイルがあると思います。そういう基本的なスタイルを適用するために、base.cssを用意します。
base.cssに基本的に当てたいスタイルを書きます。

base.css
:root {
  color: var(--color-black);
}
a {
  color: var(--color-link);
  line-height: 2;
  font-weight: bold;
  text-decoration: underline dotted;
}
h1 {
  line-height: 0.1;
  font-weight: bold;
  font-size: 20px;
}
h1:before {
  content: "●";
  margin-right: 0.5rem;
  color: var(--color-blue);
}
button {
  cursor: pointer;
  padding: 0.5rem 1rem;
  text-align: center;
  color: white;
  font-weight: bold;
  background-color: var(--color-success);
}

var() を使うと定義した変数を呼び出すことができ、その定義した文字列がCSSに適用されます。

@layerの適用

@layerで定義すると、どちらのlayerのスタイルを優先させるかを設定できます。何もlayerを設定していないCSSが一番先に優先させられます。逆にあまり優先させたくないbase,resetはlayerで定義しておきます。
先ほどのglobal.cssにlayerを追記します。

global.css
@layer reset,base;
@import url(./base.css) layer(base);
@import url(./reset.css) layer(reset);
:root {
  --color-black: #555555;
  --color-success: #0f9ee0;
  --color-link: #04ac2b;
  --color-blue: #345ef3;
  --color-red: #dd3e3e;
}

このglobalの中にbaseとresetをimportします。さらにそのimportにlayerを定義してあげて、@layerの一文構文で優先度を決めてあげます。resetよりもbaseを優先してあげたいのでbaseを一番右に書きます。
このglobal.cssをNext.jsならlayout.jsまたは_app.jsに。VueやReactそのままならindex.htmlなど、全てのコンポーネントの大元に近いところに読み込ませます。

layout.tsx
import "./globals.css";
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="ja">
      <body>{children}</body>
    </html>
  );
}

これでCSSの初期設定は整いました。次からいよいよ@scopeの登場です。

各コンポーネントに@scopeを適用

各コンポーネントに対してcssを用意します。そしてcssのクラス名はコンポーネント名にしましょう。ケバブケースでもスネークケースでも大丈夫です。
今回はMailFormというコンポーネントにしたので、クラス名は.mail-formとしてみたいと思います。

page.tsx
import { Input } from "@/components/Input";
import Link from "next/link";
import "./style.css";
export default function MailForm() {
  return (
    <div className="mail-form">
      <h1>入力フォーム</h1>
      <form className="form">
        <div>
          <Input name="名前" id="name" />
        </div>
        <div className="not-form">
          <Input name="メールアドレス" id="name" />
        </div>
        <button>送信する</button>
        <p>ご登録をぜひお願いします。</p>
        <Link href="#">詳細はこちら</Link>
      </form>
    </div>
  );
}

Input.tsxに関しては入力フォームコンポーネントで、あとで定義します。

style.css
@scope (.mail-form) {
  :scope {
    max-width: 800px;
    margin: 1rem auto;
    border: var(--color-blue) solid 1px;
    border-radius: 0.5rem;
    padding: 2rem;
    color: var(--color-success);
  }
  .form {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    margin: 2rem 6rem;
  }
  p {
    color: var(--color-black);
  }
}

:scope@scope内の:rootみたいなものです。.mail-formを定義した直下からスタイルが適用されます。

これでこの@scopeで定義したスタイルが適用されます。さらにlayerが付いていないのでほかのbaseやresetよりも優先されます。仮に共通レイアウトのコンポーネント部分に.mail-formというクラス名を使ったとしてもこの@scopeで定義したスタイルは適用されません。

Inputの定義

次に、Inputというコンポーネントを定義します。これはページコンポーネントではなく、色々な場所で使われる共通コンポーネントです。このスタイルも@scopeで定義してあげます。

Input.tsx
import { InputHTMLAttributes } from "react";
import "./style.css";
type Props = {
  name: string;
  id: string;
} & InputHTMLAttributes<HTMLInputElement>;
export const Input = ({ name, id, ...rest }: Props) => {
  return (
    <div className="input">
      <label htmlFor={id}>{name}</label>
      <input id={id} name={name} {...rest} />
    </div>
  );
};
style.css
@scope (.input) {
  label {
    display: block;
    margin-bottom: 0.5rem;
    font-size: 0.875rem;
    font-weight: 700;
    color: var(--color-red);
  }
  input {
    background-color: #ffffff;
    border: 1px solid #d2d6dc;
    font-size: 0.875rem;
    border-radius: 0.375rem;
    display: block;
    width: 100%;
    padding: 0.625rem;
    box-shadow:
      0 4px 6px -1px rgba(0, 0, 0, 0.1),
      0 2px 4px -1px rgba(0, 0, 0, 0.06);
  }
  input:focus {
    outline: 2px solid #38b2ac;
  }
}

ここで、.inputの中にも、.mail-formにもcolorが設定されていることに注目してみます。この場合、@scopeはスコープ近接性というものがあり、近いscopeで定義されたスタイルが適用されます。なのでここでも別にどっちのスタイルが優先されるかなど変に気にせずにCSSを設定してもらって大丈夫です。

これで無事に完成しました。

終わりに

他のCSSライブラリは一切使わずに、素のCSSだけで書いてみました。Sassすら使っていません。CSSだけでできることがかなり増えました。@scope@layerを組み合わせればCSSの自由度が格段に上がったと思います。早く@scopeがほぼ全てのブラウザで対応されれば大手を振って使えるのになと思っています。もはやこの登場により、CSSライブラリ論争が終結したのではと思っています。@scopeはブラウザ標準のCSSのプロパティなのでバンドルツールもいらず、またライブラリがメンテされなくなるって心配もないです。なので私としては全ブラウザが@scopeに対応したあとに新規開発を行うのであれば率先して使うべきだと思っています。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3