この記事の概要
EmotionはCSS in JSのライブラリの1つです。
The css PropやStyled Componentsなど様々な書き方ができるのが特徴です。
筆者は基本的に@emotion/react
のみを使っています。
その中でできる色々な書き方について紹介します。
中には@emotion/styled
を使えばすぐに解決するようなものもあるのですが、面白半分で読んでいただけると助かります。
基本の書き方
通常のCSSで言えば、1つのクラスとしてスタイルを適用するような書き方です。
これらの書き方がすべての基礎を成します。
String Styles
テンプレートリテラルでCSSを指定する方法です。
既存のCSSやサンプルコードを参考にする場合、単にコピー&ペーストするだけで済むので楽です。
しかしあくまで文字列として適用するため、インテリセンスや型チェックは行われません。
以下のコードは公式ドキュメントの内容を一部変更したものです。
import { css } from '@emotion/react'
const style = css`
color: hotpink;
`
const SomeComponent = () => (
<div css={style}>
Some hotpink text.
</div>
)
同じ内容をインラインでも書けます。
import { css } from '@emotion/react'
const SomeComponent = () => (
<div css={css`color: hotpink;`}>
Some hotpink text.
</div>
)
Object Styles
オブジェクトとしてCSSを指定する方法です。
(TypeScriptと併用する前提ですが)インテリセンスと静的な型チェックが可能です。
ただし、既存のCSSを複製する際は書式を変更しないといけないので一手間かかります。
量が多い場合など、筆者は以下のツールを使用します。
以下のコードは公式ドキュメントの内容を一部変更したものです。
import { css } from '@emotion/react'
const anotherStyle = css({
textDecoration: 'underline'
})
const SomeComponent = () => (
<div css={anotherStyle}>
Some text with an underline.
</div>
)
同じ内容をインラインでも書けます。
import { css } from '@emotion/react'
const SomeComponent = () => (
<div css={{textDecoration: 'underline'}}>
Some text with an underline.
</div>
)
Composition
通常のCSSで言えば、マルチクラスでスタイルを適用するような書き方です。
Include
明確に命名されているわけではなく、Sassのincludeに近いのでそう呼んでいるだけです。
以下のコードは公式ドキュメントの内容を一部変更したものです。
これぐらいの内容だとそこまでメリットはありませんが「似ているけど少しずつ違う」スタイルを定義する際に使えるのが伝われば幸いです。
import { css } from '@emotion/react'
const base = css`
color: hotpink;
`
const SomeComponent = () => (
<div>
<div
css={css`
${base};
background-color: #eee;
`}
>
Hotpink and Gray.
</div>
<div
css={css`
${base};
background-color: #000;
`}
>
Hotpink and Black.
</div>
</div>
)
Combine
これも明確に命名されているわけではありません。
また、考え方もほぼIncludeと同じです。
インラインで書いた方が早いときと、別途constを用意した方が使い回しやすいときと、状況に応じて使い分けるイメージです。
以下のコードは公式ドキュメントの内容を参考にしたものです。
import { css } from '@emotion/react'
const base = css`
background-color: darkgreen;
color: turquoise;
`
const loose = css`
padding: 10px;
`
const SomeComponent = () => (
<div>
<div css={base}>This is a basic text.</div>
<div css={[base, loose]}>This is text with lots of white space.</div>
</div>
)
Nested Selectors (公式の書き方)
公式ドキュメントにあるように、タグセレクターをネストしてスタイルを指定できます。
import { css } from '@emotion/react'
const paragraph = css`
color: turquoise;
a {
border-bottom: 1px solid currentColor;
cursor: pointer;
}
`
const SomeComponent = () => (
<p css={paragraph}>
Some text. <a>A link with a bottom border.</a>
</p>
)
また&
を使うことで親子関係を逆にした指定も可能です。
import { css } from '@emotion/react'
const paragraph = css`
color: turquoise;
header & {
color: green;
}
`
const SomeComponent = () => (
<div>
<header>
<p css={paragraph}>This is green since it's inside a header</p>
</header>
<p css={paragraph}>This is turquoise since it's not inside a header.</p>
</div>
)
Nested Selectors (非公式な書き方)
とは言え、ネスト指定したいときはこういう書き方ではないでしょうか。
先程の例に少し変更を加えてみました。
import { css } from '@emotion/react'
const paragraph = css`
color: turquoise;
`
const box = css`
${paragraph} {
color: green;
}
`
const SomeComponent = () => (
<div>
<div css={box}>
<p css={paragraph}>This is green.</p>
</div>
<div>
<p css={paragraph}>This is turquoise.</p>
</div>
<p css={paragraph}>This is turquoise.</p>
</div>
)
実現したいことは以下の通りですが、このコードでは叶いません。
- 通常の
paragraph
の色はturquoise
-
box
propを持った要素の中にあるparagraph
の色はgreen
というわけで、以下のように書き換えます。
import { css } from '@emotion/react'
const paragraph = css`
color: turquoise;
`
const box = css`
- ${paragraph} {
+ .css-${paragraph.name} {
color: green;
}
`
// 省略
ただしこの書き方はあくまでハックです。
Emotionのアップデート次第では動かなくなるため注意が必要です。
その他色々
使わなくても何の支障もありませんが、知っていると便利、くらいの内容です。
スタイルを1つのオブジェクトにまとめる
スタイルを1つのオブジェクトにまとめておくと、1つ1つのconstの名前を覚えておかないで済むので楽です。
以下の例でいえばstyles
をタイプした時点でself
とtitle
が候補として表示されます。
こちらのコードは公式ドキュメントの内容を一部変更したものです。
import { css } from '@emotion/react'
const styles = {
self: css({
backgroundColor: 'white',
border: '1px solid #eee',
borderRadius: '0.5rem',
padding: '1rem'
}),
title: css({
fontSize: '1.25rem'
})
}
const SomeComponent({ title, children }) => {
return (
<div css={styles.self}>
<span css={styles.title}>{title}</span>
{children}
</div>
)
}
副次的ですが、CSS Modulesから移行したい際は書式が同じなので少し楽かもしれません。
動的に変化させる
css propの場合は動的なスタイリングは提供されていないのですが、CSS Custom Propertiesを用いれば実現できます。
以下は公式ドキュメントの内容を一部変更したものです。
import { css } from '@emotion/react'
const avatar = css`
border-radius: 50%;
width: 40px;
height: 40px;
background-image: var(--background-style);
`
const SomeComponent = ({ imageUrl }) => (
<div css={avatar} style={{ '--background-style': imageUrl }} />
)
CSS Custom Propertiesではなく、propsを引数として渡した関数を作り、その返り値をcss propsにすることも可能です。
その場合は三項演算子による分岐なども使えます。
import { css } from '@emotion/react'
const avatar = (imageUrl) => css`
border-radius: 50%;
width: 40px;
height: 40px;
background-image: ${imageUrl ? imageUrl : 'url("path/to/defaultImage")'};
`
const SomeComponent = (props) => (
<div css={avatar(props.imageUrl)} />
)
propsの名前にあわせたスタイルを作る 1
渡ってきたpropsの名前がprimary
だったらprimary
のスタイルを適用し、secondary
だったらsecondary
を、などと制御するのは意外と面倒なので、こういう指定の仕方も可能です。
例のためにJavaScriptで書いていますが、頭の中でパターンを網羅するのも大変なのでTypeScriptで書いた方が良いでしょう。
import { css } from '@emotion/react'
const styles = {
base: css({
// 基本のスタイル
}),
primary: css({
// primaryなときのスタイル
}),
secondary: css({
// secondaryなときのスタイル
}),
}
const SomeComponent = ({ children, priority }) => {
return (
<div css={[styles.base, styles[priority]]}>
{children}
</div>
)
}
propsの名前にあわせたスタイルを作る 2
先程の応用で、このようなことも可能です。
こちらも、実際の使用にあたってはTypeScriptが前提です。
styles[`${priority}-${size}`]
をもとに有り得るパターンをすべてサジェストしてくれます。
import { css } from '@emotion/react'
const styles = {
base: css({
// 基本のスタイル
}),
'primary-large': css({
// 該当するスタイル
}),
'primary-medium': css({
// 該当するスタイル
}),
'primary-small': css({
// 該当するスタイル
}),
'secondary-large': css({
// 該当するスタイル
}),
'secondary-medium': css({
// 該当するスタイル
}),
'secondary-small': css({
// 該当するスタイル
}),
}
const SomeComponent = ({ children, priority, size }) => {
return (
<div css={[styles.base, styles[`${priority}-${size}`]]}>
{children}
</div>
)
}
最後に
そんなの使う?みたいなものも含めて色々出してみました。
曲芸っぽいものもありましたが、何かの場面で役立てば幸いです。
最後まで読んでくださってありがとうございます!
Twitterでも情報を発信しているので、良かったらフォローお願いします!