레이아웃
레이아웃은 Senco 화면의 구조와, 화면 크기에 따라 콘텐츠가 다시 배치되는 방식을 정한다.
Senco는 글자 그대로 모바일 우선이다. 휴대폰 레이아웃이 곧 디자인이고, 데스크톱 레이아웃은 같은 레이아웃에 공간을 더 준 것뿐이다. 이 순서를 지키기 때문에 두 레이아웃이 서로 다른 제품처럼 갈라지지 않는다. 송금 플로우가 1440px 모니터에서도 360px 휴대폰과 같은 모양인 것도 그래서다.
그리드 시스템
브레이크포인트에 맞춰 바뀌는 열 기반 반응형 그리드다. 콘텐츠는 다시 배치될 뿐, 작게 줄어들지 않는다.
| Breakpoint | Width | Columns | Gutter | Margin |
|---|---|---|---|---|
| Mobile | < 600px | 4 | 16px | 16px |
| Tablet | 600–1023px | 8 | 16px | 24px |
| Desktop | 1024–1439px | 12 | 24px | 32px |
| Wide | ≥ 1440px | 12 | 24px | auto (최대 1400px) |
브레이크포인트 토큰: --ds-bp-mobile (600px), --ds-bp-tablet (1024px), --ds-bp-desktop (1440px).
줄이지 말고 다시 배치한다
화면이 작아지면 콘텐츠가 차지하는 열 수가 바뀔 뿐, 크기가 작아지지는 않는다. 16px 라벨은 어느 브레이크포인트에서나 16px이다. 바뀌는 건 카드 세 개가 나란히 있던 행이 한 열로 쌓인다는 점이다.
크기를 줄이기 시작하면, 글자를 가장 크게 봐야 하는 사용자의 기기에 13px 텍스트가 뜨게 된다.
브레이크포인트별 열 범위
| Content | Mobile (4) | Tablet (8) | Desktop (12) |
|---|---|---|---|
| 전체 폭 섹션 | 4 | 8 | 12 |
| 세 개가 한 행인 카드 | 4 (세로로 쌓음) | 4 (한 행에 둘) | 4 (한 행에 셋) |
| 송금 플로우 열 | 4 | 6, 가운데 정렬 | 4, 가운데 정렬 |
| 대시보드 본문 / 사이드 | 4 / 4, 세로로 쌓음 | 8 / 8, 세로로 쌓음 | 8 / 4 |
| 설정 내비 / 패널 | 4 (탭을 위에) | 8 (탭을 위에) | 3 / 9 |
송금 플로우는 화면이 넓어질수록 오히려 좁아진다. 4열 중 4열을 쓰다가 12열 중 4열을 쓴다. 의도한 것이고, 모바일 우선 원칙이 가장 잘 드러나는 부분이다. 공간이 늘어나도 폼은 넓어지지 않고, 같은 폼 주변의 여백만 넓어진다.
브레이크포인트는 레이아웃에만 쓴다
컴포넌트는 뷰포트 크기를 보지 않는다. 주어진 컨테이너를 채울 뿐이고, 컨테이너 폭은 레이아웃이 정한다. @media (max-width: 600px)에 반응하는 카드는 데스크톱 사이드바에 넣는 순간 깨진다.
좁아지면 보조 줄을 숨기는 거래 행처럼 컴포넌트가 정말 자기 폭에 반응해야 할 때는, 미디어 쿼리가 아니라 컨테이너 쿼리를 쓴다.
컨테이너
| Type | Behavior | Usage |
|---|---|---|
| Fluid | 사용 가능한 폭을 채운다 | 전체 폭 섹션 |
| Constrained | 최대 폭을 두고 콘텐츠를 가운데 정렬 | 일반 콘텐츠 페이지 |
| Full-bleed | 화면 끝에서 끝까지 채우고 페이지 패딩을 무시 | 히어로 배너, 배경 |
Constrained 폭은 sizing 토큰(--ds-container-xs부터 --ds-container-max까지)으로 정한다. Sizing 참고.
컨테이너 고르기
| Screen | Container | Why |
|---|---|---|
| Transfer flow | sm (480px) | 한 열로 순서대로 진행한다. 넓으면 순서를 건너뛰며 훑어보게 된다 |
| Settings | lg (960px) | 데스크톱에서는 내비와 패널 두 열 |
| Dashboard | lg (960px) | 본문 열과 사이드 레일 |
| 문서·콘텐츠 | md (640px) | 한 줄 길이를 45–75자로 유지한다 |
| 마케팅 히어로 | Full-bleed | 배경이 콘텐츠 열 밖까지 이어진다 |
콘텐츠는 폭을 제한한 Full-bleed
Full-bleed 섹션에서 끝까지 채우는 건 배경뿐이다. 텍스트는 여전히 Constrained 컨테이너 안에 둔다. 배경이 아무리 좋아도 1400px 폭으로 이어지는 헤드라인은 읽을 수 없다.
Content centered in a constrained container with margins. Comfortable reading.
Content stretches edge-to-edge. Line lengths become unreadable on wide screens.
레이아웃 기본 요소
| Primitive | Purpose |
|---|---|
| Container | 콘텐츠 폭을 제한하고 좌우 패딩을 준다 |
| Grid | 콘텐츠를 반응형 열로 배치한다 |
| Stack | 같은 간격 하나로 세로로 나열한다 |
| Row | 같은 간격 하나로 가로로 나열한다. 기본적으로 줄바꿈된다 |
Stack과 Row를 쓰는 이유는 간격을 한곳에서 관리하기 위해서다. Stack으로 만든 화면은 그룹마다 간격 값이 딱 하나이고 자식 요소에는 margin이 없다. Spacing의 규칙을 리뷰에 맡기지 않고 구조로 지키게 하는 셈이다.
Header와 Footer는 템플릿 단위의 영역이다. Templates > Basic page 참고.
안전 영역
모바일 레이아웃은 env(safe-area-inset-*)를 지킨다. 하단 탭 바와 고정 액션 바는 모두 홈 인디케이터 위에 둔다. inset을 적용하지 않으면 주요 송금 액션이 제스처 바 밑으로 들어가는데, 거기서는 탭의 3분의 1 정도를 OS가 가져가 버린다.
반응형 원칙
- 모바일 우선. 기본 스타일은 모바일 기준이고, 브레이크포인트에서 확장한다
- 내비게이션은 모바일에서는 하단 탭, 데스크톱에서는 사이드바로 바뀐다
- 모바일에서 터치 타깃은 최소 48px을 지킨다
- 차지하는 열 수는 브레이크포인트마다 달라진다 (예: 데스크톱에서 3열이던 카드가 모바일에서는 한 열로 쌓인다)
- 320px 폭과 200% 확대에서도 가로 스크롤 없이 콘텐츠가 다시 배치된다 (WCAG 1.4.10)
- 레이아웃은
rem과 비율 단위로 잡는다. 그래야 OS 글자 크기를 키웠을 때 잘리지 않고 다시 배치된다
권장과 금지
| Do | Don't |
|---|---|
| Let the transfer flow stay 480px on a wide screen | Stretch a single-column form to fill 1200px |
| Reflow a three-card row into a stack | Scale three cards down to fit side by side on mobile |
| Use a container query for component-level response | Put a viewport media query inside a component |
| Apply safe-area insets to fixed bottom chrome | Pin an action bar to bottom: 0 and lose it under the home indicator |
Keep gaps on the parent via gap | Add margins to grid children to fake a gutter |
디자인과 접근성
- 시맨틱 랜드마크를 쓴다 (
header,main,footer,nav) - 그리드가 스크린 리더의 읽기 순서를 깨면 안 된다. 화면상 순서와 DOM 순서가 같아야 한다
- 두 순서가 어긋나게 되는 곳에는
order와row-reverse를 쓰지 않는다 - 컨테이너 안에 키보드 포커스를 가두지 않는다
<main>은 페이지마다 하나만 두고, 건너뛰기 링크가 여기를 가리키게 한다- 같은 랜드마크가 반복되면 라벨을 붙인다 (
<nav>가 둘이면 서로 다른aria-label이 필요하다)
알려진 공백
| Item | Status |
|---|---|
| Stack / Row 기본 요소 | 여기서 문서화했지만 컴포넌트로 구현해 출시하지는 않음 |
| 컨테이너 쿼리 | 권장 방식으로 문서화했지만 아직 쓰는 컴포넌트가 없음 |
| 그리드 열 토큰 | 열 수와 거터는 문서화했지만 --ds- 변수로 정의하지 않음 |
| Safe-area 처리 | 프로토타입 화면에는 적용했지만 레이아웃 기본 요소로 정리하지 않음 |