29장. 보기 좋고 쓰기 쉬운 UI 다듬기
![[5부] 보기 좋고 쓰기 쉬운 UI 다듬기](https://images.tilnote.io/pages/5074a5f4-6e54-46f5-af1f-20200ab4b421.webp)
![[5부] 보기 좋고 쓰기 쉬운 UI 다듬기](https://images.tilnote.io/pages/71845f25-4e07-471d-949a-f845944b1dd5.webp)
예쁜 화면보다, 덜 헷갈리는 화면이 더 좋다
지난 장에서 우리는 검색과 정렬을 붙이며 목록 화면을 더 똑똑하게 만들었다. 이제 사용자는 원하는 글을 찾고, 보기 편한 순서로 바꿀 수 있다. 기능만 놓고 보면 이미 제법 서비스다.
그런데도 막상 화면을 보면 어딘가 아쉬울 수 있다. 버튼은 눌러도 되는지 모르겠고, 카드 사이 간격은 들쭉날쭉하고, 입력창은 어디를 봐야 할지 애매하고, 검색 결과가 없을 때는 화면이 갑자기 썰렁해진다. 이런 순간 사용자는 기능이 없는 것보다 더 먼저 "이거 써도 괜찮나?"라는 불안을 느낀다.
바로 여기서 UI를 다듬는 일이 중요해진다. UI는 단순히 색을 예쁘게 칠하는 장식이 아니다. 사용자가 망설이지 않도록 길을 정리하는 작업에 가깝다. 좋은 UI는 "와, 화려하다"보다 "어? 그냥 자연스럽게 되네"라는 반응을 만든다.
이번 장의 핵심은 분명하다. 예쁘기만 한 화면보다 덜 헷갈리는 화면이 좋은 서비스다.
UI 다듬기는 무엇을 바꾸는 일일까
초보자는 UI 개선이라고 하면 보통 색상부터 떠올린다. 하지만 실제로 체감을 크게 바꾸는 것은 훨씬 기본적인 것들이다. 간격, 정렬, 글자 크기, 버튼 상태, 안내 문구, 빈 화면 같은 요소들이다.
같은 기능이라도 다음과 같은 차이가 생긴다.
요소 사이 간격이 일정하면 화면이 정돈되어 보인다.
버튼에 상태가 있으면 지금 눌러도 되는지 알 수 있다.
색이 제멋대로 쓰이지 않으면 중요한 정보가 눈에 들어온다.
빈 화면에 안내 문구가 있으면 사용자가 고장으로 오해하지 않는다.
즉, UI 다듬기는 화면을 꾸미는 일이 아니라 사용자의 혼란을 줄이는 일이다.
먼저 간격부터 정리하자
서비스 화면을 처음 만들면 자주 생기는 일이 있다. 어떤 곳은 mt-2, 어떤 곳은 mt-7, 어떤 곳은 p-3, 어떤 곳은 p-5처럼 간격이 제각각 들어간다. 코드를 쓸 때는 그때그때 그럴듯해 보여도, 화면 전체로 보면 어딘가 어수선한 느낌이 난다.
간격은 눈에 잘 안 띄지만, 화면의 질서를 만드는 가장 강력한 요소다. 같은 크기의 블록도 간격이 안정적이면 훨씬 믿음직해 보인다.
처음에는 복잡하게 생각하지 말고, 간단한 규칙 하나만 정해도 충분하다. 예를 들어 바깥 여백은 넉넉하게, 카드 안쪽 여백은 일정하게, 제목과 본문 사이는 조금 좁게 두는 식이다. 중요한 것은 "왜 여기만 갑자기 다르지?"라는 지점을 줄이는 것이다.
예를 들어 글 목록 페이지를 이렇게 정리할 수 있다.
<main className="mx-auto max-w-3xl px-4 py-10">
<section className="space-y-2">
<h1 className="text-3xl font-bold">글 목록</h1>
<p className="text-sm text-gray-600">
원하는 글을 편하게 찾고 읽어 보세요.
</p>
</section>
<section className="mt-8">
<PostListExplorer />
</section>
</main>여기서 핵심은 특별한 기술이 아니다. 제목과 설명은 서로 가깝게 두고, 다음 덩어리와는 조금 더 벌려 준다. 이렇게만 해도 정보의 묶음이 자연스럽게 보인다.
간격을 정할 때는 화면을 마치 방 정리하듯 생각하면 쉽다. 관련 있는 물건은 가까이 두고, 다른 목적의 물건은 조금 떨어뜨려 놓는 것이다.
글자에도 계층이 필요하다
사용자는 화면을 한 글자씩 읽지 않는다. 먼저 훑고, 중요한 부분만 읽는다. 그래서 글자에는 크기와 굵기, 색으로 만든 계층이 필요하다.
제목, 본문, 보조 설명, 경고 문구가 모두 같은 힘으로 보이면 사용자는 어디부터 읽어야 할지 모른다. 반대로 중요한 것과 덜 중요한 것이 분명하면 화면을 이해하는 속도가 빨라진다.
입문 단계에서는 다음 정도만 구분해도 충분하다.
화면 제목은 크고 진하게
본문 설명은 읽기 편한 크기로
날짜, 작성자, 도움말 같은 보조 정보는 조금 작고 옅게
에러나 주의 문구는 색과 위치로 분명하게
예를 들어 카드 안의 정보도 이런 식으로 역할을 나눌 수 있다.
<article className="rounded-2xl border bg-white p-5 shadow-sm">
<p className="text-xs text-gray-500">김민지 · 2026.04.23</p>
<h2 className="mt-2 text-xl font-semibold text-gray-900">
AI와 함께 첫 배포하기
</h2>
<p className="mt-3 text-sm leading-6 text-gray-700">
배포가 처음인 사람도 따라갈 수 있도록 전체 흐름을 쉽게 정리했습니다.
</p>
</article>이 코드에서 가장 중요한 것은 text-xl이나 leading-6 자체가 아니다. 제목이 제목답게, 설명이 설명답게 보인다는 점이다. 사용자는 이런 작은 차이 덕분에 화면을 덜 힘들게 읽는다.
색상은 많이 쓰는 것보다, 역할을 나눠 쓰는 것이 중요하다
초보자가 UI를 꾸밀 때 가장 흔히 하는 실수 중 하나는 색을 너무 많이 쓰는 것이다. 파랑도 예쁘고, 보라도 예쁘고, 초록도 좋아 보여서 이것저것 넣다 보면 화면이 갑자기 문구점처럼 활기차진다. 문제는 활기찬데 불친절하다는 것이다.
색은 감성도 만들지만, 그보다 먼저 의미를 만든다. 그래서 색을 고를 때는 "예쁜가?"보다 "무슨 역할인가?"를 먼저 생각하는 편이 좋다.
처음 서비스에서는 보통 이 정도면 충분하다.
기본 텍스트와 배경에 쓰는 중립 색
주요 버튼과 링크에 쓰는 대표 색
에러나 삭제처럼 주의가 필요한 행동에 쓰는 경고 색
예를 들어 주요 버튼은 같은 파란색 계열을 쓰고, 삭제 버튼만 빨간색 계열을 쓰면 사용자는 별도 설명이 없어도 위험도를 감지한다. 반대로 저장 버튼, 수정 버튼, 정렬 버튼, 태그 버튼이 모두 다른 강한 색이면 무엇이 더 중요한지 알기 어려워진다.
간단한 예시는 이렇다.
<button className="rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
글 작성하기
</button>
<button className="rounded-lg border border-red-200 bg-red-50 px-4 py-2 text-sm font-medium text-red-700 hover:bg-red-100">
삭제하기
</button>이때 중요한 것은 파란색 코드값을 외우는 일이 아니다. 같은 역할에는 같은 분위기의 색을 쓰는 습관이다.
버튼은 생김새보다 상태가 중요하다
버튼은 화면에서 가장 많이 상호작용하는 요소다. 그런데 초보자가 만든 첫 화면을 보면 버튼이 늘 같은 표정인 경우가 많다. 평소에도 똑같고, 마우스를 올려도 똑같고, 비활성 상태여도 똑같고, 저장 중이어도 똑같다.
이러면 사용자는 혼란스러워진다. "지금 눌러도 되나?", "이미 처리 중인가?", "왜 안 되지?" 같은 질문이 생긴다.
좋은 버튼은 디자인보다 상태 전달이 분명하다. 최소한 다음 상태는 표현해 주는 편이 좋다.
기본 상태: 평소에 보이는 모습
호버 상태: 마우스를 올렸을 때 반응
비활성 상태: 아직 누를 수 없을 때 흐리게
로딩 상태: 처리 중임을 텍스트나 아이콘으로 표시
예를 들어 저장 버튼을 이렇게 다듬을 수 있다.
type SubmitButtonProps = {
isLoading: boolean
disabled?: boolean
}
export default function SubmitButton({ isLoading, disabled }: SubmitButtonProps) {
const isDisabled = disabled || isLoading
return (
<button
type="submit"
disabled={isDisabled}
className="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-gray-300"
>
{isLoading ? '저장하는 중...' : '저장하기'}
</button>
)
}이 버튼은 화려하지 않지만 친절하다. 로딩 중이면 눌러지지 않고, 텍스트도 바뀌며, 비활성 상태도 눈에 보인다. 사용자는 "왜 반응이 없지?" 대신 "아, 지금 처리 중이구나"라고 이해할 수 있다.
입력 폼은 '무엇을 적는지'보다 '어떻게 적으면 되는지'도 알려줘야 한다
폼을 만들 때 흔히 입력칸만 놓고 끝내기 쉽다. 하지만 사용자는 단지 빈 칸을 보는 순간부터 질문을 시작한다. "여기에 제목만 쓰면 되나?", "몇 글자까지 가능하지?", "오류가 나면 어디를 고쳐야 하지?" 같은 질문이다.
그래서 폼은 입력 자체만 받는 도구가 아니라, 작성 방법을 안내하는 도구여야 한다.
좋은 폼은 보통 이런 요소를 갖는다.
라벨: 이 칸이 무엇인지 분명하게 설명
도움말: 어떤 형식으로 쓰면 좋은지 짧게 안내
에러 메시지: 틀렸을 때 바로 아래에서 수정 방향 제시
버튼 상태: 작성 중, 저장 중, 저장 불가 상태를 구분
예를 들어 제목 입력칸은 이렇게 만들 수 있다.
<div className="space-y-2">
<label htmlFor="title" className="text-sm font-medium text-gray-800">
글 제목
</label>
<input
id="title"
name="title"
placeholder="예: 바이브 코딩으로 만든 첫 회고 서비스"
className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm outline-none transition focus:border-blue-500 focus:ring-2 focus:ring-blue-100"
/>
<p className="text-xs text-gray-500">
다른 사람이 제목만 보고도 내용을 짐작할 수 있게 써 보세요.
</p>
</div>이런 구조가 좋은 이유는 사용자가 혼자 추측하지 않아도 되기 때문이다. UI가 먼저 말을 걸어 주기 시작한다.
빈 화면은 서비스의 성격이 드러나는 곳이다
빈 화면은 종종 잊히는 요소다. 데이터가 많을 때만 열심히 디자인하고, 아무것도 없을 때는 그냥 하얗게 비워 두기 쉽다. 하지만 실제 사용자 경험에서 빈 화면은 아주 자주 등장한다.
처음 가입한 사용자는 아직 글이 없다. 검색어를 너무 좁게 입력하면 결과가 없을 수 있다. 필터를 적용했더니 해당 항목이 하나도 없을 수도 있다. 이때 아무 안내가 없으면 사용자는 서비스가 텅 빈 것인지, 내가 뭔가 잘못한 것인지, 아니면 고장 난 것인지 구분하지 못한다.
빈 화면은 단순히 "없음"을 보여주는 곳이 아니라, 다음 행동을 알려주는 곳이어야 한다.
예를 들어 이렇게 다듬을 수 있다.
type EmptyStateProps = {
title: string
description: string
actionLabel?: string
}
export default function EmptyState({ title, description, actionLabel }: EmptyStateProps) {
return (
<div className="rounded-2xl border border-dashed bg-gray-50 px-6 py-10 text-center">
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
<p className="mt-2 text-sm leading-6 text-gray-600">{description}</p>
{actionLabel ? (
<button className="mt-5 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
{actionLabel}
</button>
) : null}
</div>
)
}그리고 상황에 따라 문구를 다르게 쓸 수 있다.
첫 글이 없을 때: "아직 작성한 글이 없어요. 첫 글을 올려 보세요."
검색 결과가 없을 때: "검색 결과가 없어요. 다른 단어로 다시 찾아보세요."
필터 결과가 없을 때: "조건에 맞는 글이 없어요. 필터를 다시 선택해 보세요."
같은 빈 화면이라도 사용자가 왜 비었는지 이해할 수 있으면 훨씬 덜 답답하다.
카드와 목록은 '읽기 쉬움'과 '누르기 쉬움'을 함께 봐야 한다
목록 화면은 정보를 빠르게 훑는 공간이다. 그래서 카드 디자인은 멋짐보다 가독성과 클릭 편의가 먼저다.
예를 들어 카드 안에 정보가 너무 많으면 한눈에 들어오지 않는다. 반대로 너무 적으면 어떤 글인지 감이 안 잡힌다. 입문 단계에서는 제목, 요약, 작성자, 날짜 정도만 깔끔하게 보여주는 편이 좋다.
또 카드 전체를 눌러 이동하게 만들었다면, 사용자가 어디를 눌러도 자연스럽게 반응해야 한다. 제목만 작게 링크 처리되어 있으면 클릭 가능한 영역을 찾느라 은근히 피곤하다.
카드를 다듬을 때는 이런 부분을 확인해 보자.
제목이 가장 먼저 보이는가
보조 정보가 본문보다 너무 튀지 않는가
카드 안쪽 여백이 답답하지 않은가
클릭 가능한 영역이 충분한가
텍스트가 너무 길 때 화면이 무너지지 않는가
예를 들어 요약은 두세 줄만 보이도록 잘라서 카드 높이를 안정적으로 유지할 수 있다.
<p className="mt-3 line-clamp-2 text-sm leading-6 text-gray-700">
이 글은 Next.js와 Supabase를 이용해 작은 풀스택 서비스를 만드는 흐름을 쉽게 소개합니다.
</p>이런 디테일은 작아 보여도 목록 전체 인상을 꽤 크게 바꾼다.
그림자와 테두리는 '구분'용으로만 써도 충분하다
처음 UI를 만들면 카드마다 그림자를 강하게 넣고 싶을 때가 있다. 하지만 그림자와 테두리는 존재감을 과시하는 장식이라기보다, 영역을 구분하는 보조 도구로 쓰는 편이 좋다.
특히 입문 프로젝트에서는 화면 요소가 많지 않기 때문에, 살짝의 테두리와 약한 그림자만 있어도 충분히 정리된 느낌이 난다. 너무 진한 그림자는 오히려 화면을 무겁게 만든다.
예를 들어 이런 정도면 괜찮다.
className="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm"이 설정은 "여기 하나의 덩어리가 있다"는 걸 보여 주는 데 딱 알맞다. UI는 늘 더하는 것보다 덜어내는 쪽이 안정적일 때가 많다.
한 화면 안에서 스타일을 통일하자
UI가 어색해 보이는 가장 큰 이유 중 하나는 완성도가 낮아서가 아니라, 통일감이 없어서다. 버튼 모서리 반경은 어떤 곳은 둥글고 어떤 곳은 각지고, 입력창 높이는 제각각이고, 본문 글자 크기도 섞여 있으면 화면 전체가 급조한 느낌을 준다.
이 문제를 줄이는 가장 쉬운 방법은 자주 쓰는 스타일을 반복 가능한 규칙으로 묶는 것이다. 예를 들어 버튼은 모두 rounded-lg, 입력창은 모두 px-4 py-3, 카드 제목은 모두 text-xl font-semibold처럼 기준을 정해 두는 것이다.
Tailwind를 쓴다면 작은 공통 컴포넌트를 만드는 방식도 좋다. 예를 들어 Button, Input, EmptyState, SectionHeader 같은 컴포넌트를 만들어 두면 화면을 넓혀 갈수록 훨씬 편해진다.
초보자일수록 이 통일감의 힘을 크게 느낀다. 코드를 조금만 바꿔도 전체 화면의 인상이 한꺼번에 좋아지기 때문이다.
지금 단계에서 특히 조심할 것
UI를 다듬는다고 할 때, 욕심이 과해지면 오히려 화면이 불편해질 수 있다. 입문 단계에서는 다음 실수를 특히 조심하면 좋다.
첫째, 설명 없는 아이콘만 늘어놓는 것이다. 휴지통 아이콘, 연필 아이콘, 정렬 아이콘이 익숙한 사람도 있지만 초보 사용자에게는 뜻이 모호할 수 있다. 처음에는 텍스트를 함께 보여 주는 편이 안전하다.
둘째, 플레이스홀더를 라벨처럼 쓰는 것이다. 입력창 안에 흐린 글씨만 넣고 라벨을 없애면, 입력을 시작한 뒤에는 그 칸이 무엇이었는지 바로 잊어버리기 쉽다.
셋째, 비활성 버튼을 이유 없이 많이 만드는 것이다. 눌리지 않는 버튼이 많으면 서비스가 자꾸 거절하는 느낌을 준다. 비활성 상태가 필요하다면 왜 지금 안 되는지도 가까운 곳에서 알려주는 편이 좋다.
넷째, 모든 요소를 강조하는 것이다. 모든 텍스트가 진하고, 모든 카드가 튀고, 모든 버튼이 강한 색이면 결국 아무것도 강조되지 않는다.
UI는 '더 예쁘게'보다 '덜 시끄럽게'에 가깝다고 생각하면 훨씬 쉬워진다.
AI에게 UI 수정을 맡길 때는 이렇게 요청하자
UI 다듬기 작업은 Claude Code나 Codex와 함께하기 아주 좋다. 다만 "예쁘게 바꿔 줘"라고 말하면 결과가 들쭉날쭉해질 수 있다. 어떤 AI는 화려한 색을 많이 넣고, 어떤 AI는 구조를 크게 바꾸고, 어떤 AI는 지금 필요한 것보다 복잡한 애니메이션까지 추가할 수 있다.
그래서 요청은 미적인 취향보다 사용성 목표를 중심으로 주는 편이 좋다. 예를 들면 이렇게 말할 수 있다.
Next.js와 Tailwind 기준으로 글 목록 화면의 UI를 다듬어줘.
기능은 그대로 두고 사용성을 개선하는 것이 목표야.
간격을 정리하고, 제목/보조정보의 계층을 분명히 해줘.
주요 버튼과 삭제 버튼의 스타일을 구분해줘.
비활성 상태와 로딩 상태가 보이게 해줘.
검색 결과가 없을 때는 친절한 빈 화면을 추가해줘.
과한 애니메이션이나 화려한 색상은 넣지 말고,
초보자가 읽기 쉬운 깔끔한 스타일로 정리해줘.이 요청의 좋은 점은 결과의 방향이 분명하다는 것이다. AI는 디자인 감각 자체보다, "무엇을 우선해야 하는지"가 선명할수록 더 좋은 초안을 준다.
그리고 결과를 받은 뒤에는 반드시 직접 확인해야 한다. 특히 이런 질문을 던져 보면 좋다.
화면을 처음 봤을 때 어디를 먼저 읽어야 할지 보이는가?
버튼이 눌릴 수 있는 상태인지 한눈에 알 수 있는가?
빈 화면에서도 다음 행동이 자연스럽게 떠오르는가?
같은 역할의 요소들이 같은 스타일을 쓰고 있는가?
AI는 빠르게 다듬어 주지만, "덜 헷갈리는가"를 판단하는 사람은 결국 사용자 입장에서 보는 우리다.
다음 장으로 이어지는 준비
이번 장에서 다룬 내용은 대부분 데스크톱 화면에서도 바로 체감할 수 있다. 간격이 정리되고, 버튼 상태가 보이고, 빈 화면이 친절해지면 서비스 완성도가 훨씬 높아진다.
하지만 여기서 끝은 아니다. 데스크톱에서 보기 좋게 만든 화면이 모바일에서는 갑자기 답답해질 수 있다. 버튼이 너무 작거나, 카드가 너무 넓거나, 검색창과 정렬 메뉴가 한 줄에 우겨 들어갈 수도 있다.
그래서 다음 장에서는 지금 다듬은 UI를 바탕으로, 모바일에서도 잘 보이고 잘 눌리게 만드는 방법을 이어서 살펴볼 것이다.
마무리
이번 장에서 우리는 대단히 복잡한 기술을 배우지 않았다. 그 대신 서비스가 서비스답게 느껴지게 만드는 기본기를 익혔다.
간격은 화면의 질서를 만들고, 글자 계층은 읽는 순서를 만들고, 색은 역할을 구분하고, 버튼 상태는 상호작용의 불안을 줄이고, 빈 화면은 사용자에게 다음 행동을 알려 준다. 각각은 작은 조각이지만, 합쳐지면 서비스 인상을 크게 바꾼다.
처음 만드는 웹 서비스는 종종 "기능은 되는데 좀 어설픈 느낌"이 난다. 그 차이를 만드는 것이 바로 이런 UI 디테일이다. 그리고 좋은 소식이 하나 있다. 이 디테일은 타고난 감각보다 관찰하고 정리하는 습관으로 훨씬 더 빨리 좋아진다.
지금부터는 화면을 볼 때 이렇게 질문해 보자.
"이건 예쁜가?"보다 먼저, "이건 덜 헷갈리는가?"
그 질문을 붙잡는 순간, 당신의 서비스는 한 단계 더 사용자 가까이 다가가기 시작한다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.