こんにちは。YAMAP でフロントエンドエンジニアをしている @SotaSuzuki と申します。
この記事は YAMAP エンジニア Advent Calendar 2019 の 9日目の記事となります。
拙い文章で恐縮ですが何卒よろしくお願いいたします。
tl;dr
- Styled System の Theming は Emotion の Theming と比べて 3倍便利
- Theme UI の Theming はさらにその 2倍高機能
- Styled System の Style Prop API と TypeScript の併せ技で 3倍高速なマークアップ
- Theme UI はいいぞ
Styled System とは
本題の Theme UI に入る前に、Theme UI のベースとなるライブラリ Styled System について簡単に説明します。
Styled System は Theme Specification に基づく theming と、Style Prop API 、Variants 機構などを備えた UI 構築用ライブラリです。
これらの機能はあくまでオリジナルの UI コンポーネントを構築するための low-level な API として提供されており、たとえば Button
などの UI コンポーネント自体は Styled Stystem からは提供されていません。
いうなれば Styled System はメタ UI ライブラリともいうことができそうです。
Styled System の機能で個人的に強力だと思うのが Style Prop API で、これと TypeScript を併用することでコード補完も効くようになるため、UI のスタイリングが体感で通常の3倍(当社比)になります。
参考
- System UI Theme Specification
- API - Styled System
- Variants - Styled System
- Ecosystem - Styled System
Theming
Theme は Styled System 以外のライブラリでも実装されている概念です。たとえば Emotion や Material UI でも実装されています。
Styled System との比較のために、Emotion の Theming を取り上げます。
emotion-theming を使った Theming
Emotion で Theming する場合、まず ThemeProvider
に theme
を設定します。
import React from 'react';
import styled from '@emotion/styled';
import { ThemeProvider } from 'emotion-theming';
const theme = {
colors: {
primary: '#3cf',
},
};
export default () => {
return (
<ThemeProvider theme={theme}>
<Main />
</ThemeProvider>
);
};
そして、theme
の値を使用するコンポーネントでは props
、または useTheme
hook を使い theme
の値を取り出します。
import React from 'react';
/** @jsx jsx */
import { jsx } from '@emotion/core';
import styled from '@emotion/styled';
import { useTheme } from 'emotion-theming';
const Main = () => {
const theme = useTheme();
return (
<>
{/* with css prop */}
<h1 css={theme => ({ color: theme.colors.primary })}>Emotion Theming</h1>
{/* with Styled */}
<StyledH1>Emotion Theming</StyledH1>
{/* with useTheme hook */}
<h1 css={{ color: theme.colors.primary }}>Emotion Theming</h1>
</>
);
};
const StyledH1 = styled({
color: theme => theme.colors.primary
});
export default Main;
Styled System の Theming
まず Styled System の theming は System UI Theme Specification に基づいて実装されています。emotion-theming との最大の違いはこれと言ってよいでしょう。
System UI Theme Specification はその名の通り Theming の仕様です。ここでは Theme key と対応する CSS プロパティが定義されており、各CSS プロパティでは対応する Theme key に設定している値のみ使用できます。
たとえば、Theme key colors
は color
, background-color
, border-color
と、Theme key space
は margin
, padding
などと対応しています。
下記の例は、padding は space
Theme key に対応しているため space
の値を使えていますが、transform は space
に対応していないため space
の値が使えていません。
// Theme
const theme = {
// space が Theme key にあたる
space: [0, 4, 8, 12]
};
// p={2} -> padding: 8px
// transform={translateY(2)} -> transform: translateY(2);
render(<Box p={2} transform={trnaslateY(2)}></Box>);
また、特殊な Theme key として breakpoints が存在します。これは mediaQuery のショートハンドのようなもので非常に便利です。
以下は Styled System で Theming を使用する例です。
const theme = {
colors: {
text: '#333',
background: '#fff',
primary: '#48f',
secondary: '#37e',
muted: '#e4e4e4',
},
space: [0, 4, 8, 12, 16, 20, 24, 32],
fontSizes: [12, 14, 16, 18, 20, 24, 32, 48, 64, 80],
breakpoints: ['480px', '768px', '1024px'],
buttons: {
primary: {
color: 'white',
backgroundColor: '#48f',
},
},
};
import React from 'react';
import { ThemeProvider } from 'styled-components';
import {
buttonStyle,
ButtonStyleProps,
color,
ColorProps,
background,
backgroundProps,
border,
borderProps,
display,
DisplayProps,
space,
SpaceProps,
} from 'styled-system';
import theme from './theme'
export default () => {
return (
<ThemeProvider theme={theme}>
<>
{/*
- ~479px -> padding: 8px
- 480px~ -> padding: 12px
- 768px~ -> padding: 16px
*/}
<Box p={[2, 3, 4]} bg="muted">
{/* using theme values */}
<Button color="text" bg="background">Normal Button</Button>
{/* using variant prop */}
<Button variant="primary">Primary Button</Button>
</Box>
</>
</ThemeProvider>
);
}
type BoxProps = SpaceProps &
ColorProps &
DisplayProps &
BorderProps;
const Box = styled.div<BoxProps>(
space,
color,
display,
border,
);
type ButtonProps = ButtonStyleProps & ColorProps & BackgroundProps;
const Button = styled.button<ButtonProps>(
buttonStyle,
color,
background,
);
または @styled-system/css の css
関数を使えば styled-component で theme
の値を扱うこともできます。
import React from 'react';
import styled from 'styled-components';
import { display, DisplayProps } from 'styled-system';
import { css } from '@styled-system/css';
import { Box } from './Box'; // 先の例の Box コンポーネントを import しています
export const FlexColorBox: React.FC = styled<BoxProps & DisplayProps>(Box)(
display,
css({
display: 'flex',
alignItems: 'center',
color: 'background',
bg: 'primary',
border: '1px solid',
borderColor: 'secondary',
p: [2, 3, 4],
})
);
このように、StyledSystem の Theming は variants や breakpoints などの便利機能を多数取り揃えています。
また、System UI Theme Specification に制約ベースの仕様のため、必然的に Theme ファイルが混沌となりづらくなるのもメリットといえるでしょう。
とはいえ、Styled System 自体ははじめに説明したとおり、UI コンポーネントを構築するためのシステムであり、UI ライブラリではありません。
実際の現場におけるアプリケーション開発で Styled System をそのまま使うのは、ちょっと悠長な気もします。たとえるなら、create-react-app や next.js を使わずに react アプリをイチから構築するのに近いかもしれません。
参考
Theme UI
One of the primary motivations behind Theme UI is to make building themeable, constraint-based user interfaces in React as simple and as interoperable as possible.
ThemeUI は、UI コンポーネントカタログや Styled System Theming の拡張、MDX、Color Mode、Gatsby 向けのプラグインまで、様々な要素を携えた React 用に作られた ライブラリです。
Theme UI 自体は Theming に特化しており、sx
prop と MDX 向けの Theming ができるのが特徴です。他にもサブディレクトリに様々なパッケージが存在し、それぞれ開発が進められているようです。
Chrome Extension の Theme UI DevTools や、アプリケーション上で Theme を編集するための UI を取り揃えている @theme-ui/editor といった面白いパッケージもあるので、Theme UI を使うなら目を通しておくとよいでしょう。
ちなみに Theme UI の作者は Styled System の作者と同じ @jxnblk 氏(Gatsby 社勤務)です。彼は Rebass という Styled System に基づいて構築された UI ライブラリも公開しています。
本人の Twitter や GitHub の Activity を見たところ、現在は ThemeUI のほうに注力しているようなので、これから導入するなら Rebass よりも Theme UI がよいでしょう。
ただし、ThemeUI は比較的新しいプロジェクト(2019年4月頃〜)で未だバージョン 0.2.49 です。
これからどんどん API の追加/削除/変更はされていくことが予想されるため、導入する際そのあたりは注意しておきたいところです。
sx prop と theme.styles
本記事では Theme UI への Deep Dive はしません。筆者も Theme UI について、ドキュメントを通したくらいなので、まだ Theme UI について多くを語ることができないからです。
そのかわりといってはなんですが、Theme UI のさわり、sx prop と theme.styles について紹介し、本記事の締めとしたいと思います。
sx prop
sx prop は Emotion の css prop と似ていますが下記のような拡張がなされているようです。
-
theme
で設定した値が使える - Style Prop API のショートハンドが使える
つまり Style Prop と同じ感覚でスタイルを書くことができるのです。ただし、sx props はオブジェクトリテラルしか許容しません。
sx prop を使用するにあたっては、 Emotion の css prop 同様 JSX pragma を記述する必要があります。
import React from 'react';
/** @jsx jsx */ // <- これが JSX Pragma
imprt { jsx } from 'theme-ui';
const ThreeBoxes: React.FC = () => {
return (
<div sx={{
display: 'flex',
p: 2,
}}>
{[1, 2, 3].map(n => (
<div key={n} sx={{
bg: 'accent',
color: 'background',
p: 4,
ml: n !== 1 ? 2 : 0,
}}>BOX{n}</div>
))}
</div>
);
};
export default ThreeBoxes;
参考
theme.styles
theme.styles の styles
は theme
オブジェクトの key です。これの役割は以下の2つです。
- MDX のスタイリング
- グローバルなスタイリング
ひとつめの MDX のスタイリングについては、まだ筆者が試せていないため言及はしません。
なので、ふたつめのグローバルなスタイリングについて、簡単な例を出し説明したいと思います。
まずは Theme ファイルを作成します。
import { Theme } from 'theme-ui';
import { invert } from '@theme-ui/color'
const theme: Theme = {
breakpoints: ['480px', '768px', '1024px'],
colors: {
text: '#fff',
primary: '#48f',
accent: 'f72',
},
fonts: {
body: 'system-ui, sans-serif',
heading: '"Avenir Next", sans-serif',
monospace: 'Menlo, monospace',
},
fontSizes: [10, 12, 14, 16, 20, 24, 30, 48, 64],
space: [0, 4, 8, 12, 16, 20, 24, 30, 60, 100],
borders: {
base: '1px solid #444',
accent: '1px solid #f72',
},
};
theme.styles = {
h1: {
fontSize: [5, 6, 7],
fontFamily: 'heading',
mt: [2, 3, 4],
mb: ['2px', 1, 2],
},
button: {
bg: 'accent',
color: invert('accent'),
border: 'accent',
p: 1,
},
};
export default theme;
次に、Theme ファイルで定義した theme.styles のスタイルをコンポーネントに適用します。
theme-ui から Styled
モジュールを import して使います。これは Styled Components の styled
に似た API です。
import * as React from 'react';
/** @jsx jsx */
import { Styled, jsx, ThemeProvider } from 'theme-ui';
import theme from './theme';
const App: React.FC = () => {
return (
<ThemeProvider theme={theme}>
<Styled.h1>Changing the HTML element or component</Styled.h1>
<p>The rendered HTML element can be changed for each Styled component. This can be useful for ensuring accessible, SEO-friendly, and semantically-correct HTML is rendered while leveraging styles from the theme. This API originated in Rebass and is similar to the <Styled.button as="a" href="https://emotion.sh/docs/styled">@emotion/styled</Styled.button> API.
</p>
<Styled.h1 as="h2">H2 Styled as h1</Styled.h1>
<p>blah blah blah</p>
</ThemeProvider>
);
};
export default App;
参考
おわります
Theme UI はあんまり日本語の情報が落ちていませんでした。追いかけるなら、英語の情報を・・というより作者の Twitter とか GitHub を watch する必要がありそうです。
筆者は MDX も気になっているので、今度は Theme UI + MDX コンポーネントを試してみようかしらん、と画策しています。
以上です。
ちなみに Styled System ベースのライブラリを使うときは TypeScript を使うと、CSS Property と Style Prop API の補完が非常に気持ちよいです。鬼に金棒コンビでおすすめです
付録
その他の Styled System ベースのライブラリ
Chakra UI
GitHub Primer Components
https://primer.style/components/
sx prop VS built-in style prop
build-in style prop というのはスタイル属性のことです。
sx のメリット
- ランダムな class 名を当ててくれる
- media-query を書ける
- 疑似セレクタ/疑似要素を書ける
- Theme が使える
style 属性のメリット
- library を必要としない
- 強い(CSS詳細度が)
Example で使っているパッケージ
yarn add theme-ui @types/theme-ui @emotion/core @mdx-js/react styled-system styled-components @types/styled-system @/types/styled-components @styled-system/css @types/styled-system__css
今回書いた Examples 全貌(ちょっと変わっているかも)
Code Sandbox で用意しています。
example1: UI built with Styled System
https://codesandbox.io/embed/styled-system-ui-j17bu?fontsize=14&hidenavigation=1&theme=dark
example2: Theme UI の sx prop と theme.styles
https://codesandbox.io/embed/sx-prop-ivin7?fontsize=14&hidenavigation=1&theme=dark