Skip to content

๐Ÿชต 6. Tailwind CSS๋กœ ๋””์ž์ธ ์‹œ์Šคํ…œ ๋ฐ UI ์ปดํฌ๋„ŒํŠธ ์„ธํŒ…

Taeyeon Yoon edited this page Dec 5, 2024 · 1 revision

์•„๋ž˜ ๊ธฐ์ค€ ํ™˜๊ฒฝ ํ•˜์—์„œ ์ž‘์„ฑ๋์Šต๋‹ˆ๋‹ค.

  • vite: 5.4.10
  • react: 18.3.1
  • typescript: 5.6.2
  • tailwind: 3.4.14

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ํƒ ๊ธฐ์ค€์€ ์ด ํŽ˜์ด์ง€๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

ํšจ์œจ์ ์ธ UI ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ๋ง: Tailwind CSS + cn.ts ์ด ๊ธ€์„ ๊ธฐ์ค€์œผ๋กœ ์„ค์น˜ํ•˜๋Š” ๋‚ด์šฉ์„ ๋‹ค๋ฃจ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Tailwind CSS ์„ค์น˜ ๋ฐ ์„ธํŒ…

1. Tailwind CSS ์„ค์น˜

ํ”„๋กœ์ ํŠธ์˜ ์Šคํƒ€์ผ๋ง์„ ๋น ๋ฅด๊ฒŒ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด Tailwind CSS๋ฅผ ์„ค์น˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

2. Tailwind CSS ์ดˆ๊ธฐ ์„ค์ •

์ƒ์„ฑ๋œ tailwind.config.js ํŒŒ์ผ์— ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค. /src ์•ˆ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์™€ ํŽ˜์ด์ง€์—์„œ Tailwind CSS๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ธํŒ…ํ•ฉ๋‹ˆ๋‹ค.

// tailwind.config.js
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

3. ์Šคํƒ€์ผ ํŒŒ์ผ ์„ค์ •

src/index.css ํŒŒ์ผ์— Tailwind CSS์˜ ๊ธฐ๋ณธ ๋””๋ ‰ํ‹ฐ๋ธŒ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

์ถ”๊ฐ€: prettier-plugin-tailwindcss

Tailwind CSS ํด๋ž˜์Šค๋“ค์„ ์ž๋™์œผ๋กœ ์ •๋ ฌํ•ด์ฃผ๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ ์ผ๊ด€์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด prettier-plugin-tailwindcss ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

npm install -D prettier prettier-plugin-tailwindcss

์„ค์น˜ ํ›„, .prettierrc ํŒŒ์ผ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์„ค์ •์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

{
  "plugins": ["prettier-plugin-tailwindcss"]
}

์ปฌ๋Ÿฌ ์‹œ์Šคํ…œ ๊ตฌ์ถ•ํ•˜๊ธฐ

Tailwind CSS์˜ theme.extend.colors ์˜ต์…˜์„ ํ™œ์šฉํ•ด ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ปฌ๋Ÿฌ ํŒ”๋ ˆํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        violet: {
          50: '#EBE9FF',
          100: '#DAD7FF',
          200: '#BEB6FF',
          // ...
        },
        halfbaked: {
          50: '#F0FAFB',
          100: '#D9F2F4',
          200: '#B8E5E9',
          // ...
        },
        // ํ•„์š”ํ•œ ์ถ”๊ฐ€ ์ƒ‰์ƒ ์ •์˜
      },
    },
  },
}

์ด ์ปฌ๋Ÿฌ ์‹œ์Šคํ…œ์€ ๋””์ž์ธ ๊ฐ€์ด๋“œ์™€ ์ผ๊ด€๋œ ์ƒ‰์ƒ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ํ”ผ๊ทธ๋งˆ์— ์ปฌ๋Ÿฌ ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋‹ค๋ฉด ๋”์šฑ ์‰ฝ๊ฒŒ ๋˜๊ฒ ๋„ค์š”.

๋””์ž์ธ์ด ์ •ํ•ด์ง„ ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„๊ธฐ

1. ์œ ํ‹ธ๋ฆฌํ‹ฐ ์„ค์ •

๋จผ์ € ํšจ์œจ์ ์ธ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ๋ง์„ ์œ„ํ•œ ํ•ต์‹ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

npm install clsx tailwind-merge class-variance-authority

๊ทธ๋ฆฌ๊ณ  src/lib/utils/cn.ts ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด cn ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

