Emotion で CSS を extend する方法

Emotion でベーススタイルなどを拡張するような書き方について、オブジェクト記法の時にはどうしたら良いのかということにちょっと時間をかけてしまいました。
結構なんでも受け入れてくれる感じでしたが、忘れないようにメモしておきます。

Version

  • Emotion 10/11

Emotion の Object Styles で extend したい

アクセシビリティ確保を考えて、各コンポーネントの Readable な部分については colorbackground-color に一定以上のコントラストを保ちたかった。

そこで、Emotion を使ってコンポーネントのスタイリングをしている時に、外部に定義した colorbackground-color のセット (テーマパレットのようなもの) を読み込むのにやや苦労したので記録。

Emotion の書き方

自分用おさらい。


Emotion は CSS をテンプレートリテラルやオブジェクトで記述できます。

  • テンプレートリテラルで記述する場合
const styles = css`
  color: rgba(0,0,0,.87);
  background-color: white;
  `;
});
  • オブジェクト記法で記述する場合
const styles = css({
  color: `rgba(0,0,0,.87)`,
  backgroundColor: `white`
});

テンプレートリテラルを使うと CSS をそのまま書くことができるので、公式でもベーシックな使い方として紹介されています。

私はコード補完とかで設定が面倒なのでオブジェクト記法で書いています。 オブジェクト記法にすると CSS 特有のケバブケースをキャメルケースに変換する必要があります。Chorme DevTools などビジュアルエディタで書いた CSS を貼り付けるなど CSS -> Emotion の移植には向ていませんが、最初から Emotion で書くなら使いやすいです。


実際にどうやって Emotion で CSS を拡張するか

テンプレートリテラルの場合

Emotion - Composition にもあるように、Emotion では css で定義したスタイルをほかの css で拡張可能。

/** @jsx jsx */
import { jsx, css } from '@emotion/core'

const base = css`
  color: hotpink;
`

render(
  <div
    css={css`
      ${base};
      background-color: #eee;
    `}
  >
    This is hotpink.
  </div>
)

from: Emotion - Composition

テンプレートリテラルで書いていれば ${} タグで記述すればよいみたいです。

オブジェクト記法で extend する方法

これが実際にいろいろハマったやつ。

基本形

オブジェクト記法で extend するには、css() に入れていけば良い。

const palette = css({
  color: `rgba(0,0,0,.87)`,
  backgroundColor: `white`
});

// 引数にいれていく場合
const styles = css(palette, {
  margin: 0,
  padding: 0
});

// 配列にしてもよい
const styleList = css([palette, {
  margin: 0,
  padding: 0
}]);

Output される CSS を見やすくするとこんな感じに (実際の出力は minify されます)。

.css-hogehoge {
  color:rgba(0,0,0,.87);
  background-color:white;
  margin:0:
  padding:0
}

優先順位を高める場合には css() の引数の順番を変更するだけで良い。

const palette = css({
  color: `rgba(0,0,0,.87)`,
  backgroundColor: `white`
});

const styles = css({
  color: red,
  backgroundColor: 'rgba(128,128,128,1)'
}, palette);

上記の場合、palettecolorbackground-colorstyles の定義より後に来ます。 そのため、実際の描画では color: rgba(0,0,0,.87), background-color: white で表現されます。

入れ子にした時はどうしたらいいのか

<Global></Global> などを使用している時には以下のような書き方をしたいシーンが結構あります。

const global = css({
  html: {...},
  body: {...},
  '*': {...},
  a: {...},
  ...
})

この時、例えば body が別の css を拡張したいときにはどう書いたらよいのでしょうか。

const global をただのオブジェクトにして各メンバーを css() にするとかはやりたくないです。

const global = {
  html: css({...}),
  body: css({...}),
  '*': css({...}),
  a: css({...}),
  ...
})

ダサい…。

解決方法

どうしても気に入らずいろいろ試してみたところ、以下のように記述できることがわかりました。

const baseColor = css({
  color: `rgba(0,0,0,.87)`,
  backgroundColor: `white`
});

const global = css({
  html: {...},
  body: [baseColor, {
    ...
  }],
  '*': {...},
  a: {...},
  ...
})

css() はメンバーの値に配列を渡しても CSS にシリアライズしてくれるようです。配列なので順番が保証されているという安心感もあります。

まとめ

とりあえず公式ドキュメントを読むタイプなのですが、Emotion - Object Styles では入れ子の時の書き方がなく結構はまりました。

入れ子にして子要素のスタイリングをしてしまうとスコープが閉じなくなるのでやらないほうがいいとは思いますが、ul > li とかもあるので悩みどころ。

今日は「型定義もちゃんと見ようね」という教訓を得ました。

unimoku

Web サイトの制作、運営とアプリケーションのフロントエンド開発などをやっています。主に使うのはTypeScript、JavaScript、PHP、C/C++。特にTypeScriptが好きです。