🍣

styled-components で共通プロパティをまとめる4つの方法

に公開

※本記事は、ChatGPTとの対話をもとに生成AIを活用して執筆していますが、全体の構成と主張は筆者自身の考えによるものです。


React × styled-components でスタイルを記述していると、「同じスタイル何回も書いてるな…」と感じること、ありませんか?

この記事では、styled-components を使っているプロジェクトで「共通プロパティをどうまとめるか」について、代表的な方法を4つ紹介します。それぞれの特徴や使うと嬉しいシチュエーションもあわせて解説します。

1. css ヘルパーで共通スタイルを定義

最もシンプルな方法が styled-components に含まれる css ヘルパーを使う方法です。

// styles/common.ts
import { css } from 'styled-components';

export const commonButtonStyle = css`
  padding: 8px 16px;
  border-radius: 8px;
  font-weight: bold;
`;
// components/Button.tsx
import styled from 'styled-components';
import { commonButtonStyle } from '../styles/common';

const PrimaryButton = styled.button`
  ${commonButtonStyle};
  background-color: blue;
  color: white;
`;

const SecondaryButton = styled.button`
  ${commonButtonStyle};
  background-color: gray;
  color: black;
`;

2. ベースコンポーネントを作成して拡張

共通スタイルをベースの styled コンポーネントに持たせて、それを継承する方法です。

// components/BaseButton.tsx
import styled from 'styled-components';

export const BaseButton = styled.button`
  padding: 8px 16px;
  border-radius: 8px;
  font-weight: bold;
`;
// components/PrimaryButton.tsx
import styled from 'styled-components';
import { BaseButton } from './BaseButton';

export const PrimaryButton = styled(BaseButton)`
  background-color: blue;
  color: white;
`;

✅ ポイント

ベースコンポーネントでは、スタイルに加えて「HTMLタグや属性の共通化」も可能です。
たとえば、BaseButtona タグや div タグとして使いたい場合、as 属性を使ってタグの差し替えができます。

as="a" を使うとは?

以下のように記述すると、HTML上では <a> タグとして出力されます:

<BaseButton as="a" href="/about">About</BaseButton>

出力されるHTML:

<a href="/about" class="...">About</a>

スタイルは BaseButton のまま、タグだけが a に変わるため、
「見た目はボタン、動作はリンク」 のようなUIも簡単に実現できます。

補足:as は styled-components 独自の機能

as 属性は React の標準機能ではありません。
これは styled-components(や Emotion などの CSS-in-JS ライブラリ)独自の機能で、
as に渡されたタグ名やコンポーネントを使って動的に JSX を出力しています。

そのため、通常の React コンポーネントに as="..." を指定しても動作しません。
as を使えるのは、styled-components でラップされたコンポーネントに限られます。

3. テーマを使って共通スタイルを変数化

ThemeProvider を使えば、デザインのトークン(色・サイズなど)をアプリ全体に渡すことができます。

// theme.ts
export const theme = {
  button: {
    padding: '8px 16px',
    borderRadius: '8px',
    fontWeight: 'bold',
  },
};
// Button.tsx
import styled from 'styled-components';

const Button = styled.button`
  padding: ${({ theme }) => theme.button.padding};
  border-radius: ${({ theme }) => theme.button.borderRadius};
  font-weight: ${({ theme }) => theme.button.fontWeight};
`;

ThemeProvider の使い方

テーマを使うには、アプリのルート付近で ThemeProvider を使ってテーマを提供する必要があります。

import { ThemeProvider } from 'styled-components';
import { theme } from './theme';
import { Button } from './Button';

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Button>Click me</Button>
    </ThemeProvider>
  );
}

これによって、styled-components の中で props.theme にアクセスできるようになります。

4. mixin化(関数化)して再利用

細かいスタイルや条件付きで使いたいものは、mixin(スタイルの関数化)すると便利です。

import { css } from 'styled-components';

export const ellipsis = css`
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
`;

export const responsiveFontSize = (size: number) => css`
  font-size: ${size}px;

  @media (max-width: 768px) {
    font-size: ${size * 0.8}px;
  }
`;
const Title = styled.h1`
  ${responsiveFontSize(24)};
`;

補足:方法4は方法1の派生テクニック

方法4(mixin)は、方法1(cssヘルパー)を関数化して柔軟性を持たせた進化形です。
最初は方法1で共通化しておいて、あとから条件分岐やパラメータが必要になったときに方法4へスケールアップする、というのが自然な流れです。

4つの手法の比較表

方法 特徴 採用を検討すべきケース
css ヘルパー スタイルの断片をそのまま再利用できる 同じ見た目のUIを複数のコンポーネントで使い回したいとき
ベースコンポーネント HTMLタグや属性も含めて共通化できる UIの種類ごとにタグや属性、スタイルをまとめて再利用したいとき
テーマ デザインのトークン管理や切り替えに強い デザイナーと連携した設計や、テーマの切り替えが必要なとき
mixin(関数) 引数で柔軟にスタイルを出し分けられる サイズや状態によってスタイルを調整したいとき

おわりに

styled-components を使っていると「このスタイル、他でも使いたいな」と思うことがよくあります。
まずは css で共通化 → 条件付きになったら mixin にスケールアップ
レイアウト構造も含めてまとめたくなったらベースコンポーネント化
さらにデザイン全体で共通化したくなったらテーマへ…
というように、状況に応じて段階的に使い分けていくのがオススメです!

参考リンク

Discussion