12
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Next.jsでビルトインサポートされているCSSを比較してみた

Last updated at Posted at 2021-05-15

これは何

作った物

Figma コーディングした結果

何かしらを投稿するサイトっぽい見た目で作ってみました。
(検証のために作っただけなのでモバイル対応はしていません。大変な崩れ方をします。)

GitHubで全てのコードを公開しているので良かったら見てください。

機能比較のための項目

  • SCSSの導入
    • CSS自体はビルトインサポートだけどSCSSはサポートされていない
  • コンポーネント自体のスタイルと親から渡すスタイルを分ける
    • コンポーネント自体の色や文字の大きさなど
    • 親要素から渡すgridやmarginなど
  • propsを渡してコンポーネントの見た目を変える
    • ボタンのコンポーネント
      • 塗りと線のバージョン
      • 大きめサイズと小さめサイズのバージョン
    • 投稿記事のコンポーネント
      • 画像有りと無しのバージョン

SCSSの導入

ビルトインサポートのスタイリングを比較していますが、なんだかんだSCSS記法は欲しくなると思います。
(疑似要素やメディアクエリなど)

CSS Modules

まずはsassのインストール。

npm install sass

次はnext.config.jsの変更。

const path = require('path')

module.exports = {
  sassOptions: {
    includePaths: [path.join(__dirname, 'styles')],
  },
}

なお、公式のドキュメントに記載があります。

styled-jsx

まずは@styled-jsx/plugin-sassのインストール。
こちらでもsassは必要なんですね。

npm install sass @styled-jsx/plugin-sass

そして.babelrcの編集

{
  "presets": [
    [
      "next/babel",
      {
        "styled-jsx": {
          "plugins": ["@styled-jsx/plugin-sass"]
        }
      }
    ]
  ]
}

こちらはNext.jsのドキュメントには記載がありませんが、パッケージのGitHubに詳細が書いてあります。

コンポーネント自体のスタイルと親から渡すスタイルを分ける

コンポーネントそのもののスタイル(色や文字の大きさやpadding)はコンポーネントに書いて、ページ上でどうレイアウトするかは親から渡した方が良いです。
ボタンのコンポーネントと、それがヘッダーの中にあるときを例にして説明します。

CSS Modules

propsとして受け取ったclassNamebuttonclassNameに渡せるようにすることで、marginやgrid-areaの指定などは親から渡せるようになっています。
直接は関係ありませんが、クラス名を複数持たせるにはclsxなど別途ライブラリを使う必要があります。

CSSModulesButton.tsx
import { FC, ReactNode } from 'react'
import styles from '../../styles/css-modules-button.module.scss'
import clsx from 'clsx'

type InputProps = JSX.IntrinsicElements['button']
type Props = {
  children: ReactNode
  className?: string
  size: 'm' | 's'
  variant: 'fill' | 'border'
  onClick?: (event: React.MouseEvent<HTMLInputElement>) => void
} & InputProps

export const CssModulesButton: FC<Props> = ({
  children,
  className,
  size,
  variant,
  onClick,
}) => {
  return (
    <button
      onClick={onClick}
      className={clsx(styles.button, styles[variant], styles[size], className)}
    >
      {children}
    </button>
  )
}

親要素であるヘッダーは、普通にclassNameを指定しているだけです。

CssModulesHeader.tsx
import { FC } from 'react'
import Image from 'next/image'
import { CssModulesButton } from './CssModulesButton'
import styles from '../../styles/css-modules-header.module.scss'

export const CssModulesHeader: FC = () => {
  return (
    <header className={styles.header}>
      <div className={styles.header__inner}>
        <h1 className={styles.header__logo}>
          <Image src="/logotype.svg" width={116} height={24} alt="Logotype" />
        </h1>
        <CssModulesButton
          size="m"
          variant="fill"
          className={styles.header__button}
          onClick={() => alert('新規作成')}
        >
          新規作成
        </CssModulesButton>
        <img
          src="https://source.unsplash.com/random/80x80"
          width="40"
          height="40"
          alt="User Icon"
          className={styles.header__icon}
        />
      </div>
    </header>
  )
}

styled-jsx

ボタンのコンポーネントはCSS Modulesとほとんど同じです。
こちらは複数のクラス名を持つにしても特別な準備は必要ありません。

余談ですが、<style jsx></style>の中に直接スタイルを書くと見通しが悪くなりすぎるので外に出すようにしています。

StyledJSXButton.tsx
import { FC, ReactNode } from 'react'
import css from 'styled-jsx/css'

type InputProps = JSX.IntrinsicElements['button']
type Props = {
  children: ReactNode
  className?: string
  size: 'm' | 's'
  variant: 'fill' | 'border'
  onClick?: (event: React.MouseEvent<HTMLInputElement>) => void
} & InputProps

export const StyledJSXButton: FC<Props> = ({
  children,
  className,
  size,
  variant,
  onClick,
}) => {
  return (
    <>
      <button
        onClick={onClick}
        className={`button ${variant} ${size} ${className}`}
      >
        {children}
      </button>
      <style jsx>{buttonStyle}</style>
    </>
  )
}

