이 페이지 목차

디자인 지침

ranui 를 실제 화면에 적용하는 법입니다. 역할과 상태를 고르고, 값은 토큰에 맡기세요.

규칙 자체는 두 곳에 있고, 이 페이지는 내용을 되풀이하지 않고 그쪽으로 연결합니다. 어떤 제품에나 통하는 방법은 디자인 가이드에, ranui 의 토큰·단계·컴포넌트 규칙은 ranui 구현 어댑터에 있습니다. 둘 다 node_modules/ranui/docs/DESIGN.md에 함께 배포됩니다. 모든 토큰과 그 값은 디자인 시스템에, 실행 중 덮어쓰기는 테마에, 페이지 구조는 정보 구조에 있습니다.

이럴 때 씁니다. <r-*> 요소로 만든 화면의 색, 간격, 글자, 깊이, 모션을 고를 때.

원칙

  1. 개성보다 명료함. 먼저 주된 과제와 주된 동작이 헷갈릴 여지 없이 드러나야 합니다.
  2. 기존 컨트롤을 재사용하세요. r-button, r-input, r-select, r-modal은 폼, 키보드, 접근성 동작을 이미 갖추고 있습니다. 다시 만들지 말고 스타일을 바꾸세요.
  3. 역할과 상태로 정하세요. "이 글자는 무엇인가"에는 답이 있지만, "어느 크기가 좋아 보이나"에는 답이 없습니다. 역할에 맞는 토큰이 값을 정하고, 그 값은 테마를 따라갑니다.
  4. 닿을 수 있는 모든 상태를 설계하고, 결과를 보세요. 기본 상태는 여덟 중 하나일 뿐이고, 아무도 못 보는 그림자는 브라우저에서 봐야만 드러납니다.

이것들은 출발점입니다. 화면이 더 나아진다면 벗어나도 되지만, 그 이유를 밝히세요.

색 고르기

호버와 활성이 어떤 모습인지는 사다리가 이미 정해 두었습니다. 여러분의 몫은 역할에 이름을 붙이는 일입니다.

이 요소는… 이것을 쓰세요
페이지나 면의 배경 --ran-color-bg / -bg-subtle / -bg-elevated / -bg-muted
포인터 아래 / 눌리는 중 --ran-color-bg-hover / -bg-active
글자 --ran-color-text / -text-secondary / -text-disabled
테두리 --ran-color-border / -hover / -active
이 화면이 존재하는 이유인 동작 --ran-color-primary(그 위에 --ran-color-primary-text)
상태 --ran-color-success / -warning / -danger
링크 --ran-color-link

강조색은 저마다 한 가지 뜻만 가집니다. 프라이머리는 무채색이라 파랑은 링크와 포커스 링 몫으로 남겨 둡니다. 초록은 성공, 호박색은 경고, 빨강은 위험입니다. 강조하려고 빨강을 쓰면, 나중에 "위험"을 말할 색이 남지 않습니다.

날 16 진수 값, 테마를 따라 바뀌지 않는 대체값, 존재하지 않는 토큰을 가리키는 대체값은 다크 모드에서 조용히 깨지거나 선언 전체를 무효로 만듭니다. 각각의 이유는 어댑터의 색 규칙이 설명하고, ranui 자체 소스에서는 verify:design이 이를 잡아냅니다.

간격과 리듬

간격은 4px 스케일에서 가져오고, 거리가 뜻을 지니게 하세요.

  • 한 묶음 안의 요소 사이는 8px.
  • 묶음과 묶음 사이는 16px.
  • 구역과 구역 사이는 24–32px.

간격이 좁을수록 관계가 가깝다는 뜻이므로, 스케일을 벗어난 간격 하나만으로도 리듬이 깨집니다. 간격마다 책임지는 곳을 하나로 두세요. 둘 사이에 세 번째 여백을 더하기 전에 자식의 margin 과 부모의 padding 부터 세어 보세요.

글자 고르기

그 글자가 어떤 역할을 하는지 (제목, 레이블, 본문, 버튼, 고정폭) 물으면 글꼴·크기·굵기·행간이 타입 스케일에서 따라 나옵니다. 동영상 위의 제스처 표시처럼 정말 한 번뿐인 글자는 가장 가까운 역할에 억지로 끼워 맞추기보다 전용 컴포넌트 토큰을 주는 편이 낫습니다.

깊이: 그림자와 쌓임

