63일차[Tailwind CSS 프레임워크]

진하의 메모장·2025년 4월 7일
1

공부일기

목록 보기
66/66
post-thumbnail

2025 / 04 / 07

오늘 수업 시간에는 저번에 만든 간단한 블로그를 CSS 프레임워크인 Tailwind CSS를 사용하여 다시 만들어보았습니다. 이번 벨로그에서는 기술적인 부분 보다는 Tailwind CSS를 사용한 화면 구현 부분에 대해 이해하기 쉽도록 하나씩 정리해보도록 하겠습니다.



💌 Tailwind css

  • 유틸리티 - 퍼스트 방식의 CSS 프레임워크입니다.
  • 직접 클래스를 만들어서 스타일을 입히는 방식이 아니라, 이미 만들어진 아주 작고 재사용 가능한 클래스들을 조합해서 스타일을 입히는 방식입니다.

1. 기존 방식

일반 CSS

<div className="card">
  Hello
</div>

<style>
.card {
  padding: 16px;
  background-color: #f3f4f6;
  border-radius: 8px;
  font-weight: bold;
}
</style>


2. Tailwind 방식

  • 클래스를 따로 만들지 않고, 이미 제공된 유틸리티 클래스들로 스타일을 지정합니다.
<div className="p-4 bg-gray-100 rounded font-bold">
  Hello
</div>


💌 Tailwind 특징

1. 유틸리티 퍼스트

  • 기능 단위로 쪼개진 CSS 클래스를 조합해서 사용하는 구조입니다.
  • ex) bg-blue-500, text-white, rounded-md, p-4


2. 모바일 퍼스트 & 반응형

  • 스크린 사이즈별로 클래스를 지정할 수 있어서 반응형 디자인도 간단합니다.
<div className="text-sm md:text-base lg:text-lg">
  반응형 글자 크기
</div>


3. 다크모드 지원

  • Tailwind는 다크모드를 아주 간단하게 지원합니다.
// tailwind.config.js
module.exports = {
  darkMode: 'class', // 'media' 도 가능
}
<div className="bg-white dark:bg-gray-800 text-black dark:text-white">
  다크 모드 지원!
</div>


4. 커스터마이징 쉬움

  • tailwind.config.js에서 테마를 쉽게 확장하거나 변경이 가능합니다.
theme: {
  extend: {
    colors: {
      brand: '#1DA1F2',
    }
  }
}


💌 Tailwind 장점

1. 빠른 개발

  • 컴포넌트를 작성하면서 바로 스타일을 추가할 수 있습니다.

2. 반응형 편함

  • 클래스 이름으로 직접 반응형을 지정할 수 있습니다.

3. 유지보수 쉬움

  • 글로벌 CSS 작성이 거의 없습니다.

4. 일관성 유지

  • 전역 디자인 시스템 없이도 일관된 스타일을 사용할 수 있습니다.

5. 커스터마이징 쉬움

  • 설정 파일로 확장이 가능합니다.(컬러, 폰트 등)


💌 Tailwind 단점

1. 클래스 길어짐

  • 많은 스타일이 필요한 경우 <div>의 className이 너무 길어집니다.

2. 학습 곡선

  • 포반엔 클래스 이름을 외우는 게 살짝 어려울 수 있습니다.

3. 추상화 어려움

  • CSS의 의미적인 클래스(.card .button 등)를 잘 쓰는 사람에게는 어려울 수 있습니다.

4. 자동 완성 필수

  • VSCode Tailwind IntelliSense 같은 플러그인이 없으면 생산성이 떨어집니다.


💌 유틸리티 클래스

실전에서 자주 사용되는 Tailwind 유틸리티 클래스들 입니다.

스타일클래스 예시
paddingp-4, px-6, py-2
marginm-2, mt-4, mb-1
colorm-2, mt-4, mb-1
font-sizetext-sm, font-bold, tracking-wide
layoutflex, grid, justify-center, items-center
반응형sm:, md:, lg:, xl: 접두사
다크모드dark:bg-gray-800, dark:text-white


💌 정리하기

  • Tailwind CSS : 빠른 UI 구현과 유지보수에 강한 유틸리티 퍼스트 CSS 프레임워크입니다.
  • React와 궁합이 좋고, 컴포넌트 기발에 최적화되어 있습니다.
  • 단점도 있지만, 자동 완성 플러그인과 좋은 구조화 전략으로 극복할 수 있습니다.
  • 설정 후에는 생산성이 향상되었다는 느낌을 받을 수 있습니다.