2. ๊ธฐ๋ณธ ์ปดํฌ๋„ŒํŠธ ๊ตฌํ˜„

๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ๋กœ ์œ ๋ช…ํ•œ Button ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ด…๋‹ˆ๋‹ค.

import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/utils/cn';

const buttonVariants = cva(
  'inline-flex items-center justify-center gap-2 whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
  {
    variants: {
      variant: {
        proimary: 'bg-violet-500 border-2 border-violet-950 hover:bg-violet-600',
        secondary: 'bg-eastbay-900 border-2 border-violet-950 hover:bg-eastbay-950',
      },
      size: {
        sm: 'h-11 w-full text-2xl font-medium text-stroke-md',
        lg: 'h-14 rounded-2xl w-full text-2xl font-medium text-stroke-md',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'proimary',
      size: 'lg',
    },
  },
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, ...props }, ref) => {
  return <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
});
Button.displayName = 'Button';

export { Button, buttonVariants };

3. ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ ์˜ˆ์‹œ

<Button onClick={handleSubmit} className="font-neodgm-pro text-2xl font-normal">์ œ์ถœ</Button>

4. ์žฅ์  ๋ฐ ํŠน์ง•

  1. ์ฒด๊ณ„์ ์ธ ๋””์ž์ธ ์‹œ์Šคํ…œ ๊ตฌํ˜„
    • ์ผ๊ด€๋œ ํด๋ž˜์Šค ์ฒ˜๋ฆฌ ํŒจํ„ด์œผ๋กœ ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ๋ง
    • ํ”„๋กœ์ ํŠธ ์ „๋ฐ˜์˜ ๋””์ž์ธ ์ผ๊ด€์„ฑ ํ™•๋ณด
    • ์ฝ”๋“œ ์Šคํƒ€์ผ์˜ ๊ทœ๊ฒฉํ™” ๋‹ฌ์„ฑ
  2. ์ž๋™ํ™”๋œ ์Šคํƒ€์ผ ๊ด€๋ฆฌ
    • ์ปดํฌ๋„ŒํŠธ ๋‚ด๋ถ€/์™ธ๋ถ€ ์Šคํƒ€์ผ ์šฐ์„ ์ˆœ์œ„ ์ž๋™ ์กฐ์ •
    • ํด๋ž˜์Šค ์ถฉ๋Œ ์—†๋Š” ์•ˆ์ „ํ•œ ์Šคํƒ€์ผ ํ™•์žฅ
    • ์กฐ๊ฑด๋ถ€ ์Šคํƒ€์ผ๋ง์˜ ๊ฐ„์†Œํ™”
  3. ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ
    • ํƒ€์ž… ์‹œ์Šคํ…œ์„ ํ†ตํ•œ props ์ž๋™ ์™„์„ฑ
    • ์ปดํฌ๋„ŒํŠธ variants์™€ size ๋“ฑ์˜ ํƒ€์ž… ์•ˆ์ „์„ฑ
    • ๋นŒ๋“œ ํƒ€์ž„ ์ตœ์ ํ™”๋กœ ๋Ÿฐํƒ€์ž„ ์„ฑ๋Šฅ ๋ณด์žฅ
  4. ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ ์•„ํ‚คํ…์ฒ˜
    • ์Šคํƒ€์ผ ๋ณ€ํ˜•์˜ ์žฌ์‚ฌ์šฉ์„ฑ ํ™•๋ณด
    • ๊ธฐ์กด ์ปดํฌ๋„ŒํŠธ์˜ ์œ ์—ฐํ•œ ํ™•์žฅ
    • ์ƒˆ๋กœ์šด ๋””์ž์ธ ์š”๊ตฌ์‚ฌํ•ญ์— ๋น ๋ฅธ ๋Œ€์‘

๐Ÿ˜Ž ์›จ๋ฒ ๋ฒ ๋ฒ ๋ฒฑ

๐Ÿ‘ฎ๐Ÿป ํŒ€ ๊ทœ์น™

๐Ÿ’ป ํ”„๋กœ์ ํŠธ

๐Ÿชต ์›จ๋ฒ ๋ฒฑ ๊ธฐ์ˆ ๋กœ๊ทธ

๐Ÿช„ ๋ฐ๋ชจ ๊ณต์œ 

๐Ÿ”„ ์Šคํ”„๋ฆฐํŠธ ๊ธฐ๋ก

๐Ÿ“— ํšŒ์˜๋ก

Clone this wiki locally