그림자 단계는 요소가 무엇인지로 고릅니다. 흐름 안의 면인지, 떠 있는 오버레이인지, 조작을 막는 대화 상자인지. 카드 단계로 떨어진 오버레이는 페이지에 박혀 있는 것처럼 보입니다.

여러분의 틀 안에 ranui 오버레이를 품을 때. z-index 사다리는 1000 에서 시작하므로, 포털된 오버레이는 평범한 페이지 틀을 스스로 넘어섭니다. 하지만 자기 섀도 DOM 안에 남아 있는 position: fixed 오버레이 (r-modal의 대화 상자) 는 가장 가까운 조상의 쌓임 맥락까지만 빠져나갑니다. 품은 내용을 쌓임 맥락을 만드는 것 (isolation, opacity < 1, transform, filter, will-change) 으로 감쌌다면, 오버레이가 실제로 열려 있는 동안에만 그 껍데기를 올리세요.

.embed {
  isolation: isolate; /* cheap: no z-index of its own, so nothing is promoted */
}
/* Escalate only while a real overlay is open — never "just in case" */
.embed:has(r-modal[open]),
.embed:has(r-modal[closing]) {
  position: relative;
  z-index: 100;
}

껍데기에 z-index를 늘 걸어 두면 정적인 내용까지 포함해 안의 모든 것이 스크롤 내내 고정 헤더 위로 올라옵니다. 이 사이트도 한 번 그렇게 배포된 적이 있습니다. open뿐 아니라 closing도 맞추세요. 마스크는 닫히는 트랜지션 동안에도 계속 그려집니다.

모션

자주 일어나는 피드백 (호버, 포커스, 누름) 은 즉시 반응합니다. 모션으로 변화를 설명할 때는 피드백은 0–100ms 안에, 들어올 때는 250–400ms 동안 감속하며, 나갈 때는 150–200ms 동안 가속합니다. 중단할 수 있게 하고 prefers-reduced-motion을 존중하세요.

색 속성은 절대 트랜지션하지 마세요. CSS 는 색이 왜 바뀌었는지 알지 못합니다. 그래서 background-color, color, border-color, box-shadow, fill, stroke에 건 transition은 테마가 바뀔 때도 발동하고, 페이지는 이미 바뀌었는데 요소마다 제 속도로 서서히 물듭니다. 대신 transform과 opacity를 애니메이션하세요. transition: all과 맨 transition: 0.2s도 색을 포함합니다.

상태와 문구

닿을 수 있는 상태는 모두 디자인의 일부입니다. 호버, 활성, 포커스, 비활성, 로딩, 비어 있음, 오류. 호버는 bg-hover / border-hover, 활성은 bg-active, 포커스는 포커스 링에 대응합니다. 누를 수 없는 것이 누를 수 있어 보이면 안 됩니다. r-card는 hoverable이 있을 때만 호버에 반응합니다.

문구도 시스템의 일부입니다.

  • 버튼은 결과를 나타내고, 문맥에서 대상이 드러나지 않으면 대상도 씁니다. "멤버 삭제". 대상이 분명한 대화 상자라면 "삭제"로 충분합니다. 막연한 "확인"은 피하세요.
  • 오류는 무슨 일이 있었는지 말하고, 이어서 어떻게 고치는지 말합니다. ✅ "빌드 실패: 번들이 크기 한도를 넘었습니다. 줄이거나 한도를 올리세요." ❌ "작업에 실패했습니다. 다시 시도해 주세요."
  • 확인 알림은 성공이 아니라 변화를 말하고, 변화가 확정된 뒤에만 나타납니다. ✅ "프로젝트를 삭제했습니다" ❌ "작업에 성공했습니다".
  • 맥락이 군더더기를 덜어 내게 하세요. "프로젝트 삭제"라는 제목의 대화 상자에 "프로젝트를 영구히, 완전히 삭제"라는 버튼은 필요 없습니다.

접근성

  • 글자와 배경의 대비는 WCAG AA를 만족시키세요.
  • 상태를 색만으로 알리지 마세요. 아이콘이나 레이블, 글과 짝지으세요.
  • 조작할 수 있는 모든 요소는 보이는 포커스 링을 지킵니다 (--ran-focus-ring, 또는 outline: 2px solid var(--ran-color-focus); outline-offset: 2px).
  • 모든 것은 키보드로 닿을 수 있어야 합니다.
  • prefers-reduced-motion과 prefers-color-scheme을 존중하세요.

