1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

デザインシステムを考慮したコンポーネント設計について考える

Last updated at Posted at 2025-02-28

忙しい人向けまとめ

React やデザインシステムに触れたばかりだと意識しづらいポイント

  • classNameを自由に渡せる設計にすると、スタイルの統一性が崩れる

  • 子コンポーネントを適切に分割しないと、管理が難しくなる

  • 呼び出し側で必要なpropsを定義してから実装しないと使いづらい

デザインシステムの意図を考慮しながらコンポーネントを設計することが大切

Before: 課題のあるコンポーネント

コード例はNext.js, スタイリングはvanilla-extractです

Button.tsx
interface ButtonProps {
  label?: string;
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
  disabled?: boolean;
  icon?: React.ReactNode;
  iconPosition?: 'before' | 'after';
  isLoading?: boolean; // isLoadingがデザインシステムにない
  className?: string;
  style?: React.CSSProperties;
}

const Button: React.FC<ButtonProps> = ({
  label,
  icon,
  iconPosition = 'before',
  onClick,
  disabled,
  isLoading,
  className,
  style,
}) => {
  return (
    <button onClick={onClick} className={className} style={style} disabled={disabled}>
      {isLoading ? 'Loading..' : (
        <>
          {icon && iconPosition === 'before' && <span>{icon}</span>}
          {label}
          {icon && iconPosition === 'after' && <span>{icon}</span>}
        </>
      )}
    </button>
  );
};
export default Button;

課題点

  • classNameを自由に渡せるため、デザインの統一性が崩れる

  • propsの整理が甘く、呼び出し側で余計な考慮が必要になる

  • labeliconの使い方が統一されておらず、意図が伝わりにくい

呼び出し側.tsx
<Button
      label="送信" // ラベルをchildrenにするかpropsにするのかは制限による
      onClick={() => alert('送信しました')}
      icon={<SendIcon />}
      iconPosition="after"
      className={primaryButtonM} // ここの自由度が高すぎるのと直感的でない
 />

After: シンプルで使いやすい設計

デザインシステムごとに適切なコンポーネントを分け、propsの自由度を抑えることで統一感を確保すると使いやすい。

PrimaryButton.tsx

type Props = {
  children: React.ReactNode;
  size?: 's' | 'm' | 'l';
  variant?: 'green' | 'blue' | 'lightBlue' | 'purple' | 'gold' | 'red';
  disabled: boolean;
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;

export const PrimaryButton: React.FC<Props> = ({
  children,
  size = 'm',
  variant = 'green',
  disabled = false,
  onClick,
}) => {
  return (
    <button
      className={`${buttonBase} ${sizes[size]} ${
        disabled ? primaryDisabled[variant] : primaryVariants[variant]
      }`}
      disabled={disabled}
      onClick={onClick}
    >
      {children}
    </button>
  );
};

デザインシステムごとにスタイルを統一

  • classNameを自由に渡せないので、見た目のバラつきを防ぐ

  • childrenを必須にして柔軟なラベル表示を可能に

呼び出し側.tsx
<PrimaryButton size="s" variant="purple" onClick={() => alert('送信しました')}>送信</PrimaryButton>

別例(アイコンとラベルはセットの場合)

FormButton.tsx
export const FormButton: React.FC<Props> = ({
  children,
  icon,
  onClick, 
  disabled,
  ...props
}) => {
  return (
    <button className={formButton} disabled={disabled} type="button" onClick={onClick} {...props}>
      <span className={iconStyle}>{icon}</span>
      {children}
    </button>
  );
};
  • アイコンとラベルはセットで必須

  • classNameを固定し、デザインの統一感を維持

  • 必要最低限のpropsだけを渡せばOK

使い方

呼び出し側.tsx
<FormButton icon={<RefreshCw />} onClick={handleClick}>
  更新
</FormButton>

もしアイコンとテキストの組み合わせを完全に固定したくなったら ...

呼び出し側.tsx
<FormButton variant="refresh" onClick={handleClick}>
  更新
</FormButton>

のように variant を受け取る形にするというように修正する

まとめ

  • 呼び出し側で余計な考慮が必要となる自由なpropsに注意

  • デザインシステムに沿って設計することで統一感と管理しやすさが向上

  • コンポーネントごとにルールを決めると、実装のブレが少なくなる

誰でも迷わず使えるコンポーネントを目指すとこんな感じになるのかなと思って書きましたが間違っているところもあると思います
デザシス x 実装についてリファレンスにできるしょできそうな関連書籍などあれば紹介いただけると嬉しいです

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?