Emotion Best Practice(TypeScript+object styles)
Emotionとは何か。
css in JSと言われるJavaScriptでcssスタイルを書くためのライブラリの1つ。他に有名なcss in JSはstyled-component。
Emotionの基本的な使い方
Emotionでは一般的にcss propsを使います。
render(
<div
css={{
backgroundColor: 'hotpink',
'&:hover': {
color: 'lightgreen'
}
}}
>
This has a hotpink background.
</div>
)
しかし、jsxにスタイルを含めてしまうと可読性を大きく損ねてしまいます。なので、css
を使ってスタイルをjsxから分離させましょう。
import { css } from '@emotion/react'
const style = css`
backgrond-color: hotpink;
&:hover {
color:lightgreen;
}
`
render(
<div css={style}>
This has a hotpink background.
</div>
)
cssやstyled-componentに慣れている人とは上記のテンプレートリテラルの方が使いやすいかも。
vs codeの拡張機能vscode-styled-component
を追加すれば、補完も効いて書きやすいと思います。
なぜTypeScript+object stylesなのか
TypeScript+object stylesはEmotionのBest Practiceにて言及されています。
Recommendations
Use TypeScript and object styles
You don't get Intellisense or type checking when using CSS strings, e.g. css
color: blue
. You can improve the developer experience and prevent style bugs by using TypeScript and object styles, which enable Intellisense and some static type checking:
https://emotion.sh/docs/best-practices
要約すると、
テンプレートリテラルだとintellisenseと静的チェックが機能しないけど、TypeScriptとobject stylesなら機能するからバグを防げるよ〜
って書いてある。
というわけで、TypeScript+object stylesで書いていきます。
TypeScript+object stylesの基本
上記の例をTypeScript+object stylesに変更していきます。
import { css } from '@emotion/react'
-const style = css`
- backgrond-color: hotpink;
- &:hover {
- color:lightgreen;
- }
-`
+const style = css({
+ backgroundColor: 'hotpink',
+ '&:hover': {
+ color: 'lightgreen'
+ }
+})
render(
<div css={style}>
This has a hotpink background.
</div>
)
ざっとこんな感じ。そこまで難しくない。
cssの仕様がわからなかったら、MDNなどで調べればだいたいわかります。
ここからはEmotionの公式ドキュメントで記載されている順序に従って、自分が実際につまずいたところを中心に紹介します。
Nested Selectors
import { css } from '@emotion/react'
const breakpoint = '@media (min-width: 1000px)'
const style = css({
backgroundColor: 'hotpink',
h1: {
color: 'red'
},
'&:hover': {
color: 'lightgreen'
},
[breakpoint]:{
backgrounrdColor: 'blue'
}
})
render(
<div css={style}>
This has a hotpink background.
<h1>red text</h1>
</div>
)
- 単一の要素セレクタの場合はSassなどと同様。
- それ以外の場合はセレクタをstringにする。(''で囲む)
- 変数の場合は[ ]で囲む。
Media Queries
import { css } from '@emotion/react'
const breakpoints = {
sm: '576px',
md: '768px',
lg: '992px',
xl: '1200px',
}
const mq = (bp: keyof typeof breakpoints) =>
`@media (min-width: ${breakpoints[bp]})`
const style = css({
color: 'green',
[mq('md')]: {
color: 'gray'
},
[mq('lg')]: {
color: 'hotpink'
}
})
render(
<div css={style}>
Some text!
</div>
)
Global Styles
import { Global, css } from '@emotion/react'
const globalStyles = css({
h1: {
fontSize: 50,
textAlign: 'center'
}
})
render(
<>
<Global styles={globalStyles}/>
<h1>Text</h1>
</>
)
Keyframes
import { css, keyframes } from '@emotion/react'
const bounce = keyframes({
'from, 20%, 53%, 80%, to': {
transform: 'translate3d(0,0,0)'
},
'40%, 43%': {
transform: 'translate3d(0, -30px, 0)'
},
'70%': {
transform: 'translate3d(0, -15px, 0)'
},
'90%': {
transform: 'translate3d(0,-4px,0)'
}
})
const style = css({
animation: `${bounce} 1s ease infinite`
})
render(
<div css={style}>
some bouncing text!
</div>
)
Attaching Props
import { css } from '@emotion/react'
import { ReactNode } from 'react'
const TextWrap = (props: {
children: ReactNode;
isRed?: boolean;
}) => {
return(
<div css={textWrap({props.isRed})}>
{props.children}
</div>
)
}
const textWrap = (props: { isRed?: boolean }) =>
css({
color: props.isRed && "red"
})
render(
<>
<TextWrap>text</TextWrap>
<TextWrap isRed >red text</TextWrap>
</>
)
Theming
import '@emotion/react'
declare module '@emotion/react' {
interface Theme {
colors: Colors
}
}
interface Colors {
mainColor: string
backgroundColor: string
}
export const theme = {
colors: {
mainColor: "#64363C",
backgroundColor: "#F8C3CD",
},
};
import { css, ThemeProvider } from "@emotion/react";
import { theme } from "./theme";
function App() {
return (
<ThemeProvider theme={theme}>
<div css={style}>Hoge</div>
</ThemeProvider>
);
}
const style = css({
color: theme.color.mainColor,
backgroundColor: theme.color.backgroundColor
})
export default App;
theme.d.tsを作ることでthemeの補完が効きます。
実装で困ったcssプロパティ
fontFamily
.style {
font-family: "Gill Sans Extrabold", sans-serif;
}
↓
import { css } from '@emotion/react'
const style = css({
fontFamily: ['Gill Sans Extrabold', 'sans-serif'].join(, )
})
配列で書く場合は以上の通り。stringも可。
lineHeight
.style {
font-size: 40px;
line-height: 50px;
}
↓
import { css } from '@emotion/react'
const style = css({
fontSize: 40,
- lineHeight: 50,
+ lineHeight: '50px',
})
numberだと(fontSize*lineHeight)pxになってしまうため、pxの場合はstringで書かなければない。
まとめ
Emotionの推奨するTypeScript+object stylesについて書きました。最後はコード多めだったけど説明に関しては公式ドキュメントや他の記事を見てほしい。そういう記事はそれなりにあるから。でも、Emotionの記事でobject styelsで書いているのって少ないんだよね。ということで、この記事を書きました。MUIを使う人にはobject stylesに馴染みがあるけど、cssやstyled-componentを使ってる人には馴染みがないんだよな。
Discussion