마우스와 손끝, 좁은 화면과 넓은 화면

입력 방식도 뷰포트도, 어느 한쪽이 뒷전인 대상은 아닙니다.

  • 드래그, 슬라이더, 제스처는 Pointer Events를 쓰고, 정확히 드래그하는 면에 touch-action: none을 겁니다. 뒤에 포인터 핸들러가 없는 touch-action: none은 망가진 컨트롤입니다.
  • 호버로만 드러나는 단서에는 탭 대안이 필요합니다. r-select나 r-popover의 trigger="hover"는 터치 기기에서 클릭이 됩니다. 여러분이 만드는 것도 그래야 합니다.
  • 브레이크포인트를 새로 만들기보다 뷰포트 기준 크기(min(560px, calc(100vw - 32px)), clamp()) 를 쓰세요. ranui 에는 브레이크포인트 토큰이 없으니, 고정 브레이크포인트 하나하나가 누군가 관리해야 하는 숫자가 됩니다.
  • 모바일에서 무언가를 하는 유일한 방법을 숨기지 마세요. 대신 재배치하세요.
  • 측정한 위치는 다음 리플로까지만 맞습니다. getBoundingClientRect()에서 나온 값은 창 크기 변경, 컨테이너 리플로, 그리고 (포털된 패널이라면) 스크롤 때 낡아집니다. 좁은 폭에서 페이지를 불러오는 것으로는 그 폭으로 줄여 가는 경우를 시험하지 못하는데, 깨지는 곳은 바로 거기입니다.

라이브러리가 기계적으로 강제하는 것

pnpm -F ranui verify:design은 ranui 자체 소스에서 아홉 가지 규칙을 검사합니다. 테마를 따라 바뀌지 않는 색 대체값, 날 색 리터럴, 간격과 크기 스케일, 마우스 전용 드래그, hidden을 망가뜨리는 :host 규칙, 선언되지 않은 토큰을 가리키는 대체값, 자기 섀도 트리를 뒤지는 컴포넌트, 생성자 밖에서 만든 섀도 트리입니다. 일부러 규칙을 어기는 줄에는 design-ok: <rule> <reason>으로 그 사실을 적습니다. 검사는 설정된 소스 루트만 다룹니다. ranui 를 쓰는 앱은 자기 검사를 돌리고 자기 페이지를 직접 봐야 합니다.

디자인 예시

ranui 기본 토큰으로 그린 도식이며 실제 컴포넌트가 아닙니다.

행의 정렬 유지하기

한 행에 내용이 추가되어도 텍스트 시작점과 작업 버튼의 열을 다른 행과 맞춥니다.

행의 정렬 유지하기한 행에 내용이 추가되어도 텍스트 시작점과 작업 버튼의 열을 다른 행과 맞춥니다.

안팎 모서리 맞추기

안팎의 경계가 모두 필요하다면 바깥 반경에서 안쪽 여백을 뺍니다: 16 − 4 = 12. 두 곡선의 중심이 맞아 간격이 고르게 보입니다.

--ran-radius-lg − --ran-space-1 = --ran-radius-md
안팎 모서리 맞추기안팎의 경계가 모두 필요하다면 바깥 반경에서 안쪽 여백을 뺍니다: 16 − 4 = 12. 두 곡선의 중심이 맞아 간격이 고르게 보입니다. 16 − 4 = 12

작은 아이콘을 쉽게 누르게 하기

16px 아이콘의 클릭 영역을 32px로 넓혀 쉽게 누를 수 있게 합니다. 마우스나 터치 등 입력 방식에 맞게 크기를 정하고 인접 영역 사이에 간격을 둡니다.

--ran-size-1 · --ran-size-7
작은 아이콘을 쉽게 누르게 하기16px 아이콘의 클릭 영역을 32px로 넓혀 쉽게 누를 수 있게 합니다. 마우스나 터치 등 입력 방식에 맞게 크기를 정하고 인접 영역 사이에 간격을 둡니다. 16px 32px

증거와 승인

실제 브라우저에서 무엇을 확인하고 무엇을 보고할지는 가이드의 변경 검증에 있습니다. 관찰한 것, 추론만 한 것, 확인하지 못한 것을 나눠 보고합니다. 에이전트의 검토는 사용자의 승인이 아닙니다.