Collapsible
내용 하나를 접고 펴는 자리 — 거래 상세의 분할 내역, 분할 합계가 어긋날 때의 "빠르게 맞추기", HR 업무 보고의 상세 검색 조건. 트리거는 Accordion 항목 하나의 모양이다. 같은 모양의 항목이 여럿이면 Accordion 이다.
SEED 에는 Collapsible 컴포넌트가 없다 — SEED Accordion 가이드의 한 줄 "단일 콘텐츠를 접고 펼치는 용도는 해당 아이템에 Collapsible 동작을 활용할 수 있어요" 뿐이다(Apache-2.0). groo 는 그 문장대로 트리거를 Accordion 항목 하나의 모양으로 둔다 — 줄 전체가 트리거, 머리 글이 곧 이름, 54 · 74, 꺾쇠 20(2026-10-10 사용자 결정 9A). 이름은 상태마다 바꾸지 않고 펼침은 aria-expanded 가 알린다(APG Disclosure), 펼친 뒤 초점은 트리거에 그대로다. 옛 Collapsible 스펙("스타일 0 · 동작만 — 시각은 사용처" · Button 으로 감싼 트리거 · "더 보기" 쓰임)을 대신한다.
수치 원본은 collapsible.yaml이다 — 트리거 · 상태 · 모션 값은 accordion.yaml과 같고, 놓인 면(surface)만 더 있다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.
직접 골라 보기
놓인 면(흰 면 · 카드 · 경고 상자) · 설명 · 막힘 · 모션 줄이기를 고르면 스펙대로 그린 Collapsible 과 그 코드가 바뀐다. 트리거를 눌러 펼치고 초점을 두고 Enter · Space 를 눌러 볼 수 있다 — 옆 글이 읽기(이름 · 펼침)를 따라간다.
화면 읽기내역 분할 6개, 합계 −123,456,789원, 머리말 3, 단추, 접힘
import { useState } from "react"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
const [open, setOpen] = useState(false)
<Collapsible open={open} onOpenChange={setOpen}>
<CollapsibleTrigger title="내역 분할 6개" description="합계 −123,456,789원" />
<CollapsibleContent layout="rows">{splits.map(renderSplitRow)}</CollapsibleContent>
</Collapsible>Anatomy
| 부위 | 설명 |
|---|---|
| ⓐ Trigger | 트리거 — 줄 전체가 누르는 자리. 머리말(h3) 안의 단추다. |
| ⓑ Title | 이름 — 16 / 22 · 500. 무엇을 여닫는지 — 상태마다 바꾸지 않는다. |
| ⓒ Description | 설명 — 13 / 18 · 500 · 옅은 색. 합계 · 적용한 필터 수처럼 접혀 있어도 알아야 할 것, 있을 때만. |
| ⓓ Chevron | 꺾쇠 — 20. 접혀 있으면 아래, 펼치면 위. |
| ⓔ Panel | 펼친 내용 — 트리거와 같은 좌우, 아래 16. 안에서 스크롤하지 않는다. |
부위
| 부위 | 설명 |
|---|---|
root | 접는 자리 하나 — 트리거 + 펼친 내용. surface card 면 Card 면 |
heading | 머리말 — 트리거를 감싼다(heading > button). 모양은 없다 |
trigger | 트리거 — 줄 전체가 누르는 자리(button). Accordion 항목과 같은 모양 |
background | 트리거 바탕 층 — 누름 · 호버 바탕. 줄지 않는다 |
content | 트리거 콘텐츠 층 — 본문 · 꺾쇠. 누르면 이 층만 준다 |
body | 본문 — 이름 + 설명 |
title | 이름 — 무엇을 여닫는지. 상태마다 바꾸지 않는다 |
description | 설명 — 있을 때만(합계 · 적용한 필터 수) |
chevron | 꺾쇠 — 오른쪽 끝. 펼치면 위를 가리킨다 |
panel | 펼친 내용 — 높이로 펼치고 접는다. 안에서 스크롤하지 않는다 |
divider | 트리거 위 선 — 경고 상자 아래 줄일 때만 |
box | 경고 상자 — surface warning 일 때 Collapsible 을 품는 상자(분할 합계 경고). 위에 경고 글, 아래 줄이 트리거 |
focusRing | 키보드 포커스 링(웹) — 트리거 안쪽 |
Properties
트리거 — Accordion 항목 하나
트리거는 Accordion 항목과 같다 — 위아래 16 · 좌우 24, 이름 16 / 22 · 500 fg-neutral, 설명 13 / 18 · 500 fg-neutral-subtle(이름 아래 2), 꺾쇠 20 fg-neutral-subtle, 본문 ↔ 꺾쇠 12. 이름만이면 54, 설명이 있으면 74 다. 줄 전체가 누르는 자리라 글 단추 18 · 16.5 같은 작은 트리거가 생기지 않는다. 지금의 머리 + 오른쪽 글 단추 배치를 지키고 단추만 44 로 키우는 안(9B — 자리마다 단추 모양이 남는다)과 지금 그대로(9C)는 고르지 않았다.
설명 있음 · 없음
| description | trigger 최소 높이 | 설명 |
|---|---|---|
none (기본) | — | 이름만 — 트리거 54(기본) |
shown | 74px — 위아래 16 + 이름 22 + 2 + 설명 18 | 이름 + 설명 — 트리거 74 |
트리거
| 속성 | 값 | 비고 |
|---|---|---|
| 최소 높이 | 54px | 위아래 16 + 이름 22 — Accordion 항목과 같다. 이름이 줄을 바꾸면 늘어난다 |
| 상하 여백 | spacing-x4 16px | |
| 좌우 여백 | spacing-global-gutter 24px | 화면 가장자리 규칙 24 |
| 글 맞춤 | left | |
| 커서 | pointer | |
| 터치 영역 | 트리거 전체 | 줄 폭 × 54 이상 — 글 단추 · 꺾쇠만 누르게 하지 않는다(사용자 결정 9A). AA ✓ · AAA ✓ |
이름
| 속성 | 값 | 비고 |
|---|---|---|
| 글자 | text-t5 16px / 22px | |
| 굵기 | 500 | |
| 글자·아이콘 | color-fg-neutral #1A1F2E · 다크 #F5F6FA | |
| 줄 수 | none | 늘 줄을 바꾼다(10A) |
설명
| 속성 | 값 | 비고 |
|---|---|---|
| 글자 | text-t3 13px / 18px | |
| 굵기 | 500 | SEED accordion-item description medium — 머리 제목 500 과 한 벌. 글자 역할 R9 의 "여닫는 머리 설명"(2026-10-17 사용자 결정 01T-005 — 줄 설명 400 에 맞추지 않는다) |
| 글자·아이콘 | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | |
| 줄 수 | none |
꺾쇠
| 속성 | 값 | 비고 |
|---|---|---|
| 크기 | 20px | lucide chevron-down — 접혀 있으면 아래 |
| 색 | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | |
| 회전 | 0deg | |
| 전환 대상 | rotate | |
| 전환 시간 | motion-duration-d6 300ms | |
| 전환 곡선 | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) |
펼친 내용
| 속성 | 값 | 비고 |
|---|---|---|
| 보이기 | 숨김 | 접혀 있으면 내용을 그리지 않는다 — 보조 기술도 읽지 않는다(앱도) |
| 위 여백 | 0px | |
| 좌우 여백 | spacing-global-gutter 24px | 트리거와 같은 좌우 — 내용(줄 · 칸 · 글)은 이 안에서 시작한다 |
| 아래 여백 | spacing-x4 16px | 트리거 위아래와 같은 16 |
| 최대 높이 | none | 패널 안에서 스크롤하지 않는다 — 길면 놓인 화면 · 시트가 스크롤한다 |
펼친 내용은 트리거와 같은 좌우 안에 두고 아래 16 으로 끝난다. 내용이 줄 목록이면(분할 내역 — 줄마다 위아래 12) 아래는 8 이다(layout="rows") — 마지막 줄의 글 아래로 20 이 된다.
펼친 내용 — 글 · 줄 목록
| layout | panel 아래 여백 | 설명 |
|---|---|---|
text (기본) | — | 글 · 카드 · 칸 — 펼친 내용 아래 16(기본) |
rows | spacing-x2 8px — 8 — 줄마다 위아래 12 를 가져 마지막 줄의 글 아래로 20(비교 페이지 9A 의 분할 내역). 줄의 좌우는 0 — 패널의 좌우가 들인다 | 줄 목록 — 줄이 제 위아래 여백을 가져 펼친 내용 아래 8(거래 상세의 분할 내역) |
Surface
놓인 면에 따라 여백 · 선 · 누름 색이 셋으로 나뉜다. 트리거의 글 · 꺾쇠 · 높이는 같다.
default— 흰 면 · 시트 · 대화상자의 줄. 줄 폭 전체이고 좌우 24 — 거래 상세의 분할 내역.card— Collapsible 자체가 Card 면(1pxstroke-neutral-weak· 모서리 16)이고 트리거가 그 머리다. 펼친 내용은 카드 안 여백 24 로 끝난다 — HR 업무 보고의 상세 검색 조건(툴바 단추가 아니라 조건 카드의 머리가 된다).warning— 경고 상자 안의 아래 줄. 상자(bg-warning-weak· 1pxstroke-warning-weak· 모서리 12 · 경고 글 위 14 · 좌우 16 · 아래 12)의 안쪽 여백에 맞춰 좌우 16, 위에 1pxstroke-warning-weak선, 누름 · 호버는 경고 바탕의 누름 짝bg-warning-weak-pressed— 분할 합계가 어긋날 때의 "빠르게 맞추기".
놓인 면
| surface | 모서리 | 배경 | 테두리 | 테두리 두께 | 넘침 | trigger 좌우 여백 | panel 좌우 여백 | panel 아래 여백 | divider 높이 | divider 배경 | divider 보이기 | 설명 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
default (기본) | — | — | — | — | — | — | — | — | — | — | — | 흰 면 · 시트 · 대화상자의 줄 — 줄 폭 전체, 좌우 24(기본) |
card | radius-r4 16px — Card 16 — 트리거가 카드 머리다. 안의 누름 바탕은 16 − 6 = 10 으로 동심 | color-bg-layer-default #FFFFFF · 다크 #242938 — Card 면 — 바닥(bg-layer-basement) 위 | color-stroke-neutral-weak #E5E8EF · 다크 #404757 | 1px | hidden | — | — | spacing-x6 24px — 카드 안 여백 24(Card) — 칸 묶음 아래 | — | — | — | 카드 면 — Collapsible 자체가 Card 면이고 트리거가 그 머리다(HR 상세 검색 조건) |
warning | — | — | — | — | — | spacing-x4 16px — 경고 상자의 안쪽 여백 16 — 상자 글과 같은 자리에 선다 | spacing-x4 16px | — | 1px | color-stroke-warning-weak #F5C7B6 · 다크 #833615 — 트리거 위 — 상자 안 경고 글과 트리거 줄을 가른다 | 트리거 위 — 상자 폭 끝까지 | 경고 상자 안의 아래 줄 — 좌우 16 · 위 선 · 누름은 경고 바탕의 누름 색(분할 "빠르게 맞추기") |
경고 상자
| surface | box 좌우 여백 | box 위 여백 | box 아래 여백 | box 모서리 | box 배경 | box 테두리 | box 테두리 두께 | box 넘침 | 설명 |
|---|---|---|---|---|---|---|---|---|---|
default (기본) | — | — | — | — | — | — | — | — | 흰 면 · 시트 · 대화상자의 줄 — 줄 폭 전체, 좌우 24(기본) |
card | — | — | — | — | — | — | — | — | 카드 면 — Collapsible 자체가 Card 면이고 트리거가 그 머리다(HR 상세 검색 조건) |
warning | spacing-x4 16px — 경고 글 좌우 16 — 트리거 좌우와 같다 | spacing-x3_5 14px — 경고 글 위 14 | spacing-x3 12px — 경고 글 ↔ 아래 줄의 선 12 | radius-r3 12px — 12 — 지금 분할 경고 상자의 모서리 | color-bg-warning-weak #FFEFE8 · 다크 #4B2B1F — 놓인 면 — 경고 글(제목 · 설명)과 아래 줄의 트리거를 함께 담는다 | color-stroke-warning-weak #F5C7B6 · 다크 #833615 | 1px | hidden — 아래 줄의 누름 바탕 · 펼친 내용을 모서리에서 자른다 | 경고 상자 안의 아래 줄 — 좌우 16 · 위 선 · 누름은 경고 바탕의 누름 색(분할 "빠르게 맞추기") |
놓인 면 — 상태
| surface | hovered 웹 — 누름과 같은 바탕(v106), 축소 없음 | focused 웹 — 키보드 포커스에만 트리거 안쪽 링 | pressed 바탕 + 콘텐츠만 2px 거리 축소(v104) |
|---|---|---|---|
default | — | — | — |
card | — | focusRing 모서리 radius-r3_5 14px — 카드 모서리 16 − 링 들임 2 = 14 — 트리거가 카드 맨 위라 모서리를 따른다 | — |
warning | background 배경 color-bg-warning-weak-pressed #FAE0D5 · 다크 #65321D — 놓인 면(bg-warning-weak)의 누름 짝 — 꺾쇠 4.37 · 4.35 · 이름 13.04 · 9.59 | focusRing 모서리 radius-r2_5 10px — 상자 모서리 12 − 링 들임 2 = 10 — 트리거가 상자 맨 아래라 모서리를 따른다 | background 배경 color-bg-warning-weak-pressed #FAE0D5 · 다크 #65321D |
State
| 상태 | 모습 |
|---|---|
enabled | 바탕 없음 · 꺾쇠 아래(펼침이면 위) |
hovered | 웹 — 누름과 같은 바탕(v106) — 좌우 6 들어온 bg-layer-default-pressed(경고 상자는 bg-warning-weak-pressed), 모서리 10 |
focused | 웹 — 키보드 포커스에만 트리거 안쪽 링 2px. 카드 · 경고 상자는 그 모서리를 따른다 |
pressed | 호버와 같은 바탕 + 콘텐츠 층만 2px 거리 축소(v104) |
disabled | 이름 · 설명 · 꺾쇠가 fg-disabled — 흐리게 하지 않는다(v106) |
상태
| 상태 | background 바탕 들임 | background 모서리 | background 배경 | content 배율 | title 글자·아이콘 | description 글자·아이콘 | chevron 색 | 링 두께 | 링 간격 | 링 색 | focusRing 모서리 |
|---|---|---|---|---|---|---|---|---|---|---|---|
enabled 기본 | 0px | 0px | transparent | — | color-fg-neutral #1A1F2E · 다크 #F5F6FA | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | — | — | — | — |
hovered 웹 — 누름과 같은 바탕(v106), 축소 없음 | spacing-x1_5 6px | radius-r2_5 10px | color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D — Accordion 과 같다 — 시트 · 대화상자 위에서도 같은 값 | — | color-fg-neutral #1A1F2E · 다크 #F5F6FA | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | — | — | — | — |
focused 웹 — 키보드 포커스에만 트리거 안쪽 링 | 0px | 0px | transparent | — | color-fg-neutral #1A1F2E · 다크 #F5F6FA | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | 2px | -2px — 트리거 안쪽 — 카드 · 상자 · 시트가 바깥 링을 자르지 않게 | color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA | 0px — 흰 면의 줄 — 각진 링 |
pressed 바탕 + 콘텐츠만 2px 거리 축소(v104) | spacing-x1_5 6px | radius-r2_5 10px | color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D | 2px 거리 축소 — 콘텐츠 층만 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(v104) | color-fg-neutral #1A1F2E · 다크 #F5F6FA | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | — | — | — | — |
disabled 전용 색(v106) — 불투명도로 흐리게 하지 않는다 | 0px | 0px | transparent | — | color-fg-disabled #8A91A0 · 다크 #838997 | color-fg-disabled #8A91A0 · 다크 #838997 | color-fg-disabled #8A91A0 · 다크 #838997 | — | — | — | — |
펼침
| open | chevron 회전 | panel 보이기 | 설명 |
|---|---|---|---|
collapsed (기본) | — | — | 접힘 — 처음 상태는 쓰는 쪽이 정한다(기본) |
expanded | 180deg — 위로 | 보임 — 초점은 트리거에 그대로 · 저절로 스크롤하지 않는다(따라오는 것) | 펼침 — 꺾쇠가 위, 내용이 보인다 |
모션
| 전환 | 시간 | 곡선 | 대상 | 비고 |
|---|---|---|---|---|
펼침 · 접힘 — 높이 | motion-duration-d6 300ms | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | height | Accordion 과 같다(사용자 결정 3A) — 펼쳐도 초점 · 스크롤은 그대로 |
꺾쇠 — 회전 | motion-duration-d6 300ms | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | rotate | 0 → 180° — 모션 줄이기면 바로 뒤집힌다 |
모션 줄이기 — 펼침 · 접힘 | motion-duration-d3 150ms | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | opacity | 높이를 움직이지 않고 150ms 서서히(v104) |
누름 · 호버 — 바탕 | motion-duration-color-transition 150ms | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | background-color, inset, border-radius | |
누름 — 콘텐츠 | motion-duration-pressed-scale 150ms | motion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1) | scale | 모션 줄이기면 하지 않는다 |
펼침 · 꺾쇠는 Accordion 과 같은 300ms motion-ease-easing 이고(사용자 결정 3A), 모션 줄이기면 높이 움직임 없이 150ms 서서히 나타나고 사라진다.
Guidelines
언제 쓰나
| 이런 자리 | 쓰는 것 |
|---|---|
| 상세 하나를 접고 편다(분할 내역 · 빠르게 맞추기 · 상세 검색 조건) | Collapsible |
| 같은 모양의 상세가 여럿(혜택 · 유의사항) | Accordion |
| 줄 자체가 다른 일(편집)을 하고 하위 줄만 접는다(카테고리) | List 하위 줄 — 줄 앞 꺾쇠 단추 44 |
| 잘린 글을 펼친다 | 쓰지 않는다 — 글을 자르지 않는다(아래) |
이름은 고정 — 펼침은 상태가 알린다
이름은 무엇을 여닫는지 그대로 쓰고 상태마다 바꾸지 않는다 — "내역 분할 6개" 이지 "합계 123,456,789원" · "펼치기 / 접기" 가 아니다. 펼침은 aria-expanded 가 알린다(APG Disclosure). 설명(합계 · 필터 수)은 이름 뒤에 함께 읽힌다 — 숫자를 이름에 붙여 "필터2" 처럼 읽히게 하지 않는다. 펼쳐도 초점은 트리거에 그대로 두고 화면을 저절로 스크롤하지 않는다.
잘린 글을 펼치지 않는다 — 자르지 않는다
읽어야 하는 글은 자르지 않고 줄을 바꿔 다 보인다 — HR 사내 규정 카드의 본문, 카드 유의사항의 제목, 부서 이름(사용자 결정 10A). 4줄에서 잘라 "더 보기" 로 펼치거나(10B) 눌러서 상세를 여는 것(10C)은 두지 않는다 — 그러니 Collapsible 로 잘린 글을 펼칠 일이 없다. 규정 카드는 한 줄의 카드 높이를 그 줄에서 가장 긴 카드에 맞춘다.
원장 머리 — 소비 요약과 월 달력은 하나만
폰 가계부 원장 머리(웹 · 앱)의 "소비 요약" 카드와 "월 달력" 은 하나만 펼친다 — 하나를 열면 다른 하나가 닫힌다(사용자 결정 11A, 지금 앱과 같다). 둘 다 열리면 거래 목록이 화면 아래로 밀려 거의 보이지 않는다. 두 단추는 원장 머리의 제 모양 그대로이고(이번에 정하지 않았다) 이 스펙의 이름 · 상태 규칙만 따른다 — 이름은 "소비 요약" · "월 달력" 으로 고정하고 aria-expanded 로 펼침을 알린다. 할 일 원장의 월 달력 단추도 같은 이름이다. 머리가 스크롤에 따라 숨는 것은 Scroll Auto Hide 다.
글
이름은 접힌 내용이 무엇인지 명사구로("내역 분할 6개" · "빠르게 맞추기" · "상세 검색 조건"), 설명은 접혀 있어도 알아야 할 값으로("합계 −123,456,789원" · "2개 필터 적용 중") 쓴다. 돈은 원까지, 빼기는 U+2212 다.
코드
레시피 recipes/shadcn/components/ui/collapsible.tsx 를 쓴다(Radix Collapsible 위에 짜도 된다). 아래 미리보기는 스펙 값으로 그린 모습이다.
Collapsible— 접는 자리.open·defaultOpen·onOpenChange(open)·disabled·surface("default"기본 ·"card"·"warning"), 나머지는div속성."card"는 Collapsible 이 Card 면을 그린다."warning"은 경고 상자의 맨 아래 줄이다 — 상자(box— 바탕 · 테두리 · 모서리 12 · 경고 글 여백)는 화면이 그리고, 상자의overflow: hidden이 아래 줄의 누름 바탕을 모서리에서 자른다.CollapsibleTrigger—title(필수 — 고정 이름) ·description·headingLevel(2~6, 기본3). 머리말 안의 단추(aria-expanded·aria-controls)와 꺾쇠 20 을 그린다 — Accordion 트리거와 같은 모양이다.CollapsibleContent— 펼친 내용.layout("text"기본 ·"rows"— 줄 목록이면 아래 8), 나머지는div속성. 좌우는 트리거와 같고 아래 16(card24). 접혀 있으면 그리지 않는다. 높이 300ms · 모션 줄이기면 150ms 서서히.
원장 머리의 "소비 요약" · "월 달력" 은 이 부품이 아니라 원장 머리의 단추다 — aria-expanded · aria-controls 를 달고, 하나를 열면 다른 하나를 닫는 것은 화면이 쥔다.
거래 상세 — 분할 내역
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
<Collapsible defaultOpen>
<CollapsibleTrigger title={`내역 분할 ${splits.length}개`} description={`합계 ${formatWon(total)}`} />
<CollapsibleContent layout="rows">{splits.map(renderSplitRow)}</CollapsibleContent>
</Collapsible>분할 — 빠르게 맞추기(경고 상자 안)
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
{/* 경고 상자(box)는 화면이 그린다 — 경고 글 아래 맨 끝 줄이 Collapsible(좌우 16 · 위 선 · 누름은 경고 바탕의 누름 색) */}
<div className="overflow-hidden rounded-r3 border border-stroke-warning-weak bg-bg-warning-weak">
<div className="px-x4 pb-x3 pt-x3_5">{shortfallMessage}</div>
<Collapsible surface="warning">
<CollapsibleTrigger title="빠르게 맞추기" />
<CollapsibleContent>{quickFixes}</CollapsibleContent>
</Collapsible>
</div>HR 업무 보고 — 상세 검색 조건(카드)
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
<Collapsible surface="card" open={filtersOpen} onOpenChange={setFiltersOpen}>
<CollapsibleTrigger
title="상세 검색 조건"
description={appliedCount > 0 ? `${appliedCount}개 필터 적용 중` : undefined}
/>
<CollapsibleContent>{filterFields}</CollapsibleContent>
</Collapsible>Behavior
| 인터랙션 | 동작 |
|---|---|
트리거 누르기 · Enter · Space | 펼치고 접는다 — 초점은 트리거에 그대로, 저절로 스크롤하지 않는다 |
Tab | 트리거 → 펼친 내용 안의 초점 가는 것 → 다음 |
| Disabled | 누르기 · 키보드 불가. 펼친 채 막히면 내용은 그대로 보인다 |
| 모션 줄이기 | 높이 움직임 없이 150ms 서서히 · 꺾쇠는 바로 · 누름 축소 없음 |
Accessibility
| 기준 | 검증 |
|---|---|
| WCAG 1.4.3 Color contrast(글자 ≥ 4.5:1) | 이름 fg-neutral 16.41 · 다크 13.42(경고 상자 누름 바탕 위 13.04 · 9.59), 설명 fg-neutral-subtle 5.50 · 6.09(경고 상자 4.91 · 5.30) ✓ |
| WCAG 1.4.11 Non-text contrast(≥ 3:1) | 꺾쇠 fg-neutral-subtle 5.50 · 6.09 · 경고 상자 누름 바탕 위 4.37 · 4.35 ✓. 키보드 포커스 링 Desk 8.38 · 6.10 · HR 8.17 · 6.07 ✓ |
| WCAG 1.4.1 Use of color | 펼침은 꺾쇠 방향과 보이는 내용으로 알린다 ✓ |
| WCAG 2.1.1 Keyboard | Tab · Enter · Space ✓ |
| WCAG 2.4.3 Focus order | 펼쳐도 초점은 트리거에 — 펼친 내용은 트리거 바로 뒤 차례 ✓ |
| WCAG 2.4.7 Focus visible | 트리거 안쪽 링 2px — 카드 · 상자에 잘리지 않는다 ✓ |
| WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24) | 트리거 줄 전체 54 이상 ✓ — 지금의 18 · 16.5 글 단추를 걷는다 |
| WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44) | 트리거 54 이상 ✓ |
| WCAG 4.1.2 Name, Role, Value | heading > button — 이름은 고정 이름 + 설명, aria-expanded · aria-controls(펼친 내용의 id) ✓ |
| ARIA | APG Disclosure — 펼친 내용에 role="region" 을 두지 않는다. 앱은 Semantics(button: true, expanded: …) 로, 접힌 내용은 의미 트리에서 뺀다 |
| Reduced motion | 높이 움직임 없이 150ms 서서히(v104) · 꺾쇠는 바로 · 누름 축소 없음 |
Do / Don't
✅ Do
- 상세 하나를 Accordion 항목 모양의 줄 전체 트리거로 접고 편다.
- 이름은 무엇을 여닫는지 고정하고, 접혀 있어도 알아야 할 값은 설명으로.
- 펼쳐도 초점 · 스크롤을 그대로 둔다.
- 원장 머리의 소비 요약과 월 달력은 하나만 펼친다.
❌ Don't
- 글 단추 · 꺾쇠만으로 트리거 만들기(누르는 영역 18 · 16.5).
- 상태마다 이름 바꾸기("펼치기 / 접기" · "목록 / 달력") · 숫자를 이름에 붙이기("필터2").
- 잘린 글을 "더 보기" 로 펼치기 — 읽어야 하는 글은 자르지 않는다.
- 같은 모양의 Collapsible 여럿을 쌓기 — Accordion 을 쓴다.
- 패널 안 스크롤 상자 · 움직임 없이 갑자기 펼치기 · 불투명도 막힘.
Specification
collapsible.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 Collapsible 을 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다.
Base
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
heading | enabled | 머리말 단계level | h3놓인 자리의 머리말 차례에 맞춘다(Accordion 과 같다) — 대화상자 · 시트 · 카드 제목(h2) 아래면 h3 |
trigger | enabled | 최소 높이minHeight | 54px위아래 16 + 이름 22 — Accordion 항목과 같다. 이름이 줄을 바꾸면 늘어난다 |
| 상하 여백paddingY | spacing-x416px | ||
| 좌우 여백paddingX | spacing-global-gutter24px화면 가장자리 규칙 24 | ||
| 커서cursor | pointer | ||
| 터치 영역touchTarget | 트리거 전체줄 폭 × 54 이상 — 글 단추 · 꺾쇠만 누르게 하지 않는다(사용자 결정 9A). AA ✓ · AAA ✓ | ||
| 글 맞춤textAlign | left | ||
background | enabled | 바탕 들임insetX | 0px |
| 모서리radius | 0px | ||
| 배경background | transparent | ||
| 전환 대상transitionProperty | background-color, inset, border-radiusAccordion 과 같다 | ||
| 전환 시간transitionDuration | motion-duration-color-transition150ms | ||
| 전환 곡선transitionEasing | motion-ease-easingcubic-bezier(0.35, 0, 0.35, 1) | ||
content | enabled | 간격gap | spacing-x312px본문 ↔ 꺾쇠 12 |
| 세로 맞춤alignItems | center | ||
| 전환 대상transitionProperty | scale | ||
| 전환 시간transitionDuration | motion-duration-pressed-scale150ms | ||
| 전환 곡선transitionEasing | motion-ease-pressed-scalecubic-bezier(0, 0, 0.15, 1) | ||
body | enabled | 간격gap | spacing-x0_52px이름 ↔ 설명 2 |
title | enabled | 글자·아이콘foreground | fg-neutral#1A1F2E다크 #F5F6FA |
| 글자typography | text-t516px / 22px | ||
| 굵기fontWeight | 500 | ||
| 줄 수lineClamp | none늘 줄을 바꾼다(10A) | ||
description | enabled | 글자·아이콘foreground | fg-neutral-subtle#62697A다크 #A2A8B7 |
| 글자typography | text-t313px / 18px | ||
| 굵기fontWeight | 500SEED accordion-item description medium — 머리 제목 500 과 한 벌. 글자 역할 R9 의 "여닫는 머리 설명"(2026-10-17 사용자 결정 01T-005 — 줄 설명 400 에 맞추지 않는다) | ||
| 줄 수lineClamp | none | ||
chevron | enabled | 크기size | 20pxlucide chevron-down — 접혀 있으면 아래 |
| 색color | fg-neutral-subtle#62697A다크 #A2A8B7 | ||
| 회전rotate | 0deg | ||
| 전환 대상transitionProperty | rotate | ||
| 전환 시간transitionDuration | motion-duration-d6300ms | ||
| 전환 곡선transitionEasing | motion-ease-easingcubic-bezier(0.35, 0, 0.35, 1) | ||
panel | enabled | 최대 높이maxHeight | none패널 안에서 스크롤하지 않는다 — 길면 놓인 화면 · 시트가 스크롤한다 |
| 좌우 여백paddingX | spacing-global-gutter24px트리거와 같은 좌우 — 내용(줄 · 칸 · 글)은 이 안에서 시작한다 | ||
| 위 여백paddingTop | 0px | ||
| 아래 여백paddingBottom | spacing-x416px트리거 위아래와 같은 16 | ||
| 보이기display | 숨김접혀 있으면 내용을 그리지 않는다 — 보조 기술도 읽지 않는다(앱도) | ||
background | hovered | 바탕 들임insetX | spacing-x1_56px |
| 모서리radius | radius-r2_510px | ||
| 배경background | bg-layer-default-pressed#F7F8FD다크 #353B4DAccordion 과 같다 — 시트 · 대화상자 위에서도 같은 값 | ||
focusRing | focused | 링 두께width | 2px |
| 모서리radius | 0px흰 면의 줄 — 각진 링 | ||
| 링 색color | stroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84 | ||
| 링 간격offset | -2px트리거 안쪽 — 카드 · 상자 · 시트가 바깥 링을 자르지 않게 | ||
background | pressed | 바탕 들임insetX | spacing-x1_56px |
| 모서리radius | radius-r2_510px | ||
| 배경background | bg-layer-default-pressed#F7F8FD다크 #353B4D | ||
content | pressed | 배율scale | 2px 거리 축소콘텐츠 층만 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(v104) |
trigger | disabled | 커서cursor | not-allowed |
title | disabled | 글자·아이콘foreground | fg-disabled#8A91A0다크 #838997 |
description | disabled | 글자·아이콘foreground | fg-disabled#8A91A0다크 #838997 |
chevron | disabled | 색color | fg-disabled#8A91A0다크 #838997 |
open=expanded
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
chevron | enabled | 회전rotate | 180deg위로 |
panel | enabled | 보이기display | 보임초점은 트리거에 그대로 · 저절로 스크롤하지 않는다(따라오는 것) |
description=shown
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
trigger | enabled | 최소 높이minHeight | 74px위아래 16 + 이름 22 + 2 + 설명 18 |
layout=rows
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
panel | enabled | 아래 여백paddingBottom | spacing-x28px8 — 줄마다 위아래 12 를 가져 마지막 줄의 글 아래로 20(비교 페이지 9A 의 분할 내역). 줄의 좌우는 0 — 패널의 좌우가 들인다 |
surface=card
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 모서리radius | radius-r416pxCard 16 — 트리거가 카드 머리다. 안의 누름 바탕은 16 − 6 = 10 으로 동심 |
| 배경background | bg-layer-default#FFFFFF다크 #242938Card 면 — 바닥(bg-layer-basement) 위 | ||
| 테두리borderColor | stroke-neutral-weak#E5E8EF다크 #404757 | ||
| 테두리 두께borderWidth | 1px | ||
| 넘침overflow | hidden | ||
panel | enabled | 아래 여백paddingBottom | spacing-x624px카드 안 여백 24(Card) — 칸 묶음 아래 |
focusRing | focused | 모서리radius | radius-r3_514px카드 모서리 16 − 링 들임 2 = 14 — 트리거가 카드 맨 위라 모서리를 따른다 |
surface=warning
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
trigger | enabled | 좌우 여백paddingX | spacing-x416px경고 상자의 안쪽 여백 16 — 상자 글과 같은 자리에 선다 |
panel | enabled | 좌우 여백paddingX | spacing-x416px |
divider | enabled | 높이height | 1px |
| 배경background | stroke-warning-weak#F5C7B6다크 #833615트리거 위 — 상자 안 경고 글과 트리거 줄을 가른다 | ||
| 보이기display | 트리거 위상자 폭 끝까지 | ||
box | enabled | 좌우 여백paddingX | spacing-x416px경고 글 좌우 16 — 트리거 좌우와 같다 |
| 위 여백paddingTop | spacing-x3_514px경고 글 위 14 | ||
| 아래 여백paddingBottom | spacing-x312px경고 글 ↔ 아래 줄의 선 12 | ||
| 모서리radius | radius-r312px12 — 지금 분할 경고 상자의 모서리 | ||
| 배경background | bg-warning-weak#FFEFE8다크 #4B2B1F놓인 면 — 경고 글(제목 · 설명)과 아래 줄의 트리거를 함께 담는다 | ||
| 테두리borderColor | stroke-warning-weak#F5C7B6다크 #833615 | ||
| 테두리 두께borderWidth | 1px | ||
| 넘침overflow | hidden아래 줄의 누름 바탕 · 펼친 내용을 모서리에서 자른다 | ||
background | hovered | 배경background | bg-warning-weak-pressed#FAE0D5다크 #65321D놓인 면(bg-warning-weak)의 누름 짝 — 꺾쇠 4.37 · 4.35 · 이름 13.04 · 9.59 |
focusRing | focused | 모서리radius | radius-r2_510px상자 모서리 12 − 링 들임 2 = 10 — 트리거가 상자 맨 아래라 모서리를 따른다 |
background | pressed | 배경background | bg-warning-weak-pressed#FAE0D5다크 #65321D |
SEED 와 다른 점
- SEED 에는 Collapsible 이 없다 — Accordion 가이드의 한 줄대로 항목 하나의 모양을 쓴다. 값은 Accordion 의 groo 값(좌우 24 · 불투명한 누름 바탕 · 안쪽 포커스 링)이다.
- 놓인 면(
surface) — 카드 면 · 경고 상자 안에 둘 때의 여백 · 선 · 누름 색은 groo 가 정했다. - 잘린 글을 펼치지 않는다 · 원장 머리는 하나만 펼친다 — SEED 에 규칙이 없다.
Migration notes
2026-10-10 — 트리거를 Accordion 항목 하나로
사용자가 펼치고 접기 · 나누기 비교 페이지에서 정했다 — 하나만 접는 자리의 트리거는 Accordion 항목 하나의 모양(9A — 줄 전체 · 머리 글 = 이름 · 54 / 74), 잘린 글은 자르지 않는다(10A — HR 규정 카드 · 유의사항 제목 · 부서 이름), 폰 가계부 요약 ↔ 달력은 하나만(11A). 펼침은 Accordion 과 같은 300ms(3A). 그리고 "따라오는 것" — 이름은 고정 + aria-expanded(APG Disclosure) · 펼친 뒤 초점은 트리거에 그대로. 글 단추 + 꺾쇠(9B) · 지금 그대로(9C) · "더 보기"(10B) · 눌러서 상세(10C) · 요약과 달력 둘 다(11B)는 고르지 않았다. 옛 스펙은 collapsible.history/v-pre-seed-collapse.md 에 남겼다.
| 옛 Collapsible | 새 Collapsible |
|---|---|
| 스타일 0 — 트리거 · 내용의 모양은 사용처 | 트리거는 Accordion 항목 모양(54 · 74 · 꺾쇠 20) · 놓인 면 셋 |
트리거는 asChild 로 Button 을 감싼다 | 줄 전체가 트리거 — 머리말 안의 단추 |
| "더 보기" · 고급 옵션 · 설정 상세 쓰임 | 잘린 글은 펼치지 않고 자르지 않는다 — 상세 하나를 접는 자리만 |
꺾쇠 회전은 사용처에서 · animate-accordion-* 재사용 | 꺾쇠 180° · 높이 300ms ease-easing · 모션 줄이기면 150ms 서서히 |
| 막힘 불투명도 0.5 | 전용 색 fg-disabled |
제품은 앱 적용 단계에서 옮긴다(2026-10-09 조사).
- 웹 거래 상세 분할 내역 — 머리 "내역 분할 6개"(h4) 오른쪽 글 단추 "합계 123,456,789원 + 꺾쇠 16" 이 128.9 × 18 이고 이름에 무엇을 여닫는지 · 빼기 부호가 없으며,
aria-expanded0 · 움직임 0 이다(widgets/expense-detail/ui/TxDetailDialog.tsx:646-700· 상태:113, D4 · D13). 앱은 같은 자리가 높이 18 · 이름 · 상태 0 이다(features/expense/presentation/tx_detail_dialog.dart:828-880).default트리거 "내역 분할 6개 · 합계 −123,456,789원" 으로. - 웹 분할 빠르게 맞추기 — "✦ 빠르게 맞추기 ⌄" 가 103 × 16.5 · 11 / 700 브랜드 ·
aria-controls0 · 움직임 0 이다(features/expense-split/ui/SplitTxDialog.tsx:590-640, D13). 앱은 높이 22 · 이름 · 상태 0(features/expense_split/presentation/split_tx_dialog.dart:513-545).warning트리거로. - HR 업무 보고 필터 — outline 단추 152 × 36 "필터 2" 가 "필터2" 로 붙어 읽히고
aria-expanded0 · 내용 움직임 0 이다(features/work-report/ui/ReportHeader.tsx:43-63→ReportFilter.tsx:58-231, D19). 조건 카드의 머리card트리거 "상세 검색 조건 · 2개 필터 적용 중" 으로. - 원장 머리 — 웹 가계부는 소비 요약 카드(
txmDrop180ms)와 월 달력이 함께 열리고, 앱은 하나를 열면 다른 하나를 닫는다(pages/expense/ui/ExpensePage.tsx:1972-2020→shared/ui/groo/ledger.tsx:202-250· 앱features/expense/presentation/expense_screen.dart:505-507 · 617-619, D24) — 웹도 하나만. 월 달력 단추의 이름이 가계부는 "목록 / 달력" 으로 잘못 바뀌고 할 일은 "월 전체 보기 / 접기" 이며aria-expanded0, 앱은 이름 0 이다(ExpensePage.tsx:2112-2116·TodoMobileLedger.tsx:472-476→ledger.tsx:394-416· 앱expense_screen.dart:1253-1266, D10) — "월 달력" +aria-expanded로. 누르는 영역도 소비 요약 39.5 · 월 달력 32 다(D13). - 잘린 글 — HR 규정 카드 16곳이
line-clamp-4로 데스크톱 4열에서 한국어 3/16 · 영어 6/16 이 잘리고 펼칠 길이 없으며, "지급 시기" 는 같은 문장이 두 번 찍힌다(features/culture-regulation/ui/RuleVacation.tsx:17 · 27-37외Rule*.tsx, D6). 자르지 않고 줄마다 가장 긴 카드에 높이를 맞춘다. 웹 유의사항 제목의 말줄임(D11)은 Accordion, HR 부서 이름의 말줄임(D12)은 Tree View 에서 걷는다. - 쓰지 않는 것 — SSO
collapsible.tsx는 쓰는 곳이 0 이다(D21).
이전 기록
- 2026-05 — Radix primitive 그대로. 스타일 없이 동작만 내보내고 시각은 사용처에 맡겼다. 이번에 트리거 모양을 정했다.
Chip
고르거나 넣은 값을 보이는 작은 알약. 2 ~ 4개 짧은 폼 값 고르기 · 누르면 값을 채워 주는 제안 · 목록 위 필터 바 · 넣은 값(지우기 버튼으로 뺀다)을 맡는다. 5개 이상은 Select, 글이 긴 2 ~ 4개는 Radio · Checkbox, 다른 구역으로 옮기는 탭 · 같은 내용을 바로 거르는 2 ~ 4개는 Tabs · Segmented Control(그 차례에), 누를 수 없는 표시는 Badge · Tag Group 이다.
Color Swatch
카테고리 · 태그 · 라벨 · 저축 목표 · 캘린더에 붙일 색을 고르는 묶음 — 차트 10색을 원 칸에 진하게 칠해 5 × 2 로 늘어놓고 하나를 고른다. 고른 색은 그 항목의 차트 · 타일 · 점에 그대로 칠해진다. 이름이 붙은 선택지를 고르는 것은 Chip · Select 다.