App Header
모바일 화면 최상단의 한 줄입니다. 자리는 컴포넌트가 고정하고 제목과 좌우 아이콘은 화면이 채웁니다.
🚩 Playground
정렬과 좌우 아이콘 개수, 제목과 설명 줄을 조합해 확인합니다. Title hidden을 켜면 제목이 화면에서만 사라지고 아이콘이 양 끝에 붙습니다. 이름은 DOM에 남아 화면 낭독기가 읽습니다. 미리보기는 375px 화면 안이며 본문을 직접 스크롤해 경계선이 나타나는 것을 볼 수 있습니다. 헤더 높이는 어느 조합에서나 56px로 같고, 설명 줄은 그 안에 들어갑니다.
Appearance
Content
Elements
Behavior
Anatomy
AppHeader를 구성하는 실제 요소와 문서에서 사용하는 명칭입니다. Container와 Title이 필수이고 나머지 셋은 선택입니다. Title은 h1이며, 화면에 글자를 두지 않는 유형도 이름은 갖고 titleHidden으로 감춥니다. Leading element와 Trailing element은 성격이 같은 아이콘 자리이며, 어느 쪽에 무엇이 서는지는 이 컴포넌트가 정하지 않습니다.
AppHeader structure
- 1.Container
- 2.Leading element(Optional)
- 3.Title
- 4.Description(Optional)
- 5.Trailing element(Optional)
Structure map
AppHeader
├─ Leading element (Optional)
├─ Title block
│ ├─ Title
│ └─ Description (Optional)
└─ Trailing element (Optional)Properties · Title align
Center와 Start 둘을 제공하며 기본값은 Center입니다. 같은 화면 안에서 섞지 않고 제품 전체에서 하나를 고르되, 긴 이름을 넓게 써야 하는 화면만 Start로 내립니다.
Center · Default
제목이 화면의 정중앙에 섭니다. 좌우 아이콘 개수와 무관합니다.
행복마트 강남대로점
Start
제목이 왼쪽 자리 바로 옆에서 시작해 오른쪽 자리 앞까지 넓게 씁니다.
행복마트 강남대로 본점 2호점
Balance
Center에서 제목은 남은 공간의 가운데가 아니라 화면의 가운데에 섭니다. 좌우 자리가 남은 공간을 반씩 나눠 가지므로, 아이콘이 한쪽에만 있거나 개수가 서로 달라도 제목의 중심이 움직이지 않습니다. 점선이 화면의 중심입니다.
좌 0개 · 우 2개
가맹점관리
좌 1개 · 우 2개
가맹점관리
좌 2개 · 우 1개
가맹점관리
좌 2개 · 우 2개
가장 좁은 조합입니다. 제목이 들어가는지 이 칸에서 확인합니다.
행복마트 강남대로점
Properties · Elements
Leading element와 Trailing element은 각각 아이콘 두 개까지, Description은 한 줄까지 받습니다. 헤더 높이는 어느 조합에서나 56px로 같습니다.
Title만
가장 단순한 형태입니다. 제목이 56px 안에서 세로 가운데에 섭니다.
가맹점관리
Description
제목 아래 한 줄이 붙어도 높이는 그대로입니다. 두 줄이 56px 안에 들어갑니다.
행복마트 강남대로점
123-45-67890긴 제목
제목과 설명 모두 한 줄에서 말줄임합니다. 두 줄로 넘어가면 높이가 변하기 때문입니다.
행복마트 강남대로 본점 2호점
123-45-67890Leading element 2개
좌우는 성격이 같은 자리입니다. 어느 쪽에 무엇이 서는지는 쓰는 화면이 정합니다.
가맹점관리
Properties · State
헤더 자체가 갖는 상태는 화면 맨 위에 있는지 하나뿐입니다. 아이콘의 Hover, Pressed와 Focus visible은 그 자리에 놓인 Button이 갖습니다.
Top · Default
맨 위에 있을 때는 본문과 같은 면으로 붙어 있고 경계선이 없습니다.
가맹점관리
Stuck
본문이 헤더 아래로 들어가기 시작하면 경계선이 나타납니다. 직접 스크롤해 확인합니다.
화면 유형
블루키에서 실제로 쓰는 다섯 조합입니다. 컴포넌트가 강제하는 것이 아니라 title과 leadingElement, trailingElement에 무엇을 넣는지의 차이일 뿐이므로, 조합이 늘어도 컴포넌트는 그대로 둡니다. 왼쪽에는 나가는 길을, 오른쪽에는 그 화면에서 하는 일을 둡니다.
Main
로고가 어느 화면인지 말하므로 제목 글자를 화면에 두지 않습니다. 이름은 titleHidden으로 감춰 두어 화면 낭독기가 읽습니다.
홈
Menu
전체메뉴 화면입니다. 닫기가 왼쪽에 서고 제목은 titleHidden으로 감춥니다.
전체메뉴
Sub
뒤로가기와 제목이 서고 오른쪽 자리는 그 화면이 바꿔 끼웁니다. 아이콘이 아니라 버튼을 넣어도 됩니다.
행복마트 강남대로점
Sub · 오른쪽에 버튼
같은 Sub이지만 오른쪽에 텍스트 버튼이 섭니다. trailingElement가 아이콘만 받는 자리가 아니라는 것을 보여 줍니다.
행복마트 강남대로점
Layer
화면을 덮는 레이어입니다. 닫기가 왼쪽에 서고 제목과 오른쪽 자리는 필요할 때만 씁니다.
정산 내역
Plain
나가는 길을 일부러 두지 않는 화면입니다. 개정된 약관에 동의해야만 넘어갈 수 있는 자리처럼, 그 화면을 벗어날 방법을 주지 않아야 할 때 씁니다.
약관 동의
Guidelines
AppHeader를 언제 쓰고 어떻게 채우는지에 대한 규칙입니다.
이 컴포넌트를 쓰는 자리
- 모바일 화면의 최상단 한 줄에 씁니다. 데스크톱 화면의 전역 바에는 쓰지 않습니다.
- 한 화면에 하나만 둡니다. 화면 안쪽 영역의 제목에는 이 컴포넌트를 쓰지 않고 그 영역이 자기 제목을 갖습니다.
- 자리는 컴포넌트가 고정하고 내용은 화면이 채웁니다. 목록에서는 기능명을, 상세에서는 그 대상의 이름을 같은 자리에 넣습니다.
제목과 설명 쓰기
- 제목은 지금 보고 있는 것이 무엇인지 한 덩어리로 말합니다. 경로를 이어 붙이지 않습니다.
- 설명은 제목이 가리키는 대상을 식별하는 짧은 값에만 씁니다. 사업자번호나 코드가 그런 값입니다.
- 설명에 안내 문장을 넣지 않습니다. 한 줄에서 잘리므로 문장은 끝까지 읽히지 않습니다.
- 둘 다 375px 화면에서 잘리는지 확인합니다. 잘리는 것이 문제가 되는 값은 본문 첫 칸에서 다시 보여 줍니다.
왼쪽과 오른쪽을 가르는 기준
- 왼쪽에는 나가는 길을 둡니다. 뒤로가기, 닫기, 홈이며 로고도 누르면 홈으로 가므로 같은 자리입니다.
- 오른쪽에는 그 화면에서 하는 일을 둡니다. 검색, 알림, 설정, 저장, 더보기가 여기에 옵니다.
- 닫기는 레이어든 전체메뉴든 왼쪽입니다. 화면을 덮고 자기 헤더를 갖는 레이어의 닫기는 앞 화면으로 되돌아가는 동작이라 뒤로가기와 성격이 같습니다.
- 닫기의 자리를 화면마다 바꾸지 않습니다. 연 자리 가까이 두면 손가락은 덜 움직이지만 그때마다 눈으로 찾게 됩니다.
아이콘 자리 채우기
- 한쪽에 아이콘을 셋 이상 두지 않습니다. 세 개부터는 제목이 들어갈 폭이 남지 않습니다.
- 아이콘은 공용 Button의 Ghost를 tone="neutral"로 씁니다. 헤더가 자기 버튼을 따로 만들지 않습니다.
- 아이콘 크기는 헤더가 정합니다. Button의 크기 단계가 갖고 오는 값을 쓰지 않고 24px로 덮어쓰므로 화면이 크기를 직접 적지 않습니다. 버튼은 Large를 쓰며, 아이콘 그림이 Container의 여백선에 맞도록 헤더가 버튼이 든 자리를 그 안쪽 여백만큼 바깥으로 당깁니다.
- 누르는 동안 아이콘이 90%로 작아졌다가 손을 떼면 돌아옵니다. 공용 Button은 누를 때 움직이지 않기로 정해져 있으므로 헤더가 자기 자리 안에서만 겁니다.
- 아이콘에는 aria-label로 이름을 줍니다. 모양만으로는 무엇을 하는 버튼인지 전달되지 않습니다.
- 화면에서 가장 중요한 동작이 아니면 헤더에 올리지 않고 본문에 둡니다.
정렬은 제품 전체에서 하나로 고르기
가맹점관리
행복마트 강남대로점
가맹점관리
행복마트 강남대로점
Do
이어지는 화면이 같은 정렬을 씁니다.
Don’t
화면마다 정렬을 바꾸지 않습니다.
- 목록에서 상세로 들어갈 때 정렬이 바뀌면 제목이 좌우로 미끄러져 다른 화면으로 왔다는 신호처럼 읽힙니다.
- 긴 이름 때문에 Start가 필요하면 그 화면만이 아니라 같은 흐름 전체를 Start로 내립니다.
설명 줄에는 식별하는 값만 두기
행복마트 강남대로점
123-45-67890행복마트 강남대로점
이 화면에서 가맹점의 계약 정보를 확인하고 수정합니다Do
대상을 가리키는 짧은 값을 둡니다.
Don’t
설명 문장을 넣지 않습니다.
- 설명은 한 줄에서 잘립니다. 문장을 넣으면 앞부분만 남아 무슨 말인지 전달되지 않습니다.
- 화면을 설명해야 한다면 헤더가 아니라 본문 첫 칸이 그 자리입니다.
Property map
실제 Props와 기본값입니다. Leading element와 Trailing element은 성격이 같은 자리이므로 같은 형태로 받습니다.
| Property | Values | Default |
|---|---|---|
| title | ReactNode | 필수 |
| titleHidden | false | true | false |
| description | ReactNode | undefined |
| leadingElement | ReactNode (아이콘 2개까지 또는 로고) | undefined |
| trailingElement | ReactNode (아이콘 2개까지 또는 버튼 1개) | undefined |
| titleAlign | center | start | center |
| sticky | false | true | true |
| className | string | undefined |
명세 문서
docs/system/components/app-header.md
App Header
packages/ui/src/app-header.tsx
모바일 화면의 최상단 한 줄이다. 자리는 이 컴포넌트가 고정하고 무엇을 표시할지는 쓰는 화면이 정한다.
역할과 경계
AppHeader는 Container와 Title, 선택 요소인 Description, Leading element, Trailing element을 소유한다. 한 화면에 하나만 둔다.- 모바일 화면에서만 쓴다. 데스크톱 화면의 전역 바에는 쓰지 않는다. 데스크톱 상단 바는 로고와 전역 내비게이션, 계정 메뉴가 함께 서는 다른 구조이며 아직 시스템에 없다.
- 화면 안쪽 영역의 제목에는 쓰지 않는다. 그 제목은 해당 영역이 직접 갖는다.
- 좌우 아이콘 자리에 놓는 버튼은 button.md가 소유한다.
AppHeader는 자기 버튼을 따로 만들지 않는다. - 아이콘의
Line과Fill선택 규칙은 icon.md가 소유한다.
Anatomy
- Container: 모든 내부 요소를 감싸며 높이와 좌우 여백, 배경과 경계선을 소유한다.
- Leading element: 선택 요소다. 왼쪽 자리다.
- Title: 필수 요소다.
h1으로 그린다. - Description: 선택 요소다. Title 아래 한 줄이다.
- Trailing element: 선택 요소다. 오른쪽 자리다.
자리의 고정 순서는 Leading element → Title block → Trailing element이며 Title과 Description은 Title block 안에 위아래로 놓인다.
Leading element와 Trailing element는 성격이 같은 자리다. 어느 쪽에 무엇이 서는지를 이 컴포넌트가 정하지 않는다. 아이콘 버튼뿐 아니라 로고와 Label이 있는 버튼도 받으므로 이름에 Icon을 붙이지 않는다. 무엇을 넣는지는 Guidelines의 「왼쪽과 오른쪽을 가르는 기준」과 「화면 유형」이 정한다.
Properties
title: 필수다. 지금 보고 있는 것이 무엇인지 말한다.titleHidden:false | true, 기본값false. 제목을 화면에서만 감추고 이름은 남긴다.description: 선택이다. Title 아래 한 줄이다. Title을 보조하는 줄이므로titleHidden일 때 함께 감춰진다.leadingElement: 왼쪽 자리. 아이콘 버튼 두 개까지, 또는 로고를 넣는다.trailingElement: 오른쪽 자리. 아이콘 버튼 두 개까지, 또는 Label이 있는 버튼 하나를 넣는다.titleAlign:center | start, 기본값centersticky:false | true, 기본값true- State: Top, Stuck. Hover, Pressed와 Focus visible은 이 컴포넌트가 갖지 않고 좌우 자리에 놓인 Button이 갖는다.
title은 HTML의 title 속성과 이름이 겹치므로 그 속성을 넘길 수 없다.
높이
56px 한 값으로 고정한다. Description이 있든 없든, 아이콘이 몇 개든 같다.
interaction.md의 공통 Control Height 중 XXLarge와 값이 같지만 그 규칙을 따르는 것은 아니다. 헤더는 Control이 아니라 Control을 담는 Container이며 값이 같은 것은 우연이다.
높이를 내용에 맡기지 않는 이유는 화면 이동 때문이다. Description이 있는 화면과 없는 화면을 오갈 때 헤더가 늘었다 줄었다 하면 그 아래 본문이 위아래로 밀린다.
고정 높이 안에 두 줄이 들어가도록 크기를 미리 정해 둔다.
- Title:
text-base에 20px 행간,font-semibold,oe-foreground-primary - Description:
text-xs에 16px 행간,oe-foreground-tertiary
Title의 크기는 Description 유무와 관계없이 같다. Description이 들어왔다고 Title이 작아지면 화면을 옮길 때 제목 크기가 흔들린다. Description이 없으면 Title이 56px 안에서 세로 가운데에 선다.
Title과 Description은 모두 한 줄에서 말줄임한다. 어느 쪽이든 두 줄로 넘어가면 고정 높이와 양립하지 않는다.
제목과 접근성
Title은 h1이다. 화면 낭독기를 쓰는 사람은 제목으로 화면을 훑으므로 heading이 아니면 그 목록에 잡히지 않는다. 한 화면에 하나만 두는 컴포넌트이니 레벨을 받지 않고 h1으로 고정한다. 본문에 같은 층의 제목이 또 있으면 그쪽을 h2로 내린다.
제목 글자를 화면에 두지 않는 화면에서도 제목을 지우지 않는다. 로고가 어느 화면인지 이미 말하고 있거나 닫기만 있으면 되는 자리에는 titleHidden을 쓴다. 글자는 보이지 않고 이름만 남으므로 그 화면에 이름이 없어지지 않는다. 감춘 제목은 흐름에서 빠지므로 좌우 자리가 남은 공간을 반씩 나눠 갖고 아이콘이 양 끝에 붙는다.
titleHidden일 때도 제목은 그 화면이 무엇인지 말하는 값이어야 한다. 「헤더」나 「타이틀」처럼 자리 이름을 넣지 않는다.
Title align과 균형
center에서 Title은 남은 공간의 가운데가 아니라 화면의 가운데에 선다.
좌우 자리가 남은 공간을 반씩 나눠 가지므로 아이콘이 한쪽에만 있거나 개수가 서로 달라도 Title의 중심이 움직이지 않는다. 왼쪽에 아이콘이 하나이고 오른쪽에 둘이어도 두 자리의 폭이 같아진다.
좌우 자리는 아이콘 폭보다 좁아지지 않는다. 폭이 모자랄 때 줄어드는 것은 아이콘이 아니라 Title이다.
start에서는 예약하지 않는다. Title이 왼쪽 자리 바로 옆에서 시작해 오른쪽 자리 앞까지 넓게 쓴다. 이 정렬을 고르는 이유가 긴 이름을 넓게 쓰기 위해서이므로, 예약하면 고른 뜻이 사라진다.
정렬을 고르는 기준은 Guidelines의 「정렬은 제품 전체에서 하나로 고르기」에 있다.
Stuck
sticky가 켜져 있으면 헤더가 화면 위에 머문다. 기본값이 켜짐이다.
경계선은 가리는 것이 있을 때만 나타난다. 맨 위에 있을 때는 본문과 같은 면으로 붙어 있다가, 본문이 헤더 아래로 들어가기 시작하면 아래에 1px oe-border-secondary가 나타난다.
선을 없앴다 만들지 않고 색만 바꾼다. 경계를 켜고 끄면 그때마다 본문이 1px씩 위아래로 움직인다.
헤더를 감싸고 실제로 스크롤되는 영역을 찾아 그 위치를 읽으므로, 페이지 전체가 스크롤되든 안쪽 영역이 스크롤되든 같게 동작한다. 현재 상태는 data-stuck으로 드러난다.
Guidelines
모바일 화면의 최상단에만 쓰기
- 모바일 화면의 맨 윗줄에 쓴다. 데스크톱 화면의 전역 바에는 쓰지 않는다.
- 한 화면에 하나만 둔다.
- 화면 안쪽 영역의 제목에는 쓰지 않는다. 그 영역이 자기 제목을 갖는다.
자리는 고정하고 내용은 화면이 채우기
- 목록 화면에서는 기능명을, 상세 화면에서는 그 대상의 이름을 같은 자리에 넣는다. 가맹점 목록에서는 「가맹점관리」를, 가맹점 상세에서는 가맹점명을 넣는 식이다.
- 화면이 바뀌어도 헤더의 높이와 자리는 그대로 둔다.
제목은 한 덩어리로 쓰고 언제나 넣기
- 지금 보고 있는 것이 무엇인지 한 덩어리로 말한다.
- 경로를 이어 붙이지 않는다. 상위 화면의 이름은 제목이 아니라 뒤로 가는 길이 전달한다.
- 화면에 글자를 두고 싶지 않아도 제목은 넣고
titleHidden으로 감춘다. 빼 버리면 그 화면에 이름이 없어진다.
설명에는 식별하는 값만 두기
- 제목이 가리키는 대상을 식별하는 짧은 값에만 쓴다. 사업자번호나 코드가 그런 값이다.
- 안내 문장을 넣지 않는다. 한 줄에서 잘리므로 문장은 끝까지 읽히지 않는다.
- 화면을 설명해야 한다면 헤더가 아니라 본문 첫 칸이 그 자리다.
375px에서 잘리는지 확인하기
- 좌우에 아이콘이 두 개씩 서는 조합이 제목에 가장 좁은 폭을 남긴다. 이 조합에서 확인한다.
- 잘리는 것이 문제가 되는 값은 본문 첫 칸에서 전체를 다시 보여 준다.
정렬은 제품 전체에서 하나로 고르기
- 이어지는 화면이 같은 정렬을 쓴다. 목록에서 상세로 들어갈 때 정렬이 바뀌면 제목이 좌우로 미끄러져 다른 화면으로 왔다는 신호처럼 읽힌다.
- 긴 이름 때문에
start가 필요하면 그 화면만이 아니라 같은 흐름 전체를start로 내린다.
왼쪽과 오른쪽을 가르는 기준
- 왼쪽에는 나가는 길을 둔다. 뒤로가기, 닫기, 홈이 여기에 온다. 로고도 누르면 홈으로 가므로 같은 자리다.
- 오른쪽에는 그 화면에서 하는 일을 둔다. 검색, 알림, 설정, 저장, 더보기가 여기에 온다.
- 닫기는 레이어든 전체메뉴든 왼쪽이다. 화면을 덮고 자기 헤더를 갖는 레이어의 닫기는 앞 화면으로 되돌아가는 동작이라 뒤로가기와 성격이 같다. 본문 위에 겹쳐 뜨는 작은 팝업이나 bottom sheet는 이 컴포넌트를 쓰지 않으므로 이 규칙의 대상이 아니다.
- 닫기의 자리를 화면마다 바꾸지 않는다. 연 자리 가까이 두면 손가락은 덜 움직이지만, 그때마다 닫기를 눈으로 찾게 된다.
아이콘 자리는 두 개까지만 채우기
- 한쪽에 아이콘을 셋 이상 두지 않는다. 세 개부터는 제목이 들어갈 폭이 남지 않는다.
- 화면에서 가장 중요한 동작이 아니면 헤더에 올리지 않고 본문에 둔다.
아이콘은 공용 Button으로 두고 이름 주기
- Ghost를
tone="neutral"로 쓴다. Ghost도 tone 색을 따라가므로 중립으로 읽혀야 하는 자리는 기본값에 기대지 않고 밝혀 적는다. aria-label로 그 버튼이 무엇을 하는지 전달한다. 모양만으로는 읽히지 않는다.
아이콘 크기는 헤더가 정한다
좌우 자리의 아이콘은 24px이다. Button의 크기 단계가 갖고 오는 값을 쓰지 않고 헤더가 자기 자리 안에서 덮어쓴다.
- 화면이 크기를 직접 적지 않는다. 화면마다 적게 두면 헤더마다 값이 갈린다.
- 크기를 바꿀 때는 이 줄과
SIDE_CONTENT_CLASS한 곳만 고친다. - 로고는 그림이라 이 규칙에 걸리지 않는다. 크기는 그 자리에 넣는 이미지가 갖는다.
누르는 동안 아이콘이 작아진다. 90%로 줄었다가 손을 떼면 돌아온다. 눌린 것이 손에서 떨어지기 전에 화면에서 먼저 읽힌다. 공용 Button은 누를 때 움직이지 않기로 정해져 있으므로(button.md) 그 컴포넌트를 고치지 않고 헤더가 자기 자리 안에서만 건다. 로고는 그림이라 걸리지 않는다.
아이콘 버튼은 Large를 쓴다. 40px 안에 24px 아이콘이 가운데 서므로 버튼 안쪽에 한쪽 8px이 남는다. 헤더는 버튼이 든 자리를 그만큼 바깥으로 당겨서 아이콘 그림이 Container의 여백선에 맞게 한다. 당기지 않으면 로고는 16px 선에 서는데 아이콘만 24px 안쪽에 서서 양 끝의 여백이 달라 보인다. 로고처럼 버튼이 아닌 것이 서는 자리는 당기지 않는다.
버튼 자체는 공용 Button을 그대로 쓴다. 헤더 전용 버튼을 따로 만들면 tone과 Hover, Focus 규칙이 두 벌로 갈린다. 폼 안에 서는 Button은 자기 단계의 아이콘 크기를 그대로 유지하므로 Input의 Leading element와 줄이 맞는다.
화면 유형
블루키에서 실제로 쓰는 다섯 조합이다. 컴포넌트가 이 구분을 갖지 않는다. 다섯의 차이가 전부 title과 leadingElement, trailingElement에 무엇을 넣는지로 설명되므로 Property로 만들지 않았다. 조합이 늘어도 컴포넌트는 그대로 두고 이 표에 줄을 더한다.
| 유형 | Title | Leading element | Trailing element |
|---|---|---|---|
| Main | 감춤 (titleHidden) |
로고 | 알림, 전체메뉴 |
| Menu | 감춤 (titleHidden) |
닫기 | 유저, 설정 |
| Sub | 보임 | 뒤로가기 | 그 화면이 정한다. 아이콘도 Label이 있는 버튼도 온다 |
| Layer | 보임 또는 감춤 | 닫기 | 선택 |
| Plain | 보임 | 없음 | 없음 |
Plain은 나가는 길을 일부러 두지 않는 화면에 쓴다. 개정된 약관에 동의해야만 넘어갈 수 있는 자리처럼, 그 화면을 벗어날 방법을 주지 않아야 할 때다. 좌우가 비어 있는 것이 빠뜨린 결과가 아니라 결정이라는 뜻이므로, 다른 유형처럼 뒤로가기를 더하지 않는다.
Main과 Menu처럼 Title을 감추는 유형에서는 titleAlign이 화면을 바꾸지 않는다. 감춘 제목이 흐름에서 빠져 좌우 자리가 남은 공간을 반씩 나눠 가지므로 아이콘이 양 끝에 붙기 때문이다.
현재 정의하지 않은 항목
- 제목 옆 상태 표시. Badge를 붙이는 자리를 만들지 않았다.
- 검색 입력으로 바뀌는 헤더.
- 여러 항목을 고르는 선택 모드 헤더.
- 데스크톱 폭에서의 동작.