LoginSignup
53
33

More than 3 years have passed since last update.

Theme UI で Theme Driven な快適 React スタイリング生活

Last updated at Posted at 2019-12-08

こんにちは。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倍(当社比)になります。

参考

Theming

Theme は Styled System 以外のライブラリでも実装されている概念です。たとえば Emotion や Material UI でも実装されています。

Styled System との比較のために、Emotion の Theming を取り上げます。

emotion-theming を使った Theming

Emotion で Theming する場合、まず ThemeProvidertheme を設定します。

theme.js
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 の値を取り出します。

Main.js
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 colorscolor, background-color, border-color と、Theme key spacemargin, 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 を使用する例です。

theme.ts
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',
    },
  },
};
App.tsx
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/csscss 関数を使えば styled-component で theme の値を扱うこともできます。

FlexColorBox.tsx
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 proptheme.styles について紹介し、本記事の締めとしたいと思います。

sx prop

sx prop は Emotion の css prop と似ていますが下記のような拡張がなされているようです。

  • theme で設定した値が使える
  • Style Prop API のショートハンドが使える

つまり Style Prop と同じ感覚でスタイルを書くことができるのです。ただし、sx props はオブジェクトリテラルしか許容しません。

sx prop を使用するにあたっては、 Emotion の css prop 同様 JSX pragma を記述する必要があります。

ThreeBoxes.tsx
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 の stylestheme オブジェクトの key です。これの役割は以下の2つです。

  • MDX のスタイリング
  • グローバルなスタイリング

ひとつめの MDX のスタイリングについては、まだ筆者が試せていないため言及はしません。
なので、ふたつめのグローバルなスタイリングについて、簡単な例を出し説明したいと思います。

まずは Theme ファイルを作成します。

theme.ts

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 です。

App.tsx
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 の補完が非常に気持ちよいです。鬼に金棒コンビでおすすめです :japanese_ogre:

付録

その他の Styled System ベースのライブラリ

Chakra UI

https://chakra-ui.com/

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

Edit styled-system-ui

example2: Theme UI の sx prop と theme.styles

https://codesandbox.io/embed/sx-prop-ivin7?fontsize=14&hidenavigation=1&theme=dark

Edit sx-prop

53
33
2

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
53
33