🇬🇧 English version: Why AI-Generated UIs Look 'Off' — and the One Principle That Fixes It
겪어보셨을 겁니다. AI한테 대시보드를 만들어 달라고 하면, 나오긴 나와요. 그런데… 묘하게 어색합니다. 컴포넌트 하나하나는 멀쩡해요. 버튼은 버튼답고, 카드는 카드답죠. 그런데 전체를 보면 "생성된 티" 가 납니다. UI를 찍은 스톡 사진 같달까요.
저도 한동안은 "컴포넌트를 더 잘 만들면" 또는 "프롬프트에 감각을 더 넣으면" 해결될 거라 생각했습니다. 아니더군요. 실제 디자인 문헌 — Refactoring UI, Material Design 3, Apple HIG, IBM Carbon, WCAG, Financial Times의 시각 어휘집 — 을 파고들수록 진짜 원인엔 이름이 있었습니다.
정합성(coherence)의 부재.
부품들이 서로 안 맞는 겁니다. 그리고 한번 보이기 시작하면, 다시는 안 보이게 할 수가 없어요.
전에 디자이너 없이 바이브코딩으로 토스급 UI 만드는 법을 다뤘는데, 이번 글은 그 아래의 '왜' — "어색함"의 실제 메커니즘과 그걸 고치는 한 가지 원칙을 팝니다.
증상: 서로 안 맞는 부품들
다음 UI엔 "틀린" 게 하나도 없습니다.
- 모서리가 각진(2px) 카드
- 그 안의 알약 모양 버튼
- 파란 기본 버튼 옆의 보라 "업그레이드" 링크
- 아이콘: Lucide 아웃라인 2개 + 채워진 이모지 1개
- 좌상단에서 떨어진 그림자 하나, 정아래에서 떨어진 그림자 하나
하나씩 떼어 보면 전부 정당화됩니다. 그런데 합치면 "부품 조립" 신호가 떠요. 사람 디자이너라면 같은 화면에서 이 결정들을 동시에 내리지 않거든요. 디자이너는 모서리 스타일 하나, 강조색 하나, 아이콘 세트 하나, 광원 하나 를 고르고 — 그걸 어디서나 반복합니다.
그 반복이 우리가 "한 사람이 만든 느낌"으로 읽는 정체입니다. UX Collective의 Goran Paun은 이걸 시각적 정합성이라 부르고, Steve Schoger의 Refactoring UI 전체가 어떤 의미에선 "불필요한 변주를 만들지 마라"는 책이죠. 원칙 자체는 오래됐습니다. 새로운 건 AI가 기본값으로 이걸 깨뜨린다는 점이고, 왜 깨는지를 알면 어떻게 고칠지가 보입니다.
AI가 유독 이걸 못 하는 이유
언어 모델은 UI를 국소적으로(locally) 생성합니다. 카드 쓰고, 버튼 쓰고, 모달 쓰고 — 각각을 그럴듯한 새 조각으로 따로 만들어요. "40줄 위의 버튼이 알약 모양이었으니 이것도 알약이어야 한다"는 기억이 없습니다. 각 컴포넌트는 자기 학습 데이터의 평균값으로 회귀하는데, "버튼의 평균"과 "카드의 평균"은 애초에 서로 맞춰진 적이 없죠.
사람은 이렇게 일하지 않습니다. 우리는 머릿속에 아주 작은 결정 집합 — 이 제품은 둥근 모서리, 파란 강조색, 8px 그리드 — 을 들고 다니고, 새 요소는 전부 그걸 물려받습니다. 그 결정이 끈끈하게(sticky) 따라붙어요.
그래서 해법은 "모델을 더 감각 있게" 가 아닙니다. 그 끈끈한 결정들을 적어두고, 모델에게 새로 만들지 말고 복사하라고 시키는 것 입니다.
메타 법칙: 축마다 값 하나
핵심은 한 문장입니다.
디자인 축마다 값(또는 패밀리)을 하나만 정하고, 토큰으로 박아서, 어디서나 적용한다.
정합성은 "모든 화면이 똑같다"가 아닙니다. 같은 결정이 반복되는 거예요. 설정 페이지와 대시보드는 전혀 다르게 생겨도 — 모서리 personality, 그림자 언어, 강조색, 간격 단위만 공유하면 — 한 제품처럼 느껴집니다.
통일돼야 하는 축들:
| 축 | 하나만 정한다 | 섞이면 |
|---|---|---|
| 모서리/라운드 | 각짐 0–4px · 부드러움 8–12px · 알약 9999px | 각진 다이얼로그 + 둥근 버튼 = "두 제품 붙인 느낌" |
| 그림자 | 한 스케일, 한 광원(좌상단), 한 색조 | "태양이 둘인 장면" |
| 강조색 | 강조는 1개 (+ 의미색 red/green/amber) | 무엇도 그 액션으로 안 읽힘 |
| 간격 단위 | 8px 그리드 하나 (4px 하프스텝) | 7/13/19px 같은 오프그리드 = "엉성함" |
| 아이콘 | 한 패밀리, 한 채움, 한 굵기 | 세트 섞이면 "겉도는" 느낌 |
| 타입 스케일 | 모듈러 스케일 하나, 폰트 ≤2종 | 임의 크기가 리듬을 깸 |
| 모션 | 지속시간 세트 하나 + 이징 패밀리 하나 | 어떤 건 빠릿, 어떤 건 굼떠 = 다른 앱 |
| 컨트롤 높이 | 공유 높이 세트 (예: 40px) | 44px 인풋 옆 32px 버튼 = 베이스라인 깨짐 |
비전공자가 핵심을 가장 잘 찌른 표현:
"모서리를 각지게 했으면 다 각지게 가야지."
정확합니다. 그리고 이건 위 표의 모든 행에 그대로 적용돼요. 축이 섞이는 건 스타일 선택이 아니라 lint 에러로 취급하세요.
이 중 넷을 코드로 구체화해 봅시다.
1. 모서리 personality 하나 (그리고 중첩 법칙)
먼저, 라운드는 토큰이지 매직 넘버가 아닙니다. 작은 스케일을 정의하고 어디서나 참조하세요.
:root {
--radius-sm: 8px; /* 인풋, 버튼 */
--radius-md: 12px; /* 카드, 메뉴 */
--radius-lg: 16px; /* 모달, 시트 */
--radius-full: 9999px;
}
그다음 personality 하나 — 전부 각지게, 전부 부드럽게, 전부 알약 — 를 골라 카드/버튼/인풋/모달/이미지에 적용합니다. 모델이 rounded-none 패널에 rounded-full 버튼을 멋대로 섞게 두지 마세요.
많이들 놓치는 두 번째 규칙: 중첩된 라운드는 중심을 공유해야 합니다. 둥근 컨테이너 안에 패딩을 두고 둥근 요소가 들어갈 때, 안쪽 반지름은 더 작아야 해요.
안쪽 반지름 = 바깥 반지름 − 패딩
.card {
--pad: 16px;
border-radius: var(--radius-lg); /* 16px */
padding: var(--pad);
}
.card > .thumbnail {
/* 16 − 16 = 0; 음수가 안 되게 clamp */
border-radius: max(0px, calc(var(--radius-lg) - var(--pad)));
}
안쪽 요소에 바깥과 같은 라운드를 주면, 코너가 컨테이너의 호를 비집고 튀어나옵니다. Apple의 새 "Liquid Glass"가 바로 이 동심원 코너 수학을 공식화했고, Cloud Four에 정석 글이 있습니다.
2. 단일 광원의 레이어드 그림자
깊이를 가짜로 보이게 하는 가장 빠른 방법이 단일 하드 box-shadow입니다. 진짜 그림자는 그라데이션(반음영)이라, 낮은 불투명도 레이어를 여러 겹 쌓고 — 결정적으로 — 검정 대신 표면 색조 쪽으로 살짝 틴트하세요. 순수 검정은 탁해집니다.
/* 카드 — 페이지에 가깝게 */
--shadow-md:
0 1px 2px hsl(220 40% 20% / 0.08),
0 2px 4px hsl(220 40% 20% / 0.08),
0 4px 8px hsl(220 40% 20% / 0.08);
/* 모달 — 사용자 쪽으로 떠오르게 */
--shadow-xl:
0 2px 4px hsl(220 40% 20% / 0.06),
0 8px 16px hsl(220 40% 20% / 0.06),
0 16px 32px hsl(220 40% 20% / 0.06),
0 32px 64px hsl(220 40% 20% / 0.06);
정합성 규칙: 페이지 전체가 한 광원 (관례상 위 + 약간 왼쪽), 수직 오프셋은 수평의 약 2배. 고도가 올라가면 오프셋·블러는 커지고 불투명도는 낮아집니다. 그러면 카드와 모달이 같은 빛이 다른 높이의 물체에 닿은 걸로 읽혀요 — 별개의 두 효과가 아니라. (Josh Comeau, Tobias Ahlin 글이 좋습니다.)
다크 모드에선 그림자가 거의 안 보이므로 톤 기반 고도로 전환하세요 — 높을수록 밝은 표면. 베이스는 절대 순수 #000 금지(Material은 #121212 + 레벨별 화이트 오버레이 권장).
3. 강조색 하나, 나머지는 그레이스케일
"AI 무지개" UI는 대개 강조가 필요할 때마다 새 색을 꺼내 써서 생깁니다. 규율:
- 상호작용 강조는 하나의 강조색.
- 텍스트·표면·테두리는 틴트된 그레이 램프 (순수 회색 말고 — 브랜드 색조 쪽으로 5–15% 밀기).
- 의미색은 딱 넷 — success / warning / error / info — 이고 오직 의미로만, 장식으로는 절대 안 씀.
:root {
--accent: #5b5bd6;
/* 채도 0 회색이 아니라 강조색 쪽으로 살짝 틴트 */
--grey-50: hsl(240 20% 98%);
--grey-200: hsl(240 14% 90%);
--grey-500: hsl(240 8% 55%);
--grey-900: hsl(240 12% 14%); /* 본문 텍스트 — #000 아님 */
}
그리고 타협 불가 접근성 하한선(WCAG 2.2 AA): 본문 텍스트 4.5:1, 큰 텍스트·UI 요소 3:1. 정보를 색으로만 전달 금지 — 아이콘·텍스트·형태와 함께. 유효성 실패 필드에 빨간 테두리만으론 부족하고, 아이콘과 메시지를 같이 넣으세요.
4. 간격 그리드 하나, 그룹핑은 근접성으로
모든 margin·padding·gap을 한 스케일 — 4, 8, 12, 16, 24, 32, 48, 64 — 에 스냅하고, 근접성이 의미를 나르게 하세요. Gestalt(그리고 Refactoring UI의 "모호한 간격을 피하라")의 규칙: 그룹 바깥 공간이 그룹 안 공간보다 확실히 커야 합니다.
폼의 구체적 사다리:
라벨 → 인풋 : 4–8px
인풋 → 인풋 : 12–16px
그룹 → 그룹 (섹션) : 24–32px
전부 균일하게 띄우면 눈이 무엇이 한 덩어리인지 알 수 없습니다. 단계마다 두 배로 벌어지는 게, 라벨이 자기 필드에 붙고 섹션이 다음 섹션과 분리되게 만드는 핵심이에요.
기계가 스스로 점검하게
결정을 적어두는 게 절반이고, 나머지 절반은 위반을 잡는 겁니다. 여기서 AI가 실제로 쓸모 있어요 — 채점 기준만 쥐여주면.
저는 위 내용을 Claude Code·Cursor가 자동으로 읽는 오픈소스 디자인 엔진(StyleSeed)에 넣지만, 아이디어 자체는 도구 무관입니다. 가장 쓸모 있던 조각은 정합성 채점기 — 파일을 점수화하고 섞인 축을 실제 감점으로 잡는 검사였습니다.
출력 예시(축약):
## Design Score: 70 / 100 (Dashboard.tsx)
Color discipline 13/18 ▓▓▓░ #000 제목; 강조색 3개
Cards & elevation 8/12 ▓▓░░ 1px 테두리가 분리를 담당
Coherence 6/12 ▓▓░░ 각진 카드(l.22) + 알약 버튼(l.48); 강조 3개
### 먼저 고칠 것 (점수 이득 큰 순)
1. 라운드 통일(부드러움 8–12px) + 강조색 1개로 → +9
2. 주문 리스트에 empty + loading 상태 추가 → +7
3. 1px 테두리 빼고 톤 + ≤8% 그림자 → +4
"Coherence" 항목이 "AI 생성 티"를 가장 잘 예측합니다. 컴포넌트별 예쁨이 아니라 전역 일관성을 재기 때문이에요. 컴포넌트는 예뻐도 틀릴 수 있습니다 — 이웃과 안 맞으면.
결론
하나만 기억한다면:
예쁜 부품이 예쁜 UI를 만들지 않습니다. 서로 맞는 부품이 만듭니다.
축마다 값 하나 — 라운드, 그림자, 강조색, 간격, 아이콘, 타입, 모션 — 를 골라 토큰으로 적어두고, 새 요소가 매번 새로 고르는 대신 그걸 물려받게 하세요. 그 한 가지 규율이 "로봇이 만들었네"와 "디자이너가 만들었네"를 가르고, AI가 만든 인터페이스에 할 수 있는 가장 싸고 효과 큰 일입니다.
모서리가 각지다고요? 그럼 전부 각지게. 끝까지.
숫자까지 들어간 풀버전 — 간격 수치, 앱 종류별 타입 레시피, 레이어드 그림자·중첩 라운드 레시피, 전부 Refactoring UI / Material 3 / Apple HIG / WCAG 근거 — 은 오픈소스(MIT)로 여기 있습니다: github.com/bitjaru/styleseed. ⭐ 하나가 더 많은 개발자(와 AI 도구)에게 닿게 합니다.
🇬🇧 The English version of this post is here.
Top comments (0)