Side Panel
화면 옆(왼쪽 · 오른쪽)에서 미끄러져 나오는 모달 패널. 지금 쓰는 곳은 HR 폰(768 미만)의 주 메뉴 서랍 하나다 — ☰ 를 누르면 왼쪽에서 나와 Side Navigation 의 항목을 보인다. 오른쪽 패널(1280 이상의 보조 작업)은 쓰는 곳이 아직 없다. 아래에서 올라오는 모달은 Bottom Sheet, 가운데 대화상자는 Dialog 다. 이동 부품(하단 탭 바 · 사이드바 · 상단 바 · Tabs)과 고르는 표는 DESIGN.md Layout 의 "이동 부품 고르기" 한 곳이다.
구조는 당근 SEED Side Panel(Apache-2.0)을 따른다 — "화면의 측면에서 슬라이드되어 나타나며, 현재 맥락을 유지한 채 상세 정보를 확인하거나 부수적인 작업을 수행하는 레이아웃 컴포넌트" 로, 딤 · 패널 · 머리(제목 · 설명 · 닫기) · 본문 · 바닥, 크기 480 · 720 · 960 · 768 미만 80%, 높이 전체, 모서리 · 그림자 · 선 없음. 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다(2026-10-04 사용자 결정). 옛 Sheet 스펙(shadcn — 네 방향 · 75% · 384 · 그림자 · 선)을 대신한다 — 아래는 Bottom Sheet 가 맡고, 위에서 내려오는 패널은 걷었다.
수치 원본은 side-panel.yaml이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.
직접 골라 보기
방향(왼쪽 주 메뉴 · 오른쪽 패널) · 크기 · 설명 · 본문 길이 · 바닥 버튼을 고르면 스펙대로 그린 패널과 그 코드가 바뀐다. 실제로 열고, 끌어 닫고, 본문을 스크롤할 수 있다.
import { SidePanel, SidePanelBody, SidePanelContent } from "@/components/ui/side-panel"
import { SideNavigationContent, SideNavigationGroup, SideNavigationItem, SideNavigationSubItem } from "@/components/ui/side-navigation"
<SidePanel open={menuOpen} onOpenChange={setMenuOpen}>
<SidePanelContent side="left" title="그루 이음" id="main-menu">
<SidePanelBody scrollFog className="px-x4">
{/* 사이드바와 같은 항목 — 지금 묶음은 저절로 펼쳐지고, 항목을 누르면 이동하고 닫힌다 */}
<SideNavigationContent onNavigate={() => setMenuOpen(false)}>…</SideNavigationContent>
</SidePanelBody>
</SidePanelContent>
</SidePanel>Anatomy
| 부위 | 설명 |
|---|---|
| ⓐ Overlay | 딤 — 패널 뒤 화면 전체. 입력 폼이 아니면 누르면 닫힌다. |
| ⓑ Container | 패널 — 왼쪽 또는 오른쪽 끝, 높이 전체. 모서리 · 그림자 · 선 없이 딤과 면 색으로 뜬다. |
| ⓒ Header | 머리 — 제목(늘 있다) · 설명(있을 때만) · 닫기 버튼. |
| ⓓ Body | 본문 — 넘치면 이 안에서만 스크롤한다. 스크롤되면 머리 아래 1px 선. |
| ⓔ Footer | 바닥 — 버튼, 오른쪽 정렬(오른쪽 패널). |
부위
| 부위 | 설명 |
|---|---|
overlay | 딤 — 패널 뒤 화면 전체. 입력 폼이 아니면 누르면 닫힌다 |
root | 패널 — 화면 높이 전체, 왼쪽 또는 오른쪽 끝에 붙는다. 모서리 · 그림자 · 선 없음 |
header | 머리 — 제목 · 설명 · 닫기 |
title | 제목 — 늘 있다(패널의 이름) |
description | 설명 — 덧붙일 말이 있을 때만 |
closeButton | 닫기 — 투명 상자 52 · 아이콘 22. 이름 "닫기" |
divider | 머리 아래 선 — 본문이 스크롤됐을 때만 |
body | 본문 — 넘치면 이 안에서만 스크롤(머리 · 바닥은 고정) |
fog | 끝 흐림 — Scroll Fog(scroll-fog.yaml 의 overlayBody). 넘칠 수 있는 본문에만 걸고, 걸면 늘 켜져 있다 |
footer | 바닥 — 버튼, 오른쪽 정렬(오른쪽 패널) |
focusRing | 키보드 포커스 링(웹) |
Properties
방향
| 방향 | 쓰는 곳 | 폭 |
|---|---|---|
left | 768 미만의 주 메뉴 서랍 — 지금 HR 폰 하나 | 화면 폭의 80% |
right | 1280 이상의 보조 작업 — 쓰는 곳 없음 | 크기(480 · 720 · 960) |
왼쪽은 내비게이션(SEED — "모바일 사이드바, 글로벌 내비게이션 메뉴"), 오른쪽은 상세 조회 · 설정 같은 보조 작업이다. 아래에서 올라오는 패널은 Bottom Sheet 이고, 위에서 내려오는 패널은 두지 않는다.
방향
| side | 너비 | 자리 | title 글자 | body 좌우 여백 | 설명 |
|---|---|---|---|---|---|
left | 화면 폭의 80% — 768 미만에서만 쓴다 — 딤이 오른쪽 20% 에 남는다. 크기(480 · 720 · 960)는 오른쪽 패널만 쓴다 | 왼쪽 끝 — 왼쪽으로 끌면 닫힌다 | text-t8 22px / 30px — 서비스 이름("그루 이음") — 주 메뉴 서랍의 제목 | spacing-x4 16px — 주 메뉴 — Side Navigation 의 항목(좌우 8)이 들어와 아이콘이 제목과 같은 24 에서 선다 | 왼쪽 — 768 미만의 주 메뉴 서랍(지금 HR 폰) |
right (기본) | — | 오른쪽 끝 — 1280 이상 — 미만에서는 같은 내용을 Bottom Sheet 로 | — | — | 오른쪽 — 1280 이상의 보조 작업(쓰는 곳 없음) |
크기
오른쪽 패널의 폭이다 — small 480(단순한 상세) · medium 720 (기본)(두 줄 폼 · 목록과 상세 나란히) · large 960(복잡한 설정). 어느 크기든 화면의 80% 를 넘지 않아 딤이 늘 20% 남는다. 높이는 화면 전체다.
크기
| size | 너비 | 설명 |
|---|---|---|
small | 480px | 480 — 단순한 상세 조회 |
medium (기본) | 720px | 720 — 기본. 두 줄 폼 · 목록과 상세 나란히 |
large | 960px | 960 — 복잡한 설정 · 편집 도구 |
머리 · 본문 · 바닥
머리는 위 24 · 좌우 24 · 아래 16 · 최소 70, 제목 22 / 30 · 700 · 설명 16 / 22(fg-neutral-muted) · 사이 6 이다. 닫기 버튼은 투명 상자 52 · 아이콘 22(fg-neutral-subtle)이고 아이콘이 위 28 · 오른쪽 24 에 놓인다 — Dialog 의 닫기와 같다. 본문은 좌우 24 이고 넘치면 본문만 스크롤된다 — 위로 스크롤하면 머리 아래 1px 선이 생긴다. 바닥은 위 16 · 좌우 24 · 아래 24 에 Button small 36 을 오른쪽으로 모은다. 머리 위에는 위 안전 영역을, 패널 맨 아래에는 아래 안전 영역을 더한다(바닥이 없어도).
공통
| 부위 | 속성 | 값 | 비고 |
|---|---|---|---|
overlay | 배경 | overlay-dim-light rgba(0, 0, 0, 0.50) · 다크 overlay-dim-dark rgba(0, 0, 0, 0.65) | groo 딤(v102) — SEED 0.455 는 들이지 않았다 |
overlay | z-index | 100 | z-modal — specs/z-index.md L2 |
root | 높이 | 화면 높이 전체 | dvh — 방향과 상관없이(SEED) |
root | 최대 너비 | 80% | 딤이 늘 20% 남는다(SEED 코드). 768 미만이면 크기와 상관없이 화면 폭의 80% |
root | 모서리 | 0px | 평평 — 모서리 · 그림자 · 선 없이 딤과 면 색으로 뜬다(SEED) |
root | 배경 | color-bg-layer-floating #FFFFFF · 다크 #2D3346 | |
root | 그림자 | none | |
root | 아래 여백 | 아래 안전 영역 | 패널 내용의 맨 아래 — 바닥이 없어도(SEED). 붙은 쪽 안전 영역(가로 노치)은 폭에 더한다 |
root | z-index | 101 | z-modal-content — specs/z-index.md L2 |
header | 위 여백 | spacing-x6 24px | 위 안전 영역을 더한다 |
header | 좌우 여백 | spacing-x6 24px | |
header | 아래 여백 | spacing-x4 16px | |
header | 최소 높이 | 70px | 제목 한 줄 + 위아래 — 닫기 버튼 상자가 들어간다 |
header | 간격 | spacing-x1_5 6px | 제목 ↔ 설명. 닫기 버튼이 있으면 오른쪽 52(24 + 아이콘 22 + 6) |
title | 글자 | text-t8 22px / 30px | |
title | 굵기 | 700 | |
title | 글자·아이콘 | color-fg-neutral #1A1F2E · 다크 #F5F6FA | |
description | 글자 | text-t5 16px / 22px | |
description | 굵기 | 400 | |
description | 글자·아이콘 | color-fg-neutral-muted #535866 · 다크 #B7BDCC | |
closeButton | 크기 | 52px | 투명 상자 — 보이는 건 아이콘 22(Dialog 의 닫기와 같다) |
closeButton | 모서리 | radius-r3 12px | |
closeButton | 아이콘 크기 | 22px | lucide x |
closeButton | 색 | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | |
closeButton | 위 거리 | 28px | 아이콘이 패널 위에서 28 · 오른쪽 24 — 상자는 위 13 · 오른쪽 9. 제목 첫 줄 가운데와 맞는다 |
closeButton | 오른쪽 거리 | spacing-x6 24px | |
closeButton | 터치 영역 | 52 × 52 | |
closeButton | 전환 대상 | background-color, scale | |
closeButton | 전환 시간 | motion-duration-color-transition 150ms | |
closeButton | 전환 곡선 | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | |
divider | 높이 | 1px | |
divider | 배경 | color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D | 머리 아래 — 본문이 위 끝에서 떨어지면 나타난다(SEED). SEED stroke.neutral-muted 의 불투명 짝 |
divider | 전환 시간 | motion-duration-color-transition 150ms | |
divider | 전환 곡선 | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | |
body | 좌우 여백 | spacing-x6 24px | |
body | 아래 여백 | spacing-x6 24px | 바닥이 없을 때만 — 바닥이 있으면 0 |
body | 세로 넘침 | auto | |
footer | 위 여백 | spacing-x4 16px | |
footer | 좌우 여백 | spacing-x6 24px | |
footer | 아래 여백 | spacing-x6 24px | |
footer | 간격 | spacing-x2 8px | |
footer | 가로 맞춤 | flex-end | 오른쪽 정렬 — [취소] [저장](Dialog 와 같다) |
footer | 버튼 크기 | small 36 | Button small — 1280 이상의 오른쪽 패널 |
본문 끝 흐림
넘칠 수 있는 본문(주 메뉴 · 목록 · 긴 폼)은 Scroll Fog 를 건다(scrollFog) — 위 20 · 아래 80 이 늘 흐리고 본문 안에 그만큼 여백을 둔다. 시트 · 대화상자와 같은 규칙이다(SEED Side Panel 의 늘 켜진 아래 48 대신). 칸 두셋처럼 늘 들어맞는 본문에는 걸지 않는다.
본문 끝 흐림
| scrollFog | body 위 여백 | body 아래 여백 | body 스크롤 여유 | fog 크기 | fog 마스크 | 설명 |
|---|---|---|---|---|---|---|
off (기본) | — | — | — | — | — | 기본 — 늘 들어맞는 본문 |
on | 20px — 머리 아래 16 은 그대로 두고 본문 안에 더한다 | 80px — 바닥이 있어도 — 끝까지 내려도 마지막 줄이 흐림 아래 남지 않는다 | 위 20px · 아래 80px — 키보드로 옮긴 칸이 흐림 아래 멈추지 않게 | 위 20px · 아래 80px — 본문 상자의 위 · 아래 끝 | gradient-fade-mask — Scroll Fog overlayBody(scroll-fog.yaml) — 늘 켜져 있다. SEED Side Panel 의 아래 48 대신 | 넘칠 수 있는 본문(주 메뉴 · 목록 · 긴 폼) — 위 20 · 아래 80 이 늘 흐리다 |
State
| 상태 | 모습 |
|---|---|
enabled | 열림 — 딤 위 패널 |
scrolled | 본문이 위로 스크롤됨 — 머리 아래 1px stroke-neutral-subtle |
pressed | 닫기 버튼 — bg-layer-floating-pressed + 2px 거리 축소 |
focused | 키보드 포커스에만 링 2px · 띄움 2px |
상태
| 상태 | closeButton 배경 | closeButton 배율 | divider 배경 | divider 불투명도 | 링 두께 | 링 간격 | 링 색 |
|---|---|---|---|---|---|---|---|
enabled 열림 | — | — | color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D — 머리 아래 — 본문이 위 끝에서 떨어지면 나타난다(SEED). SEED stroke.neutral-muted 의 불투명 짝 | — | — | — | — |
scrolled 본문이 위로 스크롤됨 — 머리 아래 선 | — | — | color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D — 머리 아래 — 본문이 위 끝에서 떨어지면 나타난다(SEED). SEED stroke.neutral-muted 의 불투명 짝 | 1 — 본문을 위로 스크롤하면 나타난다 | — | — | — |
pressed 닫기 버튼 누름 — 바탕 + 2px 거리 축소 | color-bg-layer-floating-pressed #F7F8FD · 다크 #353B4D — SEED bg.transparent-pressed 의 떠 있는 표면 짝(v102) | 2px 거리 축소 — 기준 52 → 0.962 — 모션 줄이기면 하지 않는다 | color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D — 머리 아래 — 본문이 위 끝에서 떨어지면 나타난다(SEED). SEED stroke.neutral-muted 의 불투명 짝 | — | — | — | — |
focused 웹 — 키보드 포커스에만 링 | — | — | color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D — 머리 아래 — 본문이 위 끝에서 떨어지면 나타난다(SEED). SEED stroke.neutral-muted 의 불투명 짝 | — | 2px | 2px | color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA |
모션
| 전환 | 시간 | 곡선 | 대상 | 비고 |
|---|---|---|---|---|
열림 — 패널 | motion-duration-d6 300ms | motion-ease-enter-expressive cubic-bezier(0.03, 0.4, 0.1, 1) | translateX ∓100% → 0 | 붙은 쪽에서 미끄러져 들어온다(SEED). 모션 줄이기면 150ms 서서히 나타남(v104) |
열림 — 딤 | motion-duration-d6 300ms | motion-ease-enter cubic-bezier(0, 0, 0.15, 1) | opacity 0 → 1 | |
닫힘 — 패널 | motion-duration-d6 300ms | motion-ease-exit-expressive cubic-bezier(0.35, 0, 0.95, 0.55) | translateX → ∓100% | 끌다 놓아 되돌아갈 때도 같은 시간 |
닫힘 — 딤 | motion-duration-d6 300ms | motion-ease-exit cubic-bezier(0.35, 0, 1, 1) | opacity → 0 | |
머리 아래 선 | motion-duration-color-transition 150ms | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | box-shadow | |
누름 — 닫기 버튼 | motion-duration-pressed-scale 150ms | motion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1) | scale |
Guidelines
쓰는 자리
| 이런 일 | 768 미만 | 768 ~ 1279 | 1280 이상 |
|---|---|---|---|
| 주 메뉴 | Desk 하단 탭 바 · HR Side Panel(왼쪽) | Side Navigation(접힘) | Side Navigation(펼침) |
| 지금 화면을 떠나지 않는 보조 작업(상세 · 설정) | Bottom Sheet | Bottom Sheet | Dialog — 옆에 둘 까닭이 있으면 Side Panel(오른쪽) |
| 아래에서 올라오는 모달 | Bottom Sheet | Bottom Sheet | — |
오른쪽 패널은 1280 이상에서만이다 — 1280 미만에서는 같은 내용을 Bottom Sheet 로 띄운다. 1280 은 Dialog 의 Responsive Dialog · Input Button 과 같은 경계다 — 768 ~ 1279 에서 오른쪽 패널을 두면 그 안의 고르기 칸은 시트로, 같은 내용의 폼은 시트로 열려 표면이 엇갈려 겹친다. 새 경계를 두지 않는다(2026-10-02 사용자 결정 1280 을 옮겼다 — 2026-10-22 사용자 결정 11B, https://claude.ai/artifact/YRPbWzhgD4EDdKsVioU8Zj). 옆에 둘 까닭은 본문을 보면서 함께 다룰 때(목록을 보며 한 줄의 상세)다 — 그렇지 않으면 Dialog 다. 지금은 오른쪽 패널을 쓰는 곳이 없다. 다른 표면과 견준 "상황 → 고를 것" 표는 DESIGN.md Components 의 "시트 · 대화상자 · 확인창 · 팝오버" 한 벌이다(3C).
주 메뉴 서랍(HR 폰)
HR 웹 768 미만의 Top Navigation ☰ 가 연다.
- 왼쪽 · 화면 폭의 80% · 제목은 서비스 이름("그루 이음").
- 본문은 Side Navigation 의 묶음 · 항목을 펼친 모양 그대로 담는다 — 본문 좌우 16 에 항목(좌우 8)이 들어와 아이콘이 제목과 같은 24 에 선다. 넘칠 수 있어
scrollFog를 건다. - 지금 화면의 묶음은 펼쳐진 채 열리고 지금 항목이 보인다.
- 항목을 누르면 이동하고 서랍이 닫힌다 — 다음 화면의 초점 · 스크롤은 Top Navigation 의 "화면을 옮길 때".
- 바닥에는 계정(Side Navigation 의 바닥과 같은 것)을 둔다.
- 창이 768 이상으로 넓어지면 닫힌다 — 사이드바가 그 자리를 맡는다.
입력 폼은 실수로 닫히지 않게
오른쪽 패널에 입력 폼을 띄우면 Dialog 와 같다 — 바깥(딤)을 눌러도, 끌어도 닫히지 않고, 바닥 [취소] · Esc 로 닫으며, 바뀐 값이 있으면 닫기 전에 "작성한 내용이 사라져요" 를 묻는다. 입력 폼에는 머리 닫기 버튼을 두지 않는다(닫기와 취소는 하나만). 조회 · 주 메뉴는 머리 닫기 · 딤 누르기 · Esc · 끌기로 닫힌다.
평평하게
패널은 화면 높이 전체에 붙고 모서리 · 그림자 · 테두리가 없다 — 딤과 면 색(bg-layer-floating)으로 뜬다. 패널 위에 또 패널을 겹치지 않는다 — 그 안에서는 Popover · Select 목록 · 확인창(Alert Dialog)만 뜬다(SEED 고도 — Side Panel 은 Level 2, Dialog 는 Level 3).
글
제목은 무엇을 하는 패널인지 — 주 메뉴 서랍은 서비스 이름. 설명은 덧붙일 말이 있을 때만 한 문장(해요체 · 마침표). 바닥 버튼은 동작 이름("저장") — "확인" 으로 뭉뚱그리지 않는다.
코드
레시피 recipes/shadcn/components/ui/side-panel.tsx 를 쓴다(Radix Dialog 위 — 끌어 닫기 · 뒤로 가기 닫기는 레시피가 더한다). 아래 미리보기는 스펙 값으로 그린 모습이다.
SidePanel—open·onOpenChange·form(바깥 누르기 · 끌기로 닫지 않는다) ·dirty(닫기 전에 묻는다).SidePanelTrigger—asChild.SidePanelContent— 패널.side("left"·"right"기본) ·size("small"·"medium"기본 ·"large"— 오른쪽만) ·title(필수) ·description·showCloseButton(기본true,form이면 그리지 않는다).SidePanelBody— 본문.scrollFog(위 20 · 아래 80).SidePanelFooter— 바닥 버튼, 오른쪽 정렬.
1280 미만에서 오른쪽 패널을 Bottom Sheet 로 바꾸는 부품은 오른쪽 패널을 쓸 자리가 생길 때 Responsive Dialog 처럼 더한다.
주 메뉴 서랍 — HR 폰
import { SidePanel, SidePanelBody, SidePanelContent } from "@/components/ui/side-panel"
import { SideNavigationContent, SideNavigationGroup, SideNavigationItem, SideNavigationSubItem } from "@/components/ui/side-navigation"
<SidePanel open={menuOpen} onOpenChange={setMenuOpen}>
<SidePanelContent side="left" title="그루 이음" id="main-menu">
<SidePanelBody scrollFog className="px-x4">
{/* 사이드바와 같은 항목 — 지금 묶음은 저절로 펼쳐지고, 항목을 누르면 이동하고 닫힌다 */}
<SideNavigationContent onNavigate={() => setMenuOpen(false)}>
<SideNavigationGroup label="근무">
<SideNavigationItem href="/calendar" icon={<CalendarDays />} label="캘린더" current={path === "/calendar"} />
<SideNavigationItem icon={<Plane />} label="휴가">
<SideNavigationSubItem href="/vacation/history" label="휴가 현황" current={path === "/vacation/history"} />
<SideNavigationSubItem href="/vacation/application" label="휴가 신청" current={path === "/vacation/application"} />
</SideNavigationItem>
</SideNavigationGroup>
</SideNavigationContent>
</SidePanelBody>
</SidePanelContent>
</SidePanel>오른쪽 패널 — 1280 이상(쓰는 곳 없음)
<SidePanel open={open} onOpenChange={setOpen} form dirty={isDirty}>
<SidePanelContent side="right" size="medium" title="알림 설정">
<SidePanelBody>…</SidePanelBody>
<SidePanelFooter>
<Button variant="neutralWeak" size="small" onClick={() => setOpen(false)}>취소</Button>
<Button size="small" onClick={save}>저장</Button>
</SidePanelFooter>
</SidePanelContent>
</SidePanel>Behavior
| 인터랙션 | 동작 |
|---|---|
| 열기 | 붙은 쪽에서 300ms 로 미끄러져 들어온다. 처음 초점은 패널에 간다 |
닫기 버튼 · Esc | 닫는다. 입력 폼에 바뀐 값이 있으면 먼저 묻는다 |
| 뒤로 가기(기기 · 브라우저) | 1280 미만에서만 닫는다 — 열 때 기록을 하나 쌓고 뒤로 가기가 그것을 걷는다(Dialog · Bottom Sheet 와 같은 규칙). 1280 이상의 오른쪽 패널은 기록을 쌓지 않아 뒤로 가기가 화면을 옮긴다 |
| 바깥(딤) 누르기 | 주 메뉴 · 조회는 닫는다. 입력 폼은 무시한다 |
| 붙은 쪽으로 끌기(손가락 · 펜) | 놓을 때 빠르게(0.4px/ms 넘게) 끌었거나 폭의 25% 이상 밀었으면 닫고, 아니면 제자리로(Bottom Sheet 와 같은 기준). 마우스로는 끌지 않는다 — 글 고르기와 겹친다. 입력 폼은 끌리지 않는다 |
| 주 메뉴의 항목 누르기 | 이동하고 닫는다 |
| 창이 768 이상으로 넓어짐(주 메뉴) | 닫는다 — 사이드바가 맡는다 |
| 본문 스크롤 | 본문만 — 위로 스크롤되면 머리 아래 선. 끝 흐림(scrollFog)은 걸었으면 늘 그대로 |
| 닫힌 뒤 | 300ms 로 나가고, 초점은 연 자리(☰ · 트리거)로 돌아간다 |
| 열린 동안 | 뒤 화면을 보조 기술에서 숨기고 스크롤을 잠근다. 초점은 패널 안을 돈다 |
Accessibility
| 기준 | 검증 |
|---|---|
| WCAG 1.4.3 Color contrast(글자 ≥ 4.5:1) | 제목 fg-neutral 떠 있는 표면(bg-layer-floating) 위 16.41 · 다크 11.62, 설명 fg-neutral-muted 7.11 · 6.67, 주 메뉴 항목 이름 7.11 · 6.67 · 지금 항목 15.20 · 10.32 ✓ |
| WCAG 1.4.11 Non-text contrast(≥ 3:1) | 닫기 아이콘 fg-neutral-subtle 5.50 · 5.27 ✓, 주 메뉴 아이콘 같은 값 ✓. 키보드 포커스 링 Desk 8.38 · 5.28 · HR 8.17 · 5.26 ✓ |
| WCAG 2.4.3 Focus order | 열면 패널로, 닫으면 연 자리로 초점이 간다. 열린 동안 초점이 뒤 화면으로 나가지 않는다 ✓ |
| WCAG 2.5.1 Pointer Gestures | 끌기 말고도 딤 누르기 · 닫기 · Esc · 뒤로 가기로 닫힌다 ✓ |
| WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24) | 닫기 52 · 주 메뉴 항목 44 · 바닥 버튼 36 ✓ |
| WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44) | 닫기 52 ✓ · 항목 44 ✓ · 바닥 버튼은 누르는 영역을 44 까지(Button) ✓ |
| ARIA | role="dialog" + aria-modal="true", 제목 aria-labelledby(제목 h2) · 설명 aria-describedby(있을 때만), 닫기 버튼 이름 "닫기". 주 메뉴 서랍 안의 목록은 <nav aria-label="주 메뉴">(Side Navigation 의 내용) |
Do / Don't
✅ Do
- 왼쪽은 768 미만의 주 메뉴, 오른쪽은 1280 이상의 보조 작업 — 1280 미만은 Bottom Sheet.
- 평평하게 — 높이 전체 · 모서리 · 그림자 · 선 없음.
- 주 메뉴는 지금 묶음을 펼친 채 열고, 이동하면 닫는다.
- 입력 폼은 바깥 · 끌기로 닫지 않고, 바뀐 값이 있으면 묻는다.
❌ Don't
- 아래 · 위에서 나오는 패널(아래는 Bottom Sheet).
- 1280 미만에서 오른쪽 패널.
- 이동한 뒤에도 열려 있는 서랍 · 지금 묶음이 접힌 채 여는 서랍.
- 패널 위에 패널 · 그림자 · 둥근 모서리.
- 영어 이름("Sidebar" · "Close").
Specification
side-panel.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹이 Side Panel 을 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다.
Base
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
overlay | enabled | 배경background | overlay-dim-lightrgba(0, 0, 0, 0.50)다크 overlay-dim-darkrgba(0, 0, 0, 0.65)groo 딤(v102) — SEED 0.455 는 들이지 않았다 |
| z-indexzIndex | 100z-modal — specs/z-index.md L2 | ||
root | enabled | 높이height | 화면 높이 전체dvh — 방향과 상관없이(SEED) |
| 최대 너비maxWidth | 80%딤이 늘 20% 남는다(SEED 코드). 768 미만이면 크기와 상관없이 화면 폭의 80% | ||
| 아래 여백paddingBottom | 아래 안전 영역패널 내용의 맨 아래 — 바닥이 없어도(SEED). 붙은 쪽 안전 영역(가로 노치)은 폭에 더한다 | ||
| 모서리radius | 0px평평 — 모서리 · 그림자 · 선 없이 딤과 면 색으로 뜬다(SEED) | ||
| 배경background | bg-layer-floating#FFFFFF다크 #2D3346 | ||
| 그림자shadow | none | ||
| z-indexzIndex | 101z-modal-content — specs/z-index.md L2 | ||
header | enabled | 최소 높이minHeight | 70px제목 한 줄 + 위아래 — 닫기 버튼 상자가 들어간다 |
| 좌우 여백paddingX | spacing-x624px | ||
| 위 여백paddingTop | spacing-x624px위 안전 영역을 더한다 | ||
| 아래 여백paddingBottom | spacing-x416px | ||
| 간격gap | spacing-x1_56px제목 ↔ 설명. 닫기 버튼이 있으면 오른쪽 52(24 + 아이콘 22 + 6) | ||
title | enabled | 글자·아이콘foreground | fg-neutral#1A1F2E다크 #F5F6FA |
| 글자typography | text-t822px / 30px | ||
| 굵기fontWeight | 700 | ||
description | enabled | 글자·아이콘foreground | fg-neutral-muted#535866다크 #B7BDCC |
| 글자typography | text-t516px / 22px | ||
| 굵기fontWeight | 400 | ||
closeButton | enabled | 크기size | 52px투명 상자 — 보이는 건 아이콘 22(Dialog 의 닫기와 같다) |
| 아이콘 크기iconSize | 22pxlucide x | ||
| 모서리radius | radius-r312px | ||
| 색color | fg-neutral-subtle#62697A다크 #A2A8B7 | ||
| 터치 영역touchTarget | 52 × 52 | ||
| 전환 대상transitionProperty | background-color, scale | ||
| 전환 시간transitionDuration | motion-duration-color-transition150ms | ||
| 전환 곡선transitionEasing | motion-ease-easingcubic-bezier(0.35, 0, 0.35, 1) | ||
| 위 거리top | 28px아이콘이 패널 위에서 28 · 오른쪽 24 — 상자는 위 13 · 오른쪽 9. 제목 첫 줄 가운데와 맞는다 | ||
| 오른쪽 거리right | spacing-x624px | ||
divider | enabled | 높이height | 1px |
| 배경background | stroke-neutral-subtle#EDEFF3다크 #353B4D머리 아래 — 본문이 위 끝에서 떨어지면 나타난다(SEED). SEED stroke.neutral-muted 의 불투명 짝 | ||
| 전환 시간transitionDuration | motion-duration-color-transition150ms | ||
| 전환 곡선transitionEasing | motion-ease-easingcubic-bezier(0.35, 0, 0.35, 1) | ||
body | enabled | 좌우 여백paddingX | spacing-x624px |
| 아래 여백paddingBottom | spacing-x624px바닥이 없을 때만 — 바닥이 있으면 0 | ||
| 세로 넘침overflowY | auto | ||
footer | enabled | 버튼 크기buttonSize | small 36Button small — 1280 이상의 오른쪽 패널 |
| 좌우 여백paddingX | spacing-x624px | ||
| 위 여백paddingTop | spacing-x416px | ||
| 아래 여백paddingBottom | spacing-x624px | ||
| 간격gap | spacing-x28px | ||
| 가로 맞춤justifyContent | flex-end오른쪽 정렬 — [취소] [저장](Dialog 와 같다) | ||
divider | scrolled | 불투명도opacity | 1본문을 위로 스크롤하면 나타난다 |
closeButton | pressed | 배경background | bg-layer-floating-pressed#F7F8FD다크 #353B4DSEED bg.transparent-pressed 의 떠 있는 표면 짝(v102) |
| 배율scale | 2px 거리 축소기준 52 → 0.962 — 모션 줄이기면 하지 않는다 | ||
focusRing | focused | 링 두께width | 2px |
| 링 색color | stroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84 | ||
| 링 간격offset | 2px |
side=right
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 자리position | 오른쪽 끝1280 이상 — 미만에서는 같은 내용을 Bottom Sheet 로 |
size=small
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 너비width | 480px |
size=medium
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 너비width | 720px |
size=large
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 너비width | 960px |
side=left
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 너비width | 화면 폭의 80%768 미만에서만 쓴다 — 딤이 오른쪽 20% 에 남는다. 크기(480 · 720 · 960)는 오른쪽 패널만 쓴다 |
| 자리position | 왼쪽 끝왼쪽으로 끌면 닫힌다 | ||
title | enabled | 글자typography | text-t822px / 30px서비스 이름("그루 이음") — 주 메뉴 서랍의 제목 |
body | enabled | 좌우 여백paddingX | spacing-x416px주 메뉴 — Side Navigation 의 항목(좌우 8)이 들어와 아이콘이 제목과 같은 24 에서 선다 |
scrollFog=on
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
body | enabled | 위 여백paddingTop | 20px머리 아래 16 은 그대로 두고 본문 안에 더한다 |
| 아래 여백paddingBottom | 80px바닥이 있어도 — 끝까지 내려도 마지막 줄이 흐림 아래 남지 않는다 | ||
| 스크롤 여유scrollPadding | 위 20px · 아래 80px키보드로 옮긴 칸이 흐림 아래 멈추지 않게 | ||
fog | enabled | 크기size | 위 20px · 아래 80px본문 상자의 위 · 아래 끝 |
| 마스크mask | gradient-fade-masklinear-gradient(#00000000 0%, #00000003 8%, #00000005 16%, #0000000d 22%, #00000014 29%, #00000021 35%, #0000002e 41%, #00000040 47%, #00000052 53%, #00000066 59%, #0000007a 65%, #00000094 71%, #000000ab 78%, #000000c7 84%, #000000e3 92%, #000000ff 100%)Scroll Fog overlayBody(scroll-fog.yaml) — 늘 켜져 있다. SEED Side Panel 의 아래 48 대신 |
SEED 와 다른 점
- 딤은 groo 0.50 · 다크 0.65(v102) — SEED 0.455.
- 본문 끝 흐림은 Scroll Fog(2026-10-03) — 넘칠 수 있는 본문에 늘 켜진 위 20 · 아래 80. SEED Side Panel 은 본문 아래 48 을 늘 흐린다.
- 반투명 색을 불투명 짝으로(v102) — 머리 아래 선
stroke-neutral-subtle, 닫기 버튼 누름bg-layer-floating-pressed. - 1280 미만의 오른쪽 패널은 Bottom Sheet — SEED Responsive Side Panel 은 md(768)에서 바꾼다. groo 는 Responsive Dialog · Input Button 과 같은 1280 이다.
- 기본 크기는 medium 720 — SEED React · CSS 와 같다(rootage JSON 기본은 small).
- 가로 화면의 노치 — SEED 문서는 "패널 너비에 safe-area-inset을 더합니다", 코드는 폭 안에서 뺀다. groo 는 문서를 따른다(내용 폭이 줄지 않게).
- 입력 폼은 바깥 · 끌기로 닫지 않는다 — Dialog · Bottom Sheet 와 같은 groo 규칙(2026-10-02). SEED 는 모든 패널이 바깥 누르기 · 가로 끌기(25%)로 닫힌다.
- 모션 줄이기면 150ms 서서히 나타남(v104) — SEED 는 300ms 미끄러짐 그대로.
- Persistent(비모달) 패널은 보류 — SEED 는 Backdrop 없이 배경과 함께 다루는 Persistent Type 을 둔다(md 미만은 Modal). groo 는 지금 쓸 자리가 없어 두지 않았고, 생기면 그때 정한다 — 데스크톱 필터 · 목록 옆 상세(dialog.md "앱 적용 때 정할 자리")가 후보다(2026-10-22 사용자 결정 11B — https://claude.ai/artifact/YRPbWzhgD4EDdKsVioU8Zj).
- z-index 는 specs/z-index.md 의 L2(딤
z-modal100 · 패널z-modal-content101).
Migration notes
2026-10-22 — 이동 부품 표 링크
머리말에 DESIGN.md Layout 의 "이동 부품 고르기" 링크를 더했다(6B — https://claude.ai/artifact/Mag1tEv51MdQHdy1o8hY3L). 값 변경 없음.
2026-10-22 — 1280 의 까닭 · Persistent 는 보류
"쓰는 자리" 표 아래에 1280 의 까닭(Responsive Dialog · Input Button 과 같은 경계 — 768 ~ 1279 에서 표면이 엇갈리지 않게)을 적었다. "Persistent 는 두지 않는다" 는 묻지 않고 들어간 문장이라 "지금은 쓸 자리가 없다 — 생기면 그때 정한다" 로 바꿨다(사용자 결정 11B — https://claude.ai/artifact/YRPbWzhgD4EDdKsVioU8Zj). 값 변경 없음.
2026-10-04 — SEED Side Panel 로 새로 둔다(옛 Sheet 를 대신)
사용자가 화면 틀 · 이동 비교 페이지의 "따라오는 것" 으로 정했다 — Sheet 를 Side Panel 로: 왼쪽 · 오른쪽만(아래는 Bottom Sheet, 위는 걷음), 480 · 720 · 960 · 768 미만 80%, 평평(모서리 · 그림자 없음), 머리 24 · 제목 22 / 30 · 닫기 상자 52 · 아이콘 22. 지금 쓰는 곳은 HR 폰 메뉴 서랍(왼쪽) 하나 — 이동하면 닫고, 지금 묶음을 펼친 채 연다. 2026-10-02 Bottom Sheet 의 "오른쪽에서 나오는 패널은 Side Panel 차례에 정한다" 가 이것이다. 옛 Sheet(shadcn — 네 방향 · 폭 75% · 최대 384 · shadow-xl · 가장자리 1px 선 · 제목 18 / 600 · 닫기 28)는 걷었다 — 옛 스펙은 sheet.history/v-pre-seed-nav.*.
제품은 앱 적용 단계에서 옮긴다(2026-10-04 조사).
- HR 폰 메뉴 서랍 — shadcn Sheet 왼쪽 288(80%) ·
#FAFAFA. 이름이 "Sidebar" · 설명 "Displays the mobile sidebar."(영어), 열면 지금 화면의 묶음도 접혀 있고(treeView.tsx:263-265— 펼침 초기값은 첫 마운트에만), 잎을 눌러 이동해도 서랍이 열린 채 남는다(SidebarContent.tsx:101-108·shared/ui/shadcn/sidebar.tsx:198-201, F12). Side Panel(왼쪽) + Side Navigation 항목으로. - Desk 웹 — 모바일 사이드바 시트는 닿지 않는 코드다(768 미만은 하단 탭 바 — F27). 옆 패널을 쓰는 화면이 없다.
- HR 나눔 패널 4(회사 · 부서 · 역할 · 사용자 — Resizable 25 / 75,
CompanyContent.tsx:116-140외)은 화면 안의 나눔이지 Side Panel 이 아니다 — Resizable(2026-10-10 — 목록 20 ~ 40%). - 앱 적용 때 정할 자리 — HR 전체 화면 Dialog 3(페이지 · 시트 · 오른쪽 Side Panel — dialog.md), 데스크톱 필터(Dialog · Popover · 오른쪽 Side Panel).
Side Navigation
데스크톱(768 이상)의 주 메뉴 — 화면 왼쪽의 세로 띠(사이드바)에 서비스의 최상위 화면들을 묶어 둔다. 1280 이상은 펼침 240, 768 ~ 1279 는 아이콘만 56 으로 저절로 접히고, 손으로 접고 펼 수 있다. 768 미만에서는 없다 — Desk 웹은 Bottom Navigation, HR 은 ☰ 로 여는 왼쪽 Side Panel 에 같은 항목을 담는다. 다른 이동 부품(Top Navigation · Tabs)과 고르는 표는 DESIGN.md Layout 의 "이동 부품 고르기" 한 곳이다.
Skeleton
불러오는 동안 곧 나타날 내용의 자리를 미리 그리는 회색 면. 목록 · 카드 · 상세처럼 구조가 정해진 자리에 쓴다 — 화면의 틀(머리 · 탭 · 제목 · 버튼 · 카드 면)은 실제로 그리고, 서버에서 올 데이터 자리만 스켈레톤이다. 저장 · 새로 고침처럼 행동 하나를 기다리는 자리는 Progress Circle, 이미지가 없거나 못 불러온 자리는 Content Placeholder 다.