레이아웃
이 스킬은 요소가 어디에 위치하고, 서로 얼마나 떨어져 있으며, 크기가 얼마나 되어야 하는지를 결정합니다. 동일한 순서로 동일한 결정을 내리고, 렌더링된 픽셀과 결과를 비교 확인합니다.
결정을 내리기 전에 product-ui §1(취향 저널 및 우선순위)과 §5(어떤 스킬이 무엇을 담당하는지)를 적용하세요.
다음 순서대로 결정
- 셸. 화면 영역의 이름을 지정합니다: 사이드바, 툴바, 콘텐츠, 인스펙터. 각 영역은 스크롤 소유자이거나 아닙니다. 세부 사항 및 너비: shell-and-responsive.md. 이유: 셸은 내부 레이아웃이 존재해야 하는 모든 너비를 고정합니다.
- 너비 및 측정. 콘텐츠 열은 포함하는 내용에 따라 설정합니다: 양식 440px, 읽기 720px(본문은
type측정값(66ch)으로 제한), 데이터 1200px. 열은 화면이 넓어도 늘어나지 않습니다. 이유: 75자를 넘어가면 독자가 줄을 놓치고, 늘어난 양식은 시선을 화면 전체로 분산시킵니다. - 간격. 아래 척도만 사용하고 다른 것은 사용하지 마세요. 먼저 공간으로 그룹화하고, 공간이 해결할 수 없는 경우에만 채우기(fill)를 추가하고, 그 다음에 선(line)을 추가하세요. spacing-and-density.md.
- 정렬. 공유된 가장자리를 선택하고 모든 요소를 그 위에 배치하세요. 1–4px 오차는 버그입니다. 기하학적으로 잘못 보이는 곳은 광학적으로 보정하세요. spacing-and-density.md.
- 크기. 영역의 밀도를 선택한 다음 히트 영역을 선택하세요. spacing-and-density.md.
- 최악의 데이터. 완료라고 말하기 전에 가장 길고, 가장 짧고, 누락되고, 번역되고, 아직 로딩 중인 콘텐츠에 맞게 상자의 크기를 조정하세요. stability-and-text.md.
- 적응. 구조가 기기가 아닌 컨테이너에 반응하도록 만드세요. shell-and-responsive.md.
- 측정. 2배율로 렌더링하고, 간격과 가장자리를 측정하고, 픽셀이 보여주는 것을 수정하세요. measure.md.
척도
하나의 척도, 4px 기본값. 척도의 단계가 아닌 간격 값은 절대 작성하지 마세요. 이유: 일회성 값(10px, 14px, 18px)은 리듬을 볼 수 없게 만들고 유지할 수 없게 만듭니다.
| 토큰 | px | 일반적인 용도 |
|---|---|---|
--space-xs | 4 | 레이블과 설명 사이; 칩의 아이콘과 레이블 사이 |
--space-sm | 8 | 그룹 내 항목 사이; 컨트롤 패딩 블록 |
--space-md | 12 | 테두리 있는 컨트롤 사이; 컨트롤 패딩 인라인 |
--space-lg | 16 | 필드 사이; 컴팩트 카드 패딩 |
--space-20 | 20 | 카드 내 블록 사이 |
--space-xl | 24 | 그룹 사이; 여유 카드 패딩 |
--space-2xl | 32 | 섹션 사이 |
--space-3xl | 48 | 페이지 첫 번째 제목 위 |
--space-4xl | 64 | 페이지 마지막 블록 아래 |
광학적 정렬을 위한 1–2px 미세 조정만이 척도에서 벗어난 유일한 값입니다. 이름을 지정하세요(--play-nudge: 2px).
참조가 필요 없는 규칙
- 공간으로 그룹화. 그룹 사이의 간격은 그룹 내부 간격의 최소 2배입니다: 내부 8px는 그룹 간 16px 이상을 의미합니다. 모든 곳에 동일한 간격을 두면 그룹화가 모호해지며, 여기서 모호함은 기능적 버그입니다.
- 선을 추가하기 전에 제거하세요. 먼저 공간, 두 번째로 채우기(fill), 마지막으로 구분선(divider)이며, 밀도가 높은 데이터에만 사용합니다. 이유: 모든 선은 공간이 무료로 제공하는 동일한 정보에 대해 주의를 소모합니다.
- 두 요소 사이의 공간은 위와 아래가 동일합니다. 이유: 한 요소가 한쪽 이웃에 더 가까우면 그 요소에 속한 것으로 읽힙니다.
- 컨트롤은 컨트롤처럼 보여야 합니다. 버튼, 링크 또는 토글은 모양, 밑줄 또는 툴바 위치를 가집니다. 정적 텍스트는 절대 이러한 특성을 공유하지 않습니다. 이유: 텍스트처럼 스타일링된 컨트롤은 숨겨지고, 컨트롤처럼 스타일링된 텍스트는 죽은 클릭을 유발합니다.
- 액션은 그것이 작용하는 데이터 위에 위치하며, 절대 아래에 있지 않습니다. 무언가를 설정하는 컨트롤은 그 옆에 위치합니다.
- 기본 콘텐츠는 먼저, 그리고 앞(leading)에 옵니다. 시각적 순서는 DOM 순서와 같습니다: 콘텐츠를 재정렬하기 위해
order,row-reverse또는 그리드 배치를 절대 사용하지 마세요. 이유: 키보드 및 스크린 리더 순서는 DOM을 따릅니다. - 논리적 속성(
margin-inline-start,padding-block,inset-inline-end,text-align: start)을 미러링이 필요한 모든 것에 사용하세요. 이유: RTL이 두 번째 스타일시트 없이 작동합니다. - 창이나 사이드바의 하단 가장자리에 중요한 컨트롤을 절대 배치하지 마세요. 이유: 사람들은 창을 부분적으로 화면 밖으로 밀어냅니다.
- 스크롤은 숨겨진 것을 보여줍니다. 가장자리를 넘어가는 콘텐츠는 보이는 신호(16–32px의 엿보기 또는 페이드)를 받습니다.
- 아무것도 이동하지 않습니다. 호버, 선택 및 포커스는 요소의 크기를 절대 변경하지 않습니다. 로드된 데이터는 스켈레톤이 보유했던 상자를 그대로 사용합니다.
짝 (옳고 그름)
| 잘못된 예 | 올바른 예 | 이유 |
|---|---|---|
양식에 gap: 12px이고 필드셋 사이도 gap: 12px | 내부는 gap: 8px, 그룹 간은 gap: 24px | 그룹은 최소 2배의 간격이 필요합니다 |
padding: 10px 14px | padding: var(--space-sm) var(--space-md) | 척도의 단계만 사용 |
| 제목 x=24, 설명 x=26 | 둘 다 x=24 | 1–4px 오차는 실수로 읽힙니다 |
아이콘 버튼 16×16, padding: 0 | 16px 글리프, ::before { inset: -12px }로 40px 타겟 | 마우스 타겟은 40px입니다 |
.row + .row { margin-top: 8px }, 행에 호버 및 클릭 있음 | .row { padding-block: 4px }, 마진 없음 | 행 사이의 간격은 데드 존이며 호버를 깜빡이게 합니다 |
.menu { width: 200px } | width: max-content; min-width: 160px; max-width: 320px | 메뉴는 가장 긴 레이블에 맞춰집니다 |
카드에 @media (max-width: 768px) | 카드 부모에 @container (width < 400px) | 좁은 사이드바의 카드는 화면 너비가 아닌 자신의 너비가 필요합니다 |
크기 없는 <img src>, 그 다음 점프하는 카드 | width, height 또는 aspect-ratio 설정 | 바이트가 도착하기 전에 상자가 존재합니다 |
| 고정 헤더, 앵커가 그 아래에 위치 | 스크롤러에 scroll-padding-block-start: 56px | 포커스와 앵커는 크롬 아래에 위치하지 않습니다 |
완료 전에
diff에서 다음을 검색한 후 확인하세요.
| 패턴 | 수정 |
|---|---|
척도에 없는 값을 가진 margin, padding 또는 gap | 가장 가까운 단계 |
그룹 내부와 그룹 사이에 동일한 gap 값 사용 | 내부 ≤ 사이의 절반 |
24px 미만의 아이콘 전용 버튼, 또는 ::before 없이 40px 미만 | 의사 요소로 확장 |
클릭 가능한 행이나 메뉴 항목 사이의 gap 또는 margin | 항목 내부의 패딩 |
메뉴, 버튼 또는 텍스트 상자의 고정 width | max-content, min-width, max-width; 텍스트의 경우 min-height |
재사용 가능한 컴포넌트 내부의 @media (max-width | 부모의 @container |
셸의 height: 100vh | 100dvh |
긴 텍스트를 담고 있는 1fr 트랙 또는 플렉스 자식 | minmax(0, 1fr) 또는 min-width: 0 |
미러링된 UI의 margin-left, padding-right, left: 0, text-align: left | 논리적 형식 |
scroll-padding-block 없는 고정 헤더 또는 푸터 | 크롬 높이로 설정 |
| 스켈레톤 행 높이 또는 열 너비가 로드된 것과 다름 | 일치시킴 |
차원 또는 aspect-ratio 없는 <img> | 추가 |
measure.md로 확인: 390px와 셸의 가장 넓은 너비에서 2배율로 렌더링하고, 스크립트를 실행하고, 각 후보를 자르고 픽셀을 셉니다. 그런 다음 고정 크롬이 있는 상태에서 Tab으로 화면을 탐색하고, 가장 긴 문자열을 로드합니다. 확인할 수 없었던 것은 '확인되지 않음'으로 보고하세요.