FrontEnd Develop/Project : TODOMVC

Tailwind 기반 버튼 컴포넌트 재활용하기

Frisbeen 2025. 4. 30. 17:32

재활용을 왜하는가?

React 프로젝트를 진행하다 보면 버튼과 같은 컴포넌트들을 반복해서 만들게 됩니다. 그때마다 className을 복붙하고 색깔을 바꾸다 보면 코드가 지저분해지고, 유지보수도 힘들어집니다. 이번 글에서는 Tailwind CSS + TypeScript 조합으로 깔끔하고 재사용 가능한 버튼 컴포넌트를 만들어보는 방법을 공유합니다.


1. 기본 Button 컴포넌트 만들기

먼저 가장 기본이 되는 버튼 컴포넌트를 Button.tsx로 만들겠습니다.

import { ButtonHTMLAttributes } from 'react';

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'danger' | 'black';
  fullWidth?: boolean;
}

const Button = ({
  variant = 'primary',
  fullWidth = true,
  className = '',
  ...props
}: ButtonProps) => {
  const baseStyle =
    'px-4 py-2 rounded transition text-white font-medium focus:outline-none';

  let variantStyle = '';
  if (variant === 'primary') variantStyle = 'bg-blue-500 hover:bg-blue-600';
  else if (variant === 'danger') variantStyle = 'bg-red-500 hover:bg-red-600';
  else if (variant === 'black') variantStyle = 'bg-black hover:bg-gray-800';

  const widthStyle = fullWidth ? 'w-full' : '';

  const combinedClassName = `${baseStyle} ${variantStyle} ${widthStyle} ${className}`.trim();

  return <button className={combinedClassName} {...props} />;
};

export default Button;

이 컴포넌트는 다음과 같은 특징을 가집니다:

  • variant: 버튼의 색상 테마 선택 (primary, danger, black)
  • fullWidth: 버튼 너비를 100%로 설정할지 여부
  • ...props: onClick, type, disabled 등 HTML <button> 속성들을 모두 담아서 자식에게 보낸다.

2. ButtonHTMLAttributes란?

ButtonHTMLAttributes<HTMLButtonElement>는 TypeScript에서 <button>이 가질 수 있는 모든 HTML 속성(onClick, disabled, type 등)을 미리 정의해둔 타입입니다. 이걸 extends로 사용하면 우리가 만든 컴포넌트에서도 자연스럽게 기본 속성들을 사용할 수 있게 됩니다.

 

쉽게말하면 만약 HTMLAttributes으로 제한을 해주지 않으면?

아래처럼 필요한 속성들을 모두 타입에다가 정의를 해줘야합니다.

interface ButtonProps {
  onClick?: () => void;
  type?: 'button' | 'submit' | 'reset';
  disabled?: boolean;
  className?: string;
  // ...이런 걸 전부 다! 매번!
}

 

그러나 아래처럼 타입 상속을 해준다면 이미 만들어진 ButtonHTMLAttributes의 속성과 더불어

내가 만든 variant, fullWidth 속성 또한 추가로 활용하겠다는 것입니다.

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'danger' | 'black';
  fullWidth?: boolean;
}
<Button onClick={handleClick} disabled type="submit" />

 

부모 컴포넌트에서 따로 props를 일일이 정의하거나 넘겨줄 필요 없이,

<Button> 컴포넌트를 <button>처럼 자연스럽게 사용할 수 있는 거예요.

 

3. 자식 컴포넌트에서 사용하는 쪽에서는 그냥 원래 버튼 쓰듯이 쓰면 됩니다!

<Button variant="danger" onClick={logout}>
  로그아웃
</Button>

<Button variant="primary" onClick={() => setRedirect(true)}>
  TODO 시작하기
</Button>

<Button variant="black" onClick={login}>
  GitHub로 로그인
</Button>

간단하고 깔끔하게, 스타일도 일관되게 유지할 수 있습니다.


마무리

Tailwind를 쓰는 프로젝트에서는 버튼 같은 UI 컴포넌트를 최대한 컴포넌트화해서 관리하는 것이 유지보수에도, 협업에도 훨씬 유리합니다.

위처럼 variant, width  같은 속성말고도  점점 확장해 나갈수도 있고, 원하는 테마의 버튼을 마구마구 만들어서 하나의 컴포넌트로 관리하는 방법도 있습니다.