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

[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로 깔끔하게 분리하는 균형이다. 이제 여러분의 프로젝트에서도 스크롤바 없는 쾌적한 입력창을 구현해 보길 바란다!

This post is licensed under CC BY 4.0 by the author.

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

-