💌 실습예제

  • 간단한 블로그 형식을 Tailwind CSS를 사용하여 구현해보았습니다.
  • Tailwind CSS를 활용한 화면 UI가 구성되어있습니다.


1. App.jsx

전체 애플리케이션을 구성하는 중심 컴포넌트

  • 사용자(user)와 포스트(post) 데이터를 API에서 불러옵니다.
  • 상태 관리(useState, useRef, useEffect)를 하고있습니다.
  • 검색 기능이 구현되어 있습니다.
  • 컴포넌트의 렌더링을 제어합니다.(검색창 vs 포스트 리스트)
function App() {
   const [post, setPosts] = useState([]);
   const [user, setUser] = useState([]);
   const [isSearch, setIsSearch] = useState(false);
   const [search, setSearch] = useState("");

   const inputRef = useRef(null);

   useEffect(() => {
      const getPosts = async () => {
         let url = "https://jsonplaceholder.typicode.com/posts";

         if (search) {
            url += `?userId=${search}`;
         }

         const response = await fetch(url);
         if (response.ok) {
            const data = await response.json();
            console.log(data);
            setPosts(data);
         }
      };

      getPosts();
   }, [search]);

   useEffect(() => {
      const getUsers = async () => {
         let userurl = "https://jsonplaceholder.typicode.com/users";
         const response = await fetch(userurl);
         if (response.ok) {
            const data = await response.json();
            setUser(data);
         }
      };

      getUsers();
   }, []);

   const userSearch = (e) => {
      if (e.key === "Enter") {
         const name = inputRef.current.value;
         const result = user.find((user) =>
            user.username.toLowerCase().includes(name.toLowerCase())
         );

         if (result) {
            console.log(result);
            setSearch(result.id);
            setIsSearch(false);
         } else {
            setSearch(null);
         }
      }
   };

   return (
      <div className="flex flex-col py-6 px-30">
         <Header setIsSearch={setIsSearch} />
         {isSearch && (
            <Input ref={inputRef} userSearch={userSearch} search={search} />
         )}
         {!isSearch && (
            <main className="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
               {post.map((item) => (
                  <Card post={item} key={item.id} />
               ))}
            </main>
         )}
      </div>
   );
}

export default App;


1) 상태 변수 설명

  • useState와 useRef를 사용하여 각각의 상황에 맞게 상태를 관리합니다.
const [post, setPosts] = useState([]);
const [user, setUser] = useState([]);
const [isSearch, setIsSearch] = useState(false);
const [search, setSearch] = useState("");

const inputRef = useRef(null);
  • post : API에서 받아온 게시물 목록
  • user : API에서 받아온 유저 목록
  • search : 검색된 유저의 ID(username으로 검색 후 userId 필터링 용)
  • isSeacrch : 검색창 표시 여부(true면 input을 보여줌)
  • inputRef : 검색창 input에 접근하기 위한 ref


2) 포스트 데이터 가져오기

  • search 값이 바뀔 때 마다 API를 요청합니다.
  • 해당 유저의 포스트만 필터링해서 가져올 수 있습니다.
useEffect(() => {
   const getPosts = async () => {
      let url = "https://jsonplaceholder.typicode.com/posts";

      if (search) {
         url += `?userId=${search}`; // 유저 ID로 필터링
      }

      const response = await fetch(url);
      if (response.ok) {
         const data = await response.json();
         setPosts(data);
      }
   };

   getPosts();
}, [search]);


3) 사용자 데이터 가져오기

  • 컴포넌트 마운트 시 1번만 실행됩니다.
  • 검색 시 username -> userId로 매핑하기 위해 user 데이터를 미리 받아옵니다.
useEffect(() => {
   const getUsers = async () => {
      const response = await fetch("https://jsonplaceholder.typicode.com/users");
      if (response.ok) {
         const data = await response.json();
         setUser(data);
      }
   };

   getUsers();
}, []);


4) 검색 처리 함수(userSearch)

  • Enter 키를 누르면 실행됩니다.
  • 입력값과 유저 목록을 비교합니다.(소문자로 통일해서 대소문자를 무시)
  • 일치하는 유저를 찾으면 search에 해당 ID를 저장합니다.(포스트 필터링)
  • 일치하는 유저가 없으면 null을 저장합니다.
  • null 값 저장 후에는 "유저가 존재하지 않습니다" 메시지를 표시합니다.
