ブログに戻る — 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;
この例ではコンポーネントのコンテナ要素と段落のためのスタイルを含めています。汎用的なセレクタを使っていても、これらのスタイルは他のコンポーネントの同名のクラスや <p> タグに影響しません。Styled JSXはスタイルをこのコンポーネントにのみスコープする(スタイルを適用する要素に追加のユニークなクラス名を付与する)ためです。
<style jsx> 属性を <style> 要素に追加するだけで、標準的なCSSを書け、ベンダープレフィックス付与やコンポーネント単位での自動スコープが行われます。<style jsx> 要素はコンポーネントのルート要素の中に置くべきです。
グローバルスタイルの追加
ほとんどのプロジェクトでは body 要素やリセット用のグローバルスタイルが必要になります。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> タグに対して font-size: 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() を提供しており、ワンオフのグローバルセレクタにアクセスできます。例えば、<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;
この例では .container :global(.btn) により、Widget の内部にある .btn に対してのみスタイルを適用しています。
動的スタイル
コンポーネントのスタイルを 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 type="warning">This is an important message</Alert> のように type に warning を渡すと、オレンジ色の背景になります。type を指定しない場合はデフォルトのグレー背景になります。
動的なスタイルは別の <style jsx> タグに分けて置くことを推奨します。これは必須ではありませんが、静的な部分と動的な部分を分離することで props が変化したときに再計算されるのを最小限にできます。
別のアプローチとして、props の値に応じて異なるクラス名を適用する方法もあります:
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 をチェックしてください。