コンテンツにスキップ
ブログに戻る

2019年3月28日 木曜日

Styled JSX で Next.js をスタイリングする

投稿者

Styled JSXは、コンポーネントをスタイリングするためにカプセル化されスコープされたCSSを記述できるCSS-in-JSライブラリです。あるコンポーネントに導入したスタイルが他のコンポーネントに影響を与えることはなく、意図しない副作用を心配することなくスタイルを追加、変更、削除できます。

はじめに

Next.js はデフォルトで Styled JSX を含んでいるため、既存の React 要素に <style jsx> タグを追加してその中に CSS を記述するだけで簡単に始められます。

pages/index.js
function Home() {
  return (
    <div className="container">
      <h1>Hello Next.js</h1>
      <p>Let's explore different ways to style Next.js apps</p>
      <style jsx>{`
        .container {
          margin: 50px;
        }
        p {
          color: blue;
        }
      `}</style>
    </div>
  );
}
 
export default Home;

この例では、コンポーネントのコンテナ要素と段落にスタイルを含めています。一般的なセレクターを使用していますが、スタイルは他のコンポーネントの container クラス名を持つ要素や <p> タグには影響しません。これは、Styled JSX がスタイルをこのコンポーネントのみにスコープするよう保証しているためです(スタイル化された要素に一意のクラス名を追加することで)。

<style> 要素に jsx 属性を1つ追加するだけで、自動プレフィックスが適用され、コンポーネントに自動的にスコープされる標準的なCSSを記述できます。<style jsx> 要素は、コンポーネントのルート要素内に配置する必要があります。

グローバルスタイルを追加する

ほとんどのプロジェクトでは、body 要素をスタイリングしたり、CSSリセットを提供したりするために、いくつかのグローバルスタイルが必要です。Styled JSX では、<style jsx global> を使用してグローバルスタイルを追加できます。例:

pages/index.js
function Home() {
  return (
    <div className="container">
      <h1>Hello Next.js</h1>
      <p>Let's explore different ways to style Next.js apps</p>
      <style jsx>{`
        .container {
          margin: 50px;
        }
        p {
          color: blue;
        }
      `}</style>
      <style jsx global>{`
        p {
          font-size: 20px;
        }
      `}</style>
    </div>
  );
}
 
export default Home;

これは、この特定のページ内のすべての <p> タグに20pxのフォントサイズを適用します。

アプリケーションのすべてのページにグローバルスタイルを適用するには、まずグローバルスタイルを含むレイアウトコンポーネントを作成し、そのコンポーネントですべてのページをラップするのが良い方法です。

レイアウトコンポーネントを使用すると、特定のスタイルのセットを一部のページに適用しつつ、他のページには異なるスタイルを適用できる柔軟性が得られます。

components/Layout.js
function Layout(props) {
  return (
    <div className="page-layout">
      {props.children}
      <style jsx global>{`
        body {
          margin: 0;
          padding: 0;
          font-size: 18px;
          font-weight: 400;
          line-height: 1.8;
          color: #333;
          font-family: sans-serif;
        }
        h1 {
          font-weight: 700;
        }
        p {
          margin-bottom: 10px;
        }
      `}</style>
    </div>
  );
}
 
export default Layout;

Next.js では、pages/_app.js 内にカスタム App コンポーネントを作成し、Layout コンポーネントをインポートして、レンダリングメソッドに以下のように追加することで、すべてのページで一度だけレイアウトを読み込むことができます。

pages/_app.js
import React from 'react';
import App, { Container } from 'next/app';
import Layout from '../components/Layout';
 
class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;
 
    return (
      <Container>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </Container>
    );
  }
}
 
export default MyApp;

外部ファイルにスタイルを記述する

コンポーネントの外部のファイルにスタイルを記述することもできます。

例えば、グローバルスタイルを Layout コンポーネントから別のファイルに以下のように移動できます。

styles/global.js
import css from 'styled-jsx/css';
 
export default css.global`
  body {
    margin: 0;
    padding: 0;
    font-size: 18px;
    font-weight: 400;
    line-height: 1.8;
    color: #333;
    font-family: sans-serif;
  }
  h1 {
    font-weight: 700;
  }
  p {
    margin-bottom: 10px;
  }
`;

その後、そのスタイルを Layout コンポーネントにインポートし直すことができます。

components/Layout.js
import globalStyles from '../styles/global.js';
 
function Layout(props) {
  return (
    <div className="page-layout">
      {props.children}
      <style jsx global>
        {globalStyles}
      </style>
    </div>
  );
}
 
export default Layout;

一時的なグローバルセレクター

<style jsx> を使用してコンポーネントに追加するスタイルは、そのコンポーネント内の要素のみに影響し、子コンポーネントには影響しません。

時として、子コンポーネントの特定のスタイルをオーバーライドする必要があるかもしれません。これを行うには、Styled JSX は :global() を提供しており、これにより一時的なグローバルセレクターにアクセスできます。

例えば、クラス名 btn を持つボタンを含む <Widget> コンポーネントがあるとします。ウィジェットがホームページにインポートされたときにのみこのボタンの色を変更したい場合、以下のようにできます。

pages/index.js
import Widget from '../components/Widget';
 
function Home() {
  return (
    <div className="container">
      <h1>Hello Next.js</h1>
      <Widget />
      <style jsx>{`
        .container {
          margin: 50px;
        }
        .container :global(.btn) {
          background: #000;
          color: #fff;
        }
      `}</style>
    </div>
  );
}
 
export default Home;

動的スタイル

他のソリューションと比較して、コンポーネントのスタイルをそのプロパティに基づいて適応できることは、CSS-in-JS ライブラリの大きな利点です。

Styled JSX を使用すると、以下のようにできます。

components/Alert.js
function Alert(props) {
  return (
    <div className="alert">
      {props.children}
      <style jsx>{`
        .alert {
          display: inline-block;
          padding: 20px;
          border-radius: 8px;
        }
      `}</style>
      <style jsx>{`
        .alert {
          background: ${props.type == 'warning' ? '#fff3cd' : '#eee'};
        }
      `}</style>
    </div>
  );
}
 
export default Alert;

Alert コンポーネントに warning の値を持つ type プロパティが渡された場合、例えば

<Alert type="warning">This is an important message</Alert>

コンポーネントはオレンジ色の背景を持ちます。type プロパティを指定しない場合、背景はデフォルトの灰色に戻ります。

動的スタイルを別の <style jsx> タグに配置したことに注意してください。これは必須ではありませんが、プロパティが変更されたときに動的な部分だけが再計算されるように、静的スタイルと動的スタイルを分割することを**推奨**します。

プロパティに基づいてスタイルを適応させる代替アプローチは、以下に示すようにプロパティの値に基づいて異なるクラス名を適用することです。

components/Alert.js
function Alert(props) {
  return (
    <div className={props.type == 'warning' ? 'alert warning' : 'alert'}>
      {props.children}
      <style jsx>{`
        .alert {
          display: inline-block;
          padding: 20px;
          border-radius: 8px;
          background: #eee;
        }
        .alert.warning {
          background: #fff3cd;
        }
      `}</style>
    </div>
  );
}
 
export default Alert;

サイトテーマの作成

テーマは、アプリケーションで必要となる最も一般的な変数を格納するシンプルなオブジェクトで構いません。

styles/theme.js
const theme = {
  fontFamily: {
    sansSerif: '-apple-system, "Helvetica Neue", Arial, sans-serif',
    mono: 'Menlo, Monaco, monospace',
  },
  colors: {
    text: '#333',
    background: '#fff',
    link: '#1eaaf1',
    linkHover: '#0d8ecf',
    border: '#ddd',
    warning: '#fff3cd',
    success: '#d4edda',
  },
};
 
export default theme;

次に、このテーマファイルをコンポーネントにインポートし、ハードコードされた値を変数に置き換えます。

components/Layout.js
import theme from '../styles/theme';
 
function Layout(props) {
  return (
    <div className="page-wrapper">
      {props.children}
      <style jsx global>{`
        body {
          background: ${theme.colors.background};
          color: ${theme.colors.text};
          font-family: ${theme.fontFamily.sansSerif};
        }
      `}</style>
      <style jsx global>{`
        body {
          margin: 0;
          padding: 0;
          font-size: 18px;
          font-weight: 400;
          line-height: 1.8;
        }
        h1 {
          font-weight: 700;
        }
        p {
          margin-bottom: 10px;
        }
      `}</style>
    </div>
  );
}
 
export default Layout;
components/Alert.js
import theme from '../styles/theme';
 
function Alert(props) {
  return (
    <div className="alert">
      {props.children}
      <style jsx>{`
        .alert {
          display: inline-block;
          padding: 20px;
          border-radius: 8px;
        }
      `}</style>
      <style jsx>{`
        .alert {
          background: ${props.type == 'warning'
            ? theme.colors.warning
            : theme.colors.light};
        }
      `}</style>
    </div>
  );
}
 
export default Alert;

このブログ記事では、Styled JSX の始め方について説明しました。追加機能の詳細については、GitHubでご確認ください