const userSearch = (e) => {
   if (e.key === "Enter") {
      const name = inputRef.current.value;
      const result = user.find((user) =>
         user.username.toLowerCase().includes(name.toLowerCase())
      );

      if (result) {
         setSearch(result.id);
         setIsSearch(false);
      } else {
         setSearch(null);
      }
   }
};


5. JSX 구성

  • isSearch === true → 검색 input 표시
  • isSearch === false → 카드 리스트 표시
<Header setIsSearch={setIsSearch} />
{isSearch && <Input ref={inputRef} userSearch={userSearch} search={search} />}
{!isSearch && (
   <main className="grid ...">
      {post.map((item) => (
         <Card post={item} key={item.id} />
      ))}
   </main>
)}


2. Header.jsx

상단 헤더 및 검색 버튼

  • 앱 제목을 표시합니다.
  • 검색 버튼(돋보기 아이콘)을 클릭 시 isSearch 상태를 토글합니다.
  • 아이콘 클릭할 때마다 검색창이 열리고 닫힙니다. (true ↔ false)
  • Tailwind로 아이콘에 커서 스타일이 추가되어있습니다.
<FontAwesomeIcon
   icon={faMagnifyingGlass}
   className="cursor-pointer"
   onClick={() => setIsSearch((prev) => !prev)}
/>


3. Input.jsx

검색 입력창 컴포넌트

  • 사용자가 입력한 텍스트로 유저를 검색합니다.
  • 검색을 실패하면 에러 메시지를 출력합니다.
  • forwardRef를 사용해 부모 컴포넌트(App)에서 ref를 전달받습니다.
  • Enter 입력 시 userSearch 실행됩니다.
  • search === null이면 검색 실패 메시지를 보여주게 됩니다.
<input
   type="text"
   ref={ref}
   onKeyUp={userSearch}
   placeholder="검색어를 입력해주세요!"
   className="border rounded px-3 py-2 focus:outline-none focus:ring"
/>
{search === null && (
   <div className="py-10"> 유저가 존재하지 않습니다.</div>
)}


4. Card.jsx

포스트 하나를 카드 형태로 출력하는 역할

  • 포스트 제목과 본문을 시각적으로 보여줍니다.
  • truncate, line-clamp을 활용해 텍스트 길이 제한합니다.
<div className="font-bold text-xl truncate pb-4">{post.title}</div>
<div className="line-clamp-3">{post.body}</div>
  • truncate → 한 줄 초과 시 ...으로 나타냅니다.
  • line-clamp-3 → 최대 3줄까지 표시 후 ... 처리합니다.


5. 포인트 정리

1. search 값은 userId

  • 검색창에서 username입력 -> userId로 변환하여 사용

2. useRef 사용 이유

  • input의 현재 값을 가져오기 위함(제어 컴포넌트는 아닙니다.)

3. forwardRef 쓰는 이유

  • 부모 컴포넌트(App)에서 input의 값을 직접 가져오기 위함

4. line-clamp-3 사용 조건

  • Tailwind plugin 설치 필요

5. 검색 실패 시 search === null 처리

  • null은 API 호출 안 됨 → 따로 메시지 처리 필요


💌 Tailwind 중심 분석

  • 위에서 살펴본 예제는 Tailwind CSS를 활용해 전체 UI가 구성되어있습니다.
  • CSS 파일을 따로 작성하지 않고, HTML 클래스 안에서 스타일을 직접 정의하였습니다.


1. App.jsx

전체 레이아웃 & 구조

  • 전체적으로 보면 상단에 Header → 아래 Input or Cards를 순서대로 배치하는 구조입니다.
<div className="flex flex-col py-6 px-30">

flex

  • 전체 레이아웃을 플렉스 박스로 설정합니다.

flex-col

  • 세로 방향(column)으로 정렬하였습니다.

py-6

  • 상하 패딩 1.5rem(24px)을 적용하였습니다.

px-30

  • 좌우 패딩 7.5rem(120px)를 적용하였습니다.


2. Header.jsx

상단 바 Tailwind CSS

<header className="flex justify-between items-center pt-3 pb-3">

flex

  • 내부 요소를 수평으로 정렬합니다.

justify-between

  • 좌우 끝으로 배치합니다.(JSONPLACEHOLDER, 아이콘)

items-center

  • 수직으로 중앙 정렬합니다.

pt-3 pb-3

  • 상하 패딩 0.75rem(12px)를 적용합니다.
  • py-3과 같은 기능을 합니다.


검색 버튼 Tailwind CSS

<FontAwesomeIcon
  className="cursor-pointer"
/>

cursor-pointer

  • 마우스를 올리면 손가락 모양으로 변경됩니다. (클릭 가능한 요소임을 표현)


3. Input.jsx

검색창 스타일

<input
   className="border rounded px-3 py-2 focus:outline-none focus:ring focus:ring-violet-400 z-10"
/>

border

  • 기본 테두리를 적용합니다.

rounded

  • 테두리를 둥글게 적용합니다.(기본 라운드)

px-3 py-2

  • 좌우 0.75rem / 상하 0.5rem 패딩을 적용합니다.

focus:outline-none

  • input창을 포커스 시 기본 브라우저 테두리 제거합니다.

focus:ring

  • Tailwind의 포커스 링 스타일을 추가합니다.

focus:ring-violet-400

  • 포커스 링 색상을 설정합니다. (보라색 계열)

z-10

  • z-index 설정 → 겹칠 경우 위에 위치하도록 설정합니다.


4. Card.jsx

포스트 하나의 카드 UI-1

<div className="shadow-md w-full">

shadow-md

  • 중간 정도의 그림자 효과를 적용합니다.

w-full

  • 부모 영역의 너비를 100% 차지하도록 합니다.


포스트 하나의 카드 UI-2

<div className="font-bold text-xl truncate pb-4">

font-bold

  • 굵은 텍스트를 적용합니다.

text-xl

  • 큰 텍스트 (기본보다 1.25rem)를 사용합니다.

truncate

  • 한 줄 초과 시 ...으로 처리합니다.

pb-4

  • 아래쪽 패딩 1rem(16px)을 적용합니다.


포스트 하나의 카드 UI-3

<div className="line-clamp-3">{post.body}</div>

line-clamp-3

  • 최대 3줄까지 보여주고, 초과는 ... 처리합니다.
  • line-clamp는 Tailwind의 플러그이기 때문에 사용하려면 설정을 꼭 해줘야합니다.


5. 반응형

포스트 리스트의 그리드 레이아웃

  • Tailwind의 반응형 디자인은 sm:, md:, lg:, xl:, 2xl: 같은 접두사로 사이즈별 반응형 스타일을 쉽게 적용할 수 있습니다.
<main className="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">

grid

  • CSS Grid를 적용합니다.

gap-4

  • 내부 아이템의 간격을 1rem으로 적용합니다.

grid-cols-1

  • 기본으로는 1열로 설정됩니다.

md:grid-cols-2

  • 화면 ≥ 768px → 2열로 적용됩니다.

lg:grid-cols-3

  • 화면 ≥ 1024px → 3열로 적용됩니다.

xl:grid-cols-4

  • 화면 ≥ 1280px → 4열로 적용됩니다.

2xl:grid-cols-5

  • 화면 ≥ 1536px → 5열로 적용됩니다.


💌 Tailwind Tip

Tailwind CSS에서 제가 자주 사용되는 클래스들을 정리한 내용입니다.

상황추천 클래스
전체 구조 정렬flex, flex-col, grid, justify-between, items-center
여백 설정p-, m-, py-, px-
타이포그래피text-, font-, truncate, line-clamp-*
스타일 요소rounded, shadow-*, border, ring
반응형 처리md:, lg:, xl: 같은 prefix
사용자 상호작용hover:, focus:, cursor-pointer 등



63일차 후기

  • Tailwind CSS를 활용해 구조, 스타일, 반응형까지 모두 클래스 기반으로 처리했습니다.
  • 별도의 CSS 작성 없이 UI를 빠르게 구성할 수 있었고, 특히 반응형 처리와 포커스 스타일, 텍스트 클램핑(line clamp) 같은 기능들이 눈에 띄게 편리했습니다.
  • Tailwind를 사용하면 디자인과 기능을 한눈에 파악할 수 있어 좋은 것 같습니다.
  • CSS의 선택자를 잘 사용하던 입장으로써.. div 태그가 길어지는 것은 보기 싫었습니다.
  • 그래도 처음에만 어떤 클래스를 사용할지 헷갈리지.. 후에는 좋은 것 같습니다.
  • 버튼 태그 같은 경우는 꾸미기 전까지는 일반 CSS와 다르게 기본 UI없이 글자만 화면에 보여지기 때문에 일반 텍스트 태그들과 착각하지 않도록 조심해야할 것 같습니다. 실습문제를 풀어보던 도중에 버튼 태그로 만들고.. 못 찾은적 있습니다..( ง⁼̴̀ω⁼̴ )ง⁼³₌₃
profile
૮꒰ ྀི〃´꒳`〃꒱ა

0개의 댓글