표면(Surfaces)
이 스킬은 각 요소가 무엇으로 만들어지는지 결정합니다. 즉, 채우기(fill), 가장자리(edge), 그림자(shadow), 둥근 모서리(radius), 상태(states), 아이콘(icons)을 정의합니다. 짧은 레시피 목록에서 선택하므로, 두 화면이 서로 다른 카드를 가지는 일이 없습니다.
결정을 내리기 전에 product-ui §1(취향 일지와 우선순위)과 §5(어떤 스킬이 무엇을 소유하는지)를 적용하세요.
다음 순서대로 결정하세요
- 표면이 필요한가요? 추가하기 전에 제거하세요. 주변에 공간이 있는 제목은 카드가 필요 없습니다. 카드 안에 카드를 중첩하지 마세요. 이유: 모든 컨테이너는 눈이 읽어야 할 가장자리를 추가합니다.
- 어느 레벨인가요? 페이지(Page), 올림(Raised), 플로팅(Floating) 또는 오버레이(Overlay). 요소당 하나의 레벨이며, 얼마나 돋보이게 하고 싶은지가 아니라 요소가 하는 일에 따라 선택합니다. elevation.md.
- 가장자리를 무엇으로 그리나요? 입체감(elevation)은 그림자 고리를 얻습니다. 구조(structure)는 테두리를 얻습니다. 하나의 가장자리에 둘을 함께 사용하지 마세요. elevation.md.
- 어느 둥근 모서리인가요? 요소의 역할에 맞는 토큰을 가져온 다음, 중첩된 둥근 모서리를 동심원으로 만드세요. elevation.md.
- 어느 상태인가요? 기본 상태를 다듬기 전에, 호버, 누름, 선택, 비활성화 및 포커스-가시를 표면의 변화로 디자인하세요. states-and-icons.md.
- 어느 아이콘 처리를 하나요? 하나의 세트,
currentColor, 텍스트와 일치하는 획. states-and-icons.md. - 재질(material)이 허용되나요? 스크롤되는 콘텐츠 위에 떠 있는 크롬(chrome)만 가능합니다. materials-and-edges.md.
- 스크롤의 가장자리. 구분선, 페이드, 오버스크롤. materials-and-edges.md.
네 가지 레벨
| 레벨 | 용도 | 라이트 모드 | 다크 모드 |
|---|---|---|---|
| 페이지(Page) | 바탕 | 바탕 채우기, 그림자 없음 | 가장 어두운 채우기, 그림자 없음 |
| 올림(Raised) | 카드, 컨트롤, 선택된 옵션 | 흰색, 고리 + 1px 드롭 + 3px 타이트 드롭 | 한 단계 더 밝게, 흰색 고리, 타이트한 검은색 드롭 |
| 플로팅(Floating) | 메뉴, 팝오버, 툴팁, 토스트 | 흰색, 고리 + 1px 드롭 + 4px 타이트 드롭 | 두 단계 더 밝게, 흰색 고리, 타이트한 검은색 드롭 |
| 오버레이(Overlay) | 대화상자, 시트, 명령 팔레트(스크림 포함) | 흰색, 고리 + 1px 드롭 + 6px 타이트 드롭 | 세 단계 더 밝게, 흰색 고리, 타이트한 검은색 드롭 |
정확한 토큰은 elevation.md에 있습니다. 그 뒤에 있는 규칙:
- 고리와 타이트한 드롭. 블러 ≤ 8px, 오프셋 ≤ 6px, 음수 스프레드가 있는 드롭. 이유: 크고 부드러운 블러는 글로우(glow)로 읽히며, 페이지를 지저분하게 만들고 생성된 것처럼 보입니다.
- 다크 모드는 밝아짐으로써 올라갑니다. 어두운 곳에서는 그림자가 거의 보이지 않으므로, 밝기가 높이를 전달하고 낮은 알파의 흰색 고리가 가장자리를 전달합니다.
- 그림자는 반투명하고 단일 색상입니다. 불투명한 회색은 후광(halo)을 남깁니다. 어두운 드롭은 검은색으로 유지됩니다.
- 형제 요소는 레벨을 공유합니다. 한 행의 카드는 하나의 그림자, 하나의 둥근 모서리, 하나의 패딩을 가집니다.
- 호버는 그림자가 아니라 채우기를 변경합니다.
참조가 필요 없는 규칙
- 테두리는 구조를 보여주고, 그림자는 입체감을 보여줍니다. 구분선, 입력 필드, 테이블 셀 및 선택된 상태는 테두리를 유지합니다. 오직 들어 올리기 위해 존재하는 카드의 테두리는 대신 고리를 얻습니다.
- 그림자 고리 아래에
border: 1px solid transparent를 유지하세요. 이유: 강제 색상 모드(forced-colours mode)는 그림자를 제거하며, 테두리는 여전히 가장자리를 그립니다. - 이미지는 1px 내부 아웃라인을 얻습니다: 검은색 10% (다크 모드에서는 흰색 10%)의
outline: 1px solid,outline-offset: -1px. 테두리(fringe)로 보이는 착색된 색상은 절대 사용하지 마세요. 로고 및 투명 아트워크는 건너뛰세요. - 동심원 둥근 모서리(Concentric radius). 바깥쪽 = 안쪽 + 패딩 (+ 테두리), 패딩 최대 24px까지 적용합니다. 그 이상이면 별도의 표면으로 취급합니다.
- 포커스는 고리이며, 절대 없어서는 안 됩니다.
:focus-visible는 2px 오프셋에 2px 강조 아웃라인을 표시합니다. 입력 필드는 간격 없이 2px 강조 가장자리로 붙여서 사용합니다.outline: none을 대체 없이 사용하는 것은 버그입니다. 고리는 레이아웃 공간을 차지하지 않습니다. - 비활성화된 컨트롤은 호버, 누름 또는 크기 조정을 받지 않습니다.
- 아이콘: 표면당 하나의 세트,
currentColor, 기본적으로 아웃라인, 활성 상태일 때 채워짐. 획은 인접한 텍스트 굵기를 따르며, 400에서 1.5px, 600에서 최대 2px입니다. - 뒤 배경 블러(Blur-behind)는 플로팅 크롬에만 사용합니다. 콘텐츠, 카드 또는 테이블에는 절대 사용하지 마세요.
- 스크롤 가능한 오버레이(대화상자, 서랍, 메뉴)는
overscroll-behavior: contain을 사용합니다.
짝 비교 (전/후)
| 잘못된 예 | 올바른 예 | 이유 |
|---|---|---|
카드에 border: 1px solid #e5e5e5 | box-shadow: var(--elev-raised); border: 1px solid transparent | 깊이 전용 테두리는 고리가 됩니다. 강제 색상 모드에서도 여전히 가장자리를 그립니다. |
box-shadow: 0 10px 40px rgba(0,0,0,.2) | 0 0 0 1px a.08, 0 1px 2px a.12, 0 4px 8px -4px a.16 | 크고 부드러운 것보다 타이트하고 선명한 것이 낫습니다. |
0 8px 24px #000이 있는 어두운 카드 | 더 밝은 채우기 + 0 0 0 1px oklch(1 0 0 / .08) | 어두운 곳에서는 그림자가 사라집니다. 밝기가 높이를 보여줍니다. |
| 카드 둥근 모서리 8, 내부 이미지 둥근 모서리 8, 패딩 8 | 내부 8, 외부 16 | 동심원 모서리는 균일한 간격을 유지합니다. |
흰색 카드에 녹아드는 <img> | outline: 1px solid oklch(0 0 0 / .1); outline-offset: -1px | 테두리 없이도 가장자리가 읽힙니다. |
button:focus { outline: none } | button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px } | 키보드 사용자가 현재 위치를 잃지 않습니다. |
| 600 굵기 레이블 옆에 1.5px 아이콘 획 | 2px 획 | 획 굵기가 자체 굵기와 일치합니다. |
| 모든 도구 모음 슬롯에 채워진 아이콘 | 기본적으로 아웃라인, 활성 항목은 채워짐 | 채움은 상태 신호입니다. |
카드에 backdrop-filter: blur(20px) | 불투명한 표면 | 블러는 스크롤되는 콘텐츠 위에 떠 있는 크롬에 속합니다. |
마무리 전 확인 사항
| 패턴 | 수정 |
|---|---|
라이트 UI에서 블러 > 8px 또는 알파 > .2인 box-shadow | 레벨 토큰을 사용하세요 |
같은 작업을 하는 border와 box-shadow를 함께 사용 | 고리와 투명 테두리만 사용하세요 |
border: 1px solid transparent 없는 그림자 고리 | 추가하세요 |
부모와 패딩이 있는 자식이 같은 border-radius를 가짐 | 부모에 inner + padding 사용 |
:focus-visible 대체 없이 outline: none 또는 outline: 0 | 고리를 추가하세요 |
고리를 스타일링하는 :focus (:focus-visible이 아님) | :focus-visible로 전환하세요 |
상태에 따라 border-width가 변경됨 | 색상, 채우기 또는 동일한 너비의 투명 테두리를 사용하세요 |
아이콘에 하드코딩된 fill="#…" 또는 stroke="#…" | currentColor를 사용하세요 |
| 한 화면에 두 개의 아이콘 라이브러리가 임포트됨 | 하나만 사용하세요 |
고정 막대, 사이드바 또는 플로팅 메뉴 외부의 backdrop-filter | 제거하세요 |
overscroll-behavior: contain 없는 오버레이 목록 | 추가하세요 |
스크롤러 가장자리의 하드 컷 또는 overflow: hidden에 의해 잘린 그림자 | 가장자리를 페이드 처리하고, 스크롤러에 패딩을 추가하세요 |
라이트 모드와 다크 모드를 2배율로 렌더링하고 각 레벨의 가장자리를 잘라내어 확인하세요. 각 컨트롤에 대해 호버, 누름, Tab으로 포커스, 선택 및 비활성화를 수행하고 전후의 상자 크기를 비교하세요. 강제 색상 모드(Chrome 개발자 도구, 렌더링 에뮬레이션)를 토글하고 모든 카드에 여전히 가장자리가 있는지 확인하세요.