Backブログに戻る

Styled JSX を使った Next.js のスタイリング

Styled JSX は CSS-in-JS ライブラリで、コンポーネントのスタイルをカプセル化されたスコープ付き CSS で記述できます。このブログ記事では Next.js で Styled JSX を使い始める方法を紹介します。

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

はじめに

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 属性を追加するだけで、自動プレフィックスが付き、コンポーネントに自動的にスコープされた標準 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 コンポーネントをインポートして、次のように render メソッドに追加することで、すべてのページに一度レイアウトを読み込めます:

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() を提供しており、単発的なグローバルセレクタにアクセスできます。

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

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;

動的スタイル

他のソリューションと比較して、コンポーネントの props に基づいてスタイルを適応させられることは 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 prop が渡されると:

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

コンポーネントはオレンジ色の背景になります。type prop を指定しない場合、背景はデフォルトの灰色にフォールバックします。

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

props の値に基づいてスタイルを適応させる別の方法は、次のように prop 値に基づいて異なるクラス名を適用することです:

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 でチェックしてください。