const buttonStyle = css`
  {/* 省略 */}
`

スタイルを渡す側の記述は若干面倒です。

StyledJSXHeader.tsx
import { FC } from 'react'
import Image from 'next/image'
import { StyledJSXButton } from './StyledJSXButton'
import css from 'styled-jsx/css'

export const StyledJSXHeader: FC = () => {
  return (
    <>
      <header className="header">
        <div className="header__inner">
          <h1 className="header__logo">
            <Image src="/logotype.svg" width={116} height={24} alt="Logotype" />
          </h1>
          <StyledJSXButton
            size="m"
            variant="fill"
            className={`header__button ${className}`}
            onClick={() => alert('新規作成')}
          >
            新規作成
          </StyledJSXButton>
          <img
            src="https://source.unsplash.com/random/80x80"
            width="40"
            height="40"
            alt="User Icon"
            className="header__icon"
          />
        </div>
      </header>
      <style jsx>{headerStyle}</style>
      {styles}
    </>
  )
}

const headerStyle = css`
  {/* 省略 */}
`

const { className, styles } = css.resolve`
  .header__button {
    margin-left: auto;
  }
`

css.resolveという記法で書く必要があります。
また、スタイルを渡したいコンポーネント(ここでいうとStyledJSXButton)のclassNameは以下のように書いて

className={`header__button ${className}`}

<style jsx>とは別に{styles}も記載しなければなりません。
css.resolveで定義されているのがclassName, stylesと名前被り必至なのが微妙に悩ましいです。

propsを渡してコンポーネントの見た目を変える

またもボタンを例に出しますが、propsを渡して以下を切り替えられるようにします。

  • 塗りのボタンと線のボタン
  • 大きめサイズと小さいサイズ

CSS Modules

CssModulesButton.tsx
export const CssModulesButton: FC<Props> = ({
  children,
  className,
  size,
  variant,
  onClick,
}) => {
  return (
    <button
      onClick={onClick}
      className={clsx(styles.button, styles[variant], styles[size], className)}
    >
      {children}
    </button>
  )
}

`scss:css-modules-button.module.scss
.button {
border-color: transparent;
border-radius: 2px;
border-style: solid;
border-width: 1px;
font-weight: bold;
padding: 7px 15px;
}

.fill {
background-color: var(--color-primary);
color: var(--color-light-text-high);
&:hover {
background-color: var(--color-primary-dark);
}
}

.border {
border-color: var(--color-primary);
color: var(--color-dark-text-medium);
&:hover {
background-color: var(--color-background);
color: var(--color-dark-text-high);
}
}

.m {
font-size: var(--font-size-body1);
}

.s {
font-size: var(--font-size-body3);
}
`

.buttonのクラスは常についていて、渡ってくるpropsにあわせてstyles[variant], styles[size]が上手く切り替わって付与されます。

また、CSS Modulesの記事を見るとstyles.classNameの記法が紹介されていますが、ドットで繋げても動かないためstyles[className]と記載する必要があります。
よくよく考えれば分かるのですが、自分はここに苦戦してなかなか実現できませんでした。

styled-jsx

例が若干悪かったかもしれません……ほぼCSS Modulesと変わらない書き方になりました。

StyledJSXButton.tsx
export const StyledJSXButton: FC<Props> = ({
  children,
  className,
  size,
  variant,
  onClick,
}) => {
  return (
    <>
      <button
        onClick={onClick}
        className={`button ${variant} ${size} ${className}`}
      >
        {children}
      </button>
      <style jsx>{buttonStyle}</style>
    </>
  )
}

const buttonStyle = css`
  .button {
    border-color: transparent;
    border-radius: 2px;
    border-style: solid;
    border-width: 1px;
    font-weight: bold;
    padding: 7px 15px;
  }

  .fill {
    background-color: var(--color-primary);
    color: var(--color-light-text-high);
    &:hover {
      background-color: var(--color-primary-dark);
    }
  }

  .border {
    border-color: var(--color-primary);
    color: var(--color-dark-text-medium);
    &:hover {
      background-color: var(--color-background);
      color: var(--color-dark-text-high);
    }
  }

  .m {
    font-size: var(--font-size-body1);
  }

  .s {
    font-size: var(--font-size-body3);
  }
`

本家のドキュメントにあるDynamic stylesも紹介します。

公式にあるコード
const Button = (props) => (
  <button>
     { props.children }
     <style jsx>{`
        button {
          padding: ${ 'large' in props ? '50' : '20' }px;
          background: ${props.theme.background};
          color: #999;
          display: inline-block;
          font-size: 1em;
        }
     `}</style>
  </button>
)

今回の例では塗りのボタンと線のボタンを切り替えるのがprops ? foo : barだとやたら記述量が多くなったので却って原始的な作りを実施しました。

まとめ

  • SCSSの導入とpropsにあわせてスタイルを変更するのはどちらもそんなに変わらない
  • 親からスタイルを受け渡すのはstyled-jsxだと割と面倒
    • またcss.resolveで定義されているのがclassName, stylesなのが名前被りして悩むタイミングがある
  • 個人的にはこの2つならCSS Modulesを選ぶ
12
10
0

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
12
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?