[React] textarea 높이 자동 조절: Tailwind CSS와 useRef로 구현하는 깔끔한 UX
포스트
취소

[React] textarea 높이 자동 조절: Tailwind CSS와 useRef로 구현하는 깔끔한 UX


🚀 도입: 인라인 스타일 탈출, 그리고 자동 높이 조절

프로젝트를 진행하다 보면 유독 textarea가 말썽을 부릴 때가 많다. 기본적으로 제공되는 고정 높이는 사용자 경험(UX)을 해치기 일쑤다. 처음에는 자바스크립트로 높이를 조절하면서 style= 같은 인라인 스타일을 남발하곤 했다.

하지만 Tailwind CSS를 도입하면서 “어떻게 하면 인라인 스타일을 최소화하면서 유연한 UI를 만들 수 있을까?”를 고민하게 되었다. 오늘은 인라인 스타일의 지저분함을 걷어내고, Tailwind CSS와 useRef를 조합해 세련되게 늘어나는 textarea를 구현한 과정을 공유한다.


💡 핵심 개념: 왜 scrollHeight인가?

textarea는 내부 콘텐츠가 아무리 길어져도 DOM 노드 자체의 height는 변하지 않는다. 이때 우리가 참고해야 할 속성이 바로 scrollHeight다.

scrollHeight는 패딩을 포함하여 화면에 보이지 않는 콘텐츠까지 합친 전체 높이를 말한다. 이 값을 실시간으로 추적해 요소의 style.height에 주입하는 것이 핵심이다. (이 부분은 아쉽게도 아직 순수 CSS만으로는 완벽한 호환성을 보장하기 어렵기에 JS의 힘을 빌려야 한다.)


🛠️ 실전 코드: Tailwind CSS + useRef

인라인 스타일을 최소화하기 위해 레이아웃과 디자인은 Tailwind 클래스로 처리하고, 동적인 높이 값만 Ref를 통해 제어한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { useRef, useEffect, useState } from "react";

export default function AutoResizableTextarea() {
  const [text, setText] = useState("");
  const textareaRef = useRef(null);

  const handleResizeHeight = () => {
    const textarea = textareaRef.current;
    if (textarea) {
      // 1. 높이를 초기화하여 줄어들 때의 scrollHeight를 정확히 측정
      textarea.style.height = "auto";
      // 2. 측정된 scrollHeight를 실제 높이에 반영
      textarea.style.height = `${textarea.scrollHeight}px`;
    }
  };

  useEffect(() => {
    handleResizeHeight();
  }, [text]); // 텍스트 상태가 변할 때마다 실행

  return (
    <div className="w-full max-w-md p-4 mx-auto">
      <label className="block mb-2 text-sm font-medium text-gray-700">
        자동 높이 조절 입력창
      </label>
      <textarea
        ref={textareaRef}
        value={text}
        onChange={(e) => setText(e.target.value)}
        rows={1}
        placeholder="내용을 입력하세요..."
        className="w-full p-3 border border-gray-300 rounded-lg shadow-sm 
                   focus:ring-2 focus:ring-blue-500 focus:border-transparent 
                   resize-none overflow-hidden transition-all duration-75 
                   bg-white text-gray-900"
      />
    </div>
  );
}

📊 비교: 왜 useRef 방식이 더 나은가?

항목순수 Javascript (Vanilla)React useRef 방식
접근 방식document.querySelector로 직접 탐색React 노드 참조를 통해 안전하게 접근
상태 동기화이벤트 리스너와 상태가 따로 놀 수 있음useEffect 의존성 배열로 상태 변화에 즉각 대응
코드 응집도스크립트가 분산될 우려가 큼컴포넌트 내부에 로직이 캡슐화됨
성능불필요한 DOM 탐색 발생 가능참조값 유지로 불필요한 오버헤드 감소

결론: 리액트 환경에서 직접적인 DOM API를 사용하는 Vanilla JS 방식은 렌더링 타이밍을 놓치거나 메모리 누수를 유발할 수 있다. useRef를 활용하면 리액트의 생명주기 안에서 안전하게 DOM을 조절할 수 있어 훨씬 견고한 코드가 된다.


📝 실전 포인트 & 인사이트

언제 써야 하는가?
댓글창이나 모바일 메신저 UI처럼 한 줄로 시작해서 글이 길어지는 인터페이스에 필수적이다. 특히 Tailwind의 resize-none 클래스와 함께 사용해 사용자가 임의로 크기를 조절하지 못하게 막으면서 시스템이 자동으로 맞춰주는 것이 가장 깔끔하다.


실수하기 쉬운 부분
Tailwind를 쓸 때 h-10 같이 고정 높이 클래스를 넣어버리면 JS가 부여하는 style.height와 충돌이 발생한다. 초기 높이는 rows={1} 속성이나 패딩으로 조절하고, 높이 관련 Tailwind 클래스는 피하는 것이 좋다.


개발 중 겪은 삽질 (Troubleshooting)
텍스트를 한꺼번에 지울 때 높이가 즉시 줄어들지 않는 버그를 겪었다. 원인은 textarea.style.height = 'auto' 코드를 빠뜨렸기 때문이었다. 이 코드가 있어야 현재 높이의 제약 없이 브라우저가 콘텐츠의 '진짜 높이'를 다시 계산할 수 있다.

✅ 마무리하며

인라인 스타일을 지양하고 Tailwind CSS를 쓰는 이유는 코드의 가독성과 유지보수 때문이다. 하지만 높이 조절처럼 “계산된 값”이 필요한 순간에는 JS의 힘을 빌릴 수밖에 없다.

중요한 것은 그 JS 로직을 리액트답게(useRef, useEffect) 작성하고, 나머지 디자인은 Tailwind로 깔끔하게 분리하는 균형이다. 이제 여러분의 프로젝트에서도 스크롤바 없는 쾌적한 입력창을 구현해 보길 바란다!

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

[React] Input 커서 위치에 텍스트 삽입하기: useRef와 Selection API 활용법

[React] requestAnimationFrame으로 채팅창 스크롤 최적화하기: 끊김 없는 UI 경험의 비밀