Groo Design

Select

짧은 선택지 5개 이상에서 폼에 넣을 값을 고르는 칸. 결제 수단 · 통화 · 휴가 정책 · 반복 단위처럼 한 줄 글(+ 한 줄 설명)로 충분한 선택지를 칸 아래 목록으로 연다. 달력 · 시각 · 아이콘 격자 · 검색이 필요한 긴 목록은 Input Button, 설명 · 그림이 붙는 2 ~ 6개는 Select Box다.

구조는 당근 SEED Select(Apache-2.0)를 따른다 — 트리거(Trigger) · 목록(Content) · 선택지(Item) · 묶음(Group) · 묶음 사이 선(Divider). 라벨 · 설명 · 오류는 Field가 둘레에서 그린다. 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다(2026-10-01 사용자 결정).

수치 원본은 select.yaml이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.

9:41
거래 추가
12,000원
식비 · 점심
현대카드 M
10월 1일 (목)
시간
오후 3:00
hr.groo.app
휴가 신청
연차
연차남은 연차 11일
반차(오전)
반차(오후)
경조 휴가
병가
공가
10월 12일 (월)
9:41
거래 추가
12,000원
식비 · 점심
현대카드 M
10월 1일 (목)
시간
오후 3:00
hr.groo.app
휴가 신청
연차
연차남은 연차 11일
반차(오전)
반차(오후)
경조 휴가
병가
공가
10월 12일 (월)
거래 추가 · 휴가 신청 — 라이트 · 다크

직접 골라 보기

크기 · 상태 · 여럿 고르기 · 앞 아이콘 · 묶음 · 설명을 고르면 스펙대로 그린 칸과 그 코드가 바뀐다. 실제로 열고 고를 수 있다.

크기 size
상태
여럿 고르기 multiple
앞 아이콘 prefixIcon
묶음 SelectGroup
설명 description
모드
브랜드
import { Field } from "@/components/ui/field"
import { Select, SelectGroup, SelectItem } from "@/components/ui/select"

<Field label="결제 수단">
  <Select placeholder="결제 수단 선택" value={asset} onValueChange={setAsset}>
    <SelectGroup>
      <SelectItem value="none" label="결제 수단 없음" />
    </SelectGroup>
    <SelectGroup label="카드">
      <SelectItem value="kb-check" label="국민 체크카드" />
      <SelectItem value="hyundai-m" label="현대카드 M" />
      <SelectItem value="shinhan" label="신한카드 Deep" />
    </SelectGroup>
    <SelectGroup label="계좌 · 현금">
      <SelectItem value="toss" label="토스뱅크 통장" />
      <SelectItem value="kakao" label="카카오뱅크 통장" />
      <SelectItem value="cash" label="현금" />
    </SelectGroup>
  </Select>
</Field>

Anatomy

ⓑⓒⓓⓐⓔ
현대카드 M
결제 수단 없음
카드
국민 체크카드
현대카드 M
계좌 · 현금
토스뱅크 통장1000-1234-5678
현금
ⓕⓖⓗ
ⓐ Triggerⓑ Prefix Iconⓒ Valueⓓ Chevronⓔ Contentⓕ Group Labelⓖ Itemⓗ Divider
트리거와 목록 — 목록 안에 선택지 · 묶음 · 묶음 사이 선
부위설명
ⓐ Trigger트리거 — 고른 값이나 placeholder 를 보이고, 누르면 목록을 연다. 상자는 Input Button · Text Input 과 같다.
ⓑ Prefix Icon트리거 앞 아이콘 — 없어도 된다. 하나를 고르면 그 선택지의 아이콘으로 바뀐다.
ⓒ Value · Placeholder고른 값 — 한 줄. 고르기 전에는 고를 값의 종류("결제 수단 선택").
ⓓ Chevron셰브론 — 목록이 열려 있는지 알린다. 열리면 180° 돈다.
ⓔ Content목록 — 트리거 폭 그대로 트리거 아래에 붙는다(모자라면 위).
ⓕ Group Label묶음 제목 — 없어도 된다.
ⓖ Item선택지 — 앞 아이콘 · 글 · 설명(한 줄). 고른 선택지는 오른쪽에 체크(Indicator).
ⓗ Divider묶음 사이 선 — 묶음이 둘 이상이면 저절로 그린다. 선택지 사이에는 없다.

부위

부위설명
trigger트리거 — Input Button 과 같은 상자. 누르면 목록이 열린다
value고른 값 — 한 줄. 여럿이면 쉼표로 잇고, 넘치면 "첫 값 외 N개"
placeholder고르기 전의 글 — "{값의 종류} 선택"("결제 수단 선택"). 라벨만 그대로 두지 않는다
prefixIcon트리거 앞 아이콘 — 하나를 고르면 그 선택지의 아이콘으로 바뀐다
chevron셰브론 — 열리면 180° 돈다
focusRing트리거의 키보드 포커스 링(웹)
content목록 — 칸 폭 그대로, 칸 아래(모자라면 위) 8
item선택지 한 줄 — 글은 목록 안에서 줄바꿈된다
itemIcon선택지 앞 아이콘
itemLabel선택지 글 — 명사형으로 짧게
itemDescription선택지 설명 — 한 줄. 트리거에는 보이지 않는다
indicator고른 표시 — 오른쪽 체크. 바탕 · 굵기는 바꾸지 않는다
highlight누름 · 호버 · 키보드 위치 — 좌우 8 들인 알약
groupLabel묶음 제목
divider묶음 사이 선 — 선택지 사이에는 없다

Properties

Size

large(트리거 52 · 선택지 46)는 폰 · 앱에서, medium(트리거 40 · 선택지 39)은 1280 이상 데스크톱 웹(마우스)에서만 쓴다. 웹의 기본은 responsive 다 — 1280 미만은 large, 이상은 medium(SEED lg). 앱은 늘 large 다. 예외 하나 — Table Pagination 의 두 고르기는 줄 한 높이를 지키려고 폭과 상관없이 medium 40 이다(사용자 결정 2026-10-08). 트리거와 목록은 같은 크기를 쓰고, 한 폼 안에서 크기를 섞지 않는다.

현대카드 M
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
large — 트리거 52 · 선택지 46폰 · 앱 — 글자 16 · 모서리 12 · 셰브론 20 · 체크 14
현대카드 M
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
medium — 트리거 40 · 선택지 391280 이상 데스크톱 웹 — 글자 14 · 모서리 8 · 셰브론 16 · 체크 12
크기 — large · medium 의 트리거와 목록

크기 — 트리거

sizetrigger 최소 높이trigger 좌우 여백trigger 간격trigger 모서리trigger 바뀌는 폭값 글자placeholder 글자앞 아이콘chevron 크기설명
large52pxspacing-x4 16pxspacing-x2_5 10pxradius-r3 12px—text-t5 16px / 22pxtext-t5 16px / 22px20px20px트리거 52 · 선택지 46 — 폰 · 앱
medium40pxspacing-x3_5 14pxspacing-x2 8pxradius-r2 8px—text-t4 14px / 19pxtext-t4 14px / 19px16px16px트리거 40 · 선택지 39 — 1280 이상 데스크톱 웹(마우스)에서만
responsive (기본)————1280px — 미만은 large, 이상은 medium 의 값을 쓴다(SEED lg) — 앱은 늘 large————폭에 따라 — 1280 미만 large · 이상 medium. 웹의 기본

크기 — 선택지

sizeitem 높이item 상하 여백item 간격itemIcon 크기itemLabel 글자itemDescription 글자indicator 크기설명
large46px — 한 줄 — 설명이 있으면 66spacing-x3 12pxspacing-x3 12px22pxtext-t5 16px / 22pxtext-t3 13px / 400 / 18px14px트리거 52 · 선택지 46 — 폰 · 앱
medium39px — 한 줄 — 설명이 있으면 57spacing-x2_5 10pxspacing-x2 8px18pxtext-t4 14px / 19pxtext-t2 12px / 400 / 16px12px트리거 40 · 선택지 39 — 1280 이상 데스크톱 웹(마우스)에서만
responsive (기본)———————폭에 따라 — 1280 미만 large · 이상 medium. 웹의 기본

크기 — 묶음 제목

sizegroupLabel 상하 여백groupLabel 글자groupLabel 굵기설명
largespacing-x2_5 10pxtext-t4 14px / 400 / 19px500트리거 52 · 선택지 46 — 폰 · 앱
mediumspacing-x2 8pxtext-t3 13px / 400 / 18px400트리거 40 · 선택지 39 — 1280 이상 데스크톱 웹(마우스)에서만
responsive (기본)———폭에 따라 — 1280 미만 large · 이상 medium. 웹의 기본

State

트리거의 상태는 Input Button 과 같고, 목록이 열려 있는 동안 셰브론이 180° 돈다.

트리거모습
enabled투명 바탕 · 안쪽 1px stroke-neutral-weak
pressed바탕 bg-layer-default-pressed + 값 · 아이콘만 2px 거리로 준다(테두리 · 바탕은 그대로). 마우스는 호버에 같은 바탕
focused키보드 포커스에만 바깥 링 2px · 띄움 2px stroke-focus-ring — 트리거는 버튼이라 마우스 · 터치로 눌러서는 링이 없다
open셰브론 180°(열 때 150ms · 닫을 때 100ms)
invalid안쪽 2px stroke-critical-solid — 눌러도 그대로. 오류 글은 Field 가 칸 아래에
disabled바탕 bg-disabled · 글자 · 아이콘 fg-disabled. 흐리게 하지 않는다(v106)
readonly바탕 bg-disabled · 값은 진한 글자 그대로. 포커스는 되고 열리지 않는다
결제 수단 선택
기본
현대카드 M
누름
현대카드 M
포커스(키보드)
현대카드 M
열림
결제 수단 선택
오류
현대카드 M
비활성
현대카드 M
읽기 전용
라이트
결제 수단 선택
기본
현대카드 M
누름
현대카드 M
포커스(키보드)
현대카드 M
열림
결제 수단 선택
오류
현대카드 M
비활성
현대카드 M
읽기 전용
다크
트리거 상태 — 기본 · 누름 · 포커스 · 열림 · 오류 · 비활성 · 읽기 전용

트리거 상태

상태trigger 배경trigger 테두리trigger 테두리 두께trigger 배율trigger 커서trigger 축소 시간trigger 축소 곡선
enabled 기본transparentcolor-stroke-neutral-weak #E5E8EF · 다크 #4047571px — 상자 안쪽(inset). 오류의 2px 는 안쪽에 덧그린다—pointer——
pressed 누름 — 트리거는 바탕 + 콘텐츠 축소, 선택지는 알약 + 콘텐츠 축소. 마우스 호버 · 선택지의 키보드 위치는 같은 바탕 · 알약(축소 없음)color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D — SEED bg.transparent-pressed 의 불투명 짝. 마우스는 호버에 같은 바탕color-stroke-neutral-weak #E5E8EF · 다크 #4047571px — 상자 안쪽(inset). 오류의 2px 는 안쪽에 덧그린다콘텐츠 2px 거리 축소 — 값 · 아이콘만 준다(SEED scaleScope: content) — 테두리 · 바탕은 그대로pointermotion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)
invalid 오류 — 트리거 안쪽 2px. Field 의 invalidtransparentcolor-stroke-critical-solid #D72323 · 다크 #FF8477 — 1px 위에 2px 가 나타난다(100ms)2px—pointer——
disabled 비활성 — 트리거 · 선택지color-bg-disabled #F5F6FA · 다크 #353B4D — 흐리게 하지 않는다(v106)color-stroke-neutral-weak #E5E8EF · 다크 #4047571px — 상자 안쪽(inset). 오류의 2px 는 안쪽에 덧그린다—not-allowed——
readonly 읽기 전용 — 트리거, 열리지 않는다color-bg-disabled #F5F6FA · 다크 #353B4D — 값은 진한 글자 그대로 — 열리지 않는다color-stroke-neutral-weak #E5E8EF · 다크 #4047571px — 상자 안쪽(inset). 오류의 2px 는 안쪽에 덧그린다—default——

트리거 글자 · 아이콘

상태값 글자색placeholder 색앞 아이콘 색chevron 색chevron 회전
enabled 기본color-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-placeholder #767C8B · 다크 #838997color-fg-neutral-muted #535866 · 다크 #B7BDCCcolor-fg-neutral-muted #535866 · 다크 #B7BDCC—
open 목록이 열림 — 셰브론 180°color-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-placeholder #767C8B · 다크 #838997color-fg-neutral-muted #535866 · 다크 #B7BDCCcolor-fg-neutral-muted #535866 · 다크 #B7BDCC180deg
disabled 비활성 — 트리거 · 선택지color-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997—

포커스 링

상태링 두께링 간격링 색
enabled 기본———
focused 웹 — 트리거에 키보드 포커스 링2px2pxcolor-stroke-focus-ring #1A1F2E · 다크 #F5F6FA — 트리거 — 키보드 포커스에만(마우스 · 터치로 눌러서는 없다)
선택지모습
enabled바탕 없음
pressed좌우 8 들어온 알약(모서리 12) bg-layer-floating-pressed + 콘텐츠 2px 거리 축소. 마우스 호버 · 키보드로 옮긴 선택지는 같은 알약(축소 없음)
selected오른쪽 체크(large 14 · medium 12). 바탕 · 글 굵기는 바꾸지 않는다
disabled글 · 설명 · 아이콘 · 체크 모두 fg-disabled. 알약이 생기지 않는다
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
기본누름고름키보드 위치비활성
라이트 — 키보드 위치 · 호버는 누름과 같은 알약(축소 없음)
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
기본누름고름키보드 위치비활성
다크 — 키보드 위치 · 호버는 누름과 같은 알약(축소 없음)
선택지 상태 — 기본 · 누름 · 키보드 위치 · 고름 · 비활성

선택지 상태

상태item 배율item 커서
enabled 기본—pointer
pressed 누름 — 트리거는 바탕 + 콘텐츠 축소, 선택지는 알약 + 콘텐츠 축소. 마우스 호버 · 선택지의 키보드 위치는 같은 바탕 · 알약(축소 없음)콘텐츠 2px 거리 축소 — 누르는 동안만(호버 · 키보드 위치는 축소 없음)pointer
disabled 비활성 — 트리거 · 선택지—not-allowed

알약 — 모양

속성값비고
배경transparent누를 때 · 키보드로 옮길 때만 칠한다
모서리radius-r3 12px

알약

상태highlight 바탕 들임highlight 배경
enabled 기본—transparent — 누를 때 · 키보드로 옮길 때만 칠한다
pressed 누름 — 트리거는 바탕 + 콘텐츠 축소, 선택지는 알약 + 콘텐츠 축소. 마우스 호버 · 선택지의 키보드 위치는 같은 바탕 · 알약(축소 없음)spacing-x2 8px — 좌우 8 들어온 알약 — 글은 알약 안쪽 8 지점에서 시작한다color-bg-layer-floating-pressed #F7F8FD · 다크 #353B4D — 목록 바탕(bg-layer-floating) 위의 누름. 마우스 호버 · 키보드 위치도 같다

선택지 글자 · 아이콘 · 고른 표시

상태itemIcon 색itemLabel 글자·아이콘itemDescription 글자·아이콘indicator 색indicator 불투명도
enabled 기본color-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral-subtle #62697A · 다크 #A2A8B7color-fg-neutral #1A1F2E · 다크 #F5F6FA — lucide check · 선 2.5 — 앱은 2.5 굵기 글꼴 LucideIcons.check500(Q-008-2)—
selected 고른 선택지 — 오른쪽 체크color-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral #1A1F2E · 다크 #F5F6FAcolor-fg-neutral-subtle #62697A · 다크 #A2A8B7color-fg-neutral #1A1F2E · 다크 #F5F6FA — lucide check · 선 2.5 — 앱은 2.5 굵기 글꼴 LucideIcons.check500(Q-008-2)1 — 고르지 않은 선택지에는 체크를 그리지 않는다(자리도 없다)
disabled 비활성 — 트리거 · 선택지color-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997color-fg-disabled #8A91A0 · 다크 #838997—

모션

전환시간곡선대상비고
누름 · 호버 — 바탕motion-duration-color-transition 150msmotion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)background-color
누름 — 콘텐츠motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)scale트리거 · 선택지의 콘텐츠만 2px 거리 축소 — 모션 줄이기면 하지 않는다
셰브론 — 열 때motion-duration-d3 150msmotion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)rotate
셰브론 — 닫을 때motion-duration-d2 100msmotion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)rotate
목록 — 열 때motion-duration-d3 150msmotion-ease-enter cubic-bezier(0, 0, 0.15, 1)opacity, scale0.95 → 1 · 투명 → 불투명. 붙은 쪽(트리거 쪽)에서 커진다
목록 — 닫을 때motion-duration-d2 100msmotion-ease-exit cubic-bezier(0.35, 0, 1, 1)opacity, scale1 → 0.95 · 사라진다. 모션 줄이기면 투명도만
선택지 — 알약motion-duration-color-transition 150msmotion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)background-color, inset알약 바탕이 나타나며 좌우 8 들어온다
오류 — 테두리motion-duration-d2 100msmotion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)border-color

Selection Mode

하나 고르기가 기본이다. 여럿 고르기(multiple)도 둔다.

  • 하나 고르기 — 고르면 목록이 닫히고 값이 트리거에 들어간다. 고른 선택지를 다시 눌러도 풀리지 않는다 — "없음" 이 답이 될 수 있는 칸은 그 답을 선택지로 둔다(아래 "없음" 을 답으로 받기).
  • 여럿 고르기 — 목록이 열린 채로 남아 이어서 고른다. 고른 선택지를 다시 누르면 풀린다. 트리거에는 고른 순서대로 쉼표로 잇고("식비, 교통"), 칸 폭을 넘으면 "첫 값 외 N개" 로 줄인다.
현대카드 M
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
하나 고르기고르면 닫히고 값이 칸에 · 다시 눌러도 풀리지 않는다
식비, 교통
식비
교통
쇼핑
문화
의료
여럿 고르기 multiple열린 채로 이어서 고른다 · 다시 누르면 풀린다 · 고른 순서대로 쉼표
하나 고르기 · 여럿 고르기

Prefix Icon

트리거와 선택지 앞에 아이콘을 둘 수 있다. 트리거의 아이콘은 어떤 종류의 값을 고르는 자리인지 함께 알리고 싶을 때 쓴다. 트리거에 실제로 그리는 아이콘은 고른 개수로 정한다(SEED).

  • 하나를 골랐다 — 그 선택지의 아이콘을 그린다(트리거에 준 아이콘을 덮는다). 선택지에 아이콘이 없으면 트리거의 아이콘.
  • 둘 이상 골랐다(여럿 고르기) — 트리거의 아이콘.
  • 그릴 아이콘이 없으면 아이콘 없이 그린다.

선택지 아이콘은 묶음 안에서 모두 두거나 모두 빼고, 아이콘만으로 뜻을 알리지 않는다.

현대카드 M
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
선택지 아이콘묶음 안에서 모두 두거나 모두 뺀다 — 고르면 그 아이콘이 트리거로
결제 수단 선택
고르기 전트리거에 준 아이콘(wallet)
현대카드 M
하나를 골랐다그 선택지의 아이콘이 덮는다
식비, 교통
둘 이상 골랐다트리거의 아이콘(tag)
앞 아이콘 — 고른 선택지의 아이콘이 트리거로

Content

목록은 트리거 폭 그대로, 트리거 아래 8 에 붙는다. 아래가 모자라면 위로 뒤집고, 화면 가장자리와는 8 을 둔다(안전 영역이 있으면 그 값). 높이는 480 과 목록이 놓이는 쪽에 남은 화면 중 작은 값이고(남은 화면이 200 보다 좁아도 200 은 둔다), 넘치면 목록 안에서 스크롤한다. 놓이는 쪽은 아래에 다 들어가면 아래, 아니면 트리거 아래 · 위 중 넓은 쪽이고, 남은 화면은 트리거에서 그쪽 화면 가장자리 8(안전 영역이 있으면 그 값)까지다. 열면 초점이 목록으로 가 화면 키보드는 내려간다 — 남은 화면에 키보드를 세지 않는다(구현 세션 질문 Q-008-1 — SEED "가용 높이" 와 같은 뜻). 480 은 listMaxHeight 로 줄일 수 있다 — Table Pagination 의 범위 목록만 240(SEED)을 쓴다. 열면 고른 선택지가 보이게 스크롤된 채로 열린다 — 선택지가 많은 Select 는 목록 안에서 스크롤되는 모습을 가정하고 짠다.

선택지 글은 목록 안에서는 줄바꿈되고(자르지 않는다), 트리거에서는 한 줄로 말줄임된다.

9:41
거래 추가
현대카드 M
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
아래에 붙는다칸 아래 8 · 칸 폭 그대로
9:41
거래 추가
12,000원
점심 식사
10월 1일 (목)
현대카드 M
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
모자라면 위로칸 위 8 — 아래 남은 화면이 목록보다 좁을 때
9:41
거래 추가
유로
원KRW
미국 달러USD
일본 엔JPY
유로EUR
중국 위안CNY
영국 파운드GBP
호주 달러AUD
캐나다 달러CAD
스위스 프랑CHF
홍콩 달러HKD
싱가포르 달러SGD
베트남 동VND
길면 안에서 스크롤높이 min(480, 남은 화면) · 하한 200 — 고른 선택지가 보이게 연다
목록 — 아래에 붙는다 · 모자라면 위로 · 길면 안에서 스크롤

목록

속성값비고
너비트리거 폭트리거와 같은 폭 — 선택지 글은 목록 안에서 줄바꿈된다
z-index200z-floating — specs/z-index.md L3. 대화상자 · 시트(101) 안에서 열어도 그 위
배경color-bg-layer-floating #FFFFFF · 다크 #2D3346
모서리radius-r5 20px
그림자shadow-s3
상하 여백spacing-x2 8px
간격spacing-x2 8px묶음 사이(위아래 8 + 선 1px + 8)
위 바깥 여백spacing-x2 8px트리거와의 거리 — 아래가 모자라면 위로 뒤집는다
바깥 여백spacing-x2 8px화면 가장자리와 최소 거리(안전 영역이 있으면 그 값)
최대 높이min(480px, 남은 화면)남은 화면 = 목록이 놓이는 쪽(아래에 다 들어가면 아래, 아니면 아래 · 위 중 넓은 쪽)의 트리거 ~ 화면 가장자리 8. 화면 키보드는 세지 않는다 — 열면 초점이 목록으로 가 키보드가 내려간다(Q-008-1). 남은 화면이 200 보다 좁아도 200 은 둔다 — 넘치면 목록 안에서 스크롤. 열면 고른 선택지가 보이게 스크롤된다

Group

성격이나 정렬 기준이 다른 선택지는 묶음으로 나눈다. 묶음에는 제목을 붙일 수 있고(fg-neutral-subtle), 묶음이 둘 이상이면 사이에 1px 선(좌우 16 들임)을 저절로 그린다 — 묶음 사이는 8 + 1 + 8 = 17 이다. 선택지 사이에는 선을 긋지 않는다.

결제 수단 없음
카드
국민 체크카드
현대카드 M
계좌 · 현금
토스뱅크 통장1000-1234-5678
현금
묶음 사이 8 + 1 + 8 = 17선은 좌우 16 들인 1px — 선택지 사이에는 없다묶음 제목 — 39(없어도 된다)
묶음 — 제목 · 묶음 사이 선

선택지

속성값비고
좌우 여백spacing-x4 16px
커서pointer
전환 대상background-color알약 바탕과 들임(inset)이 함께
전환 시간motion-duration-color-transition 150ms
전환 곡선motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1)

묶음 제목

속성값
글자·아이콘color-fg-neutral-subtle #62697A · 다크 #A2A8B7
좌우 여백spacing-x4 16px

묶음 사이 선

속성값비고
색color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D
높이1px
좌우 바깥 여백spacing-x4 16px좌우 16 들인다
아래 바깥 여백spacing-x2 8px

Guidelines

늘 Field 의 라벨과 함께

Select 는 늘 Field 의 라벨과 함께 쓴다. 라벨이 없으면 고른 뒤에 그 값이 무엇인지 알 수 없다 — placeholder 는 고르면 사라지므로 라벨을 대신하지 못한다.

현대카드 M
원
이렇게 — Field 의 라벨 — 고른 뒤에도 무엇을 고른 칸인지 보인다
현대카드 M
원
이렇게 하지 않는다 — 라벨 없이 값만 — placeholder 는 고르면 사라져 칸의 뜻이 남지 않는다
라벨 — 고른 뒤에도 뜻이 보인다 · 라벨 없이 값만

"없음" 을 답으로 받기

하나 고르기는 고른 선택지를 다시 눌러도 풀리지 않는다. 칸의 성격에 따라 셋으로 나눈다(SEED).

칸어떻게
"없음" 이 답이 될 수 있다"없음" 선택지를 둔다 — 글은 "{칸 이름} 없음" 한 가지 꼴("결제 수단 없음" — "해당 없음" · "선택 안 함" 이 아니다). 다른 선택지와 성격이 달라 맨 앞 따로 묶음에 둔다
하나를 꼭 골라야 한다"없음" 을 두지 않는다. 고르지 않고 제출하면 Field 의 오류로 알린다("결제 수단을 골라주세요.")
고르지 않은 상태가 있을 수 없다"없음" 을 두지 않고 기본값을 미리 골라 둔다(정렬 기준 · 통화)

"없음" 을 고르면 값을 고른 것으로 친다 — 트리거에 그 글을 보이고, 필수 칸이어도 검증을 통과한다. 그래서 질문을 건너뛴 것과 없다고 답한 것을 가를 수 있다. 저장된 기록을 수정으로 열 때 그 칸의 값이 비어 있으면(null) 트리거에 "{칸 이름} 없음" 을 보인다 — 저장한 기록의 빈 값은 "없다고 답한 것" 이다. 자리 글(placeholder)은 아직 아무것도 묻지 않은 새로 추가할 때만 보인다(2026-10-29 17-1 — 이 구분 규칙의 풀이). Select 에는 지우기 버튼을 두지 않는다(선택 사항인 Input Button 에만).

결제 수단 없음
결제 수단 없음
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
이렇게 — "{칸 이름} 없음" 을 맨 앞 따로 묶음에 — 고르면 칸에 그 글이 들어간다
해당 없음
현금
국민 체크카드
현대카드 M
토스뱅크 통장
해당 없음
이렇게 하지 않는다 — 맨 뒤 "해당 없음" — 칸마다 글이 달라지고 다른 선택지와 섞인다
없음 — 맨 앞 따로 묶음의 "결제 수단 없음" · 맨 뒤 "해당 없음"

여럿 고르기

여럿 고르기는 다시 누르면 풀리므로 고른 것을 되돌리는 선택지가 따로 필요 없다. 대신 생김새로는 드러나지 않는 제약을 글로 알린다.

  • 몇 개까지 고를 수 있는지 — Field 설명에 적는다("최대 3개까지 고를 수 있어요.").
  • 꼭 골라야 하면 — 필수 표시를 하고, 하나도 고르지 않고 제출하면 Field 의 오류로 알린다.
  • 다른 선택지를 바꾸는 선택지를 두지 않는다 — "전체 선택" 이나 나머지를 지우는 "없음" 은 결과를 짐작하기 어렵다. 전체 선택이 잦으면 Checkbox 의 부모 · 자식 묶음을 쓴다. 건너뛴 것과 없다고 답한 것을 갈라야 하면 앞에 Checkbox 를 두고("하자 있음"), 체크했을 때만 Select("하자 종류")를 보인다.
식비, 교통
최대 3개까지 고를 수 있어요.
이렇게 — 몇 개까지 고를 수 있는지 Field 설명에 적는다
전체 선택, 식비, 교통, 쇼핑, 문화, 의료
전체 선택
식비
교통
쇼핑
문화
의료
이렇게 하지 않는다 — "전체 선택" — 다른 선택지를 바꿔 결과를 짐작하기 어렵다
여럿 고르기 — 개수는 설명에 · "전체 선택" 선택지

여럿 고른 값 보이기

트리거는 한 줄이다. 다 보이면 고른 순서대로 쉼표로 잇고("식비, 교통"), 칸 폭을 넘으면 개수로 줄인다 — 가장 먼저 고른 값을 남기고 나머지 개수를 붙인다("식비 외 2개"). "외" 는 앞의 값을 뺀 개수다 — "식비 등 2개" 라고 쓰면 모두 두 개라는 뜻이 되어 틀린다. 값이 서로 동등해 하나를 보여 주는 것이 도움이 안 되면 전체 개수로 쓴다("3개 고름").

고른 값이 조건으로 묶여 결과가 달라지는 자리(필터)는 쉼표 대신 접속사로 관계를 드러내고("식비 또는 교통"), 어떻게 묶이는지를 Field 설명에도 적는다.

식비, 교통
이렇게 — 다 보이면 고른 순서대로 쉼표
식비, 교통, 쇼핑
이렇게 — 넘치면 첫 값 외 나머지 개수(셋을 골랐다) — 값이 동등하면 "3개 고름"
식비 등 2개
이렇게 하지 않는다 — "등 2개" — 모두 두 개라는 뜻이 되어 틀린다
여럿 고른 값 — 쉼표 · "외 N개" · "등 N개"

선택지 순서

사용 빈도 · 크기 · 시간처럼 그 목록이 이미 가진 기준 하나로 일관되게 줄 세운다(가나다순을 기본으로 두지 않는다). 기준이 다른 선택지는 묶음으로 나눠 경계를 드러낸다.

선택지 글

  • 명사형으로 짧게 — "서울 선택" 처럼 동작이나 부연을 붙이지 않는다. 선택지 사이에 낱말 · 길이 · 말투를 맞춘다.
  • 코드값(Y · N · ANNUAL · 아이디)을 글로 내지 않는다(Writing).
  • 트리거 폭에서 말줄임 없이 보이는지 확인한다 — 목록에서는 줄바꿈되어 다 보여도 트리거에서는 잘린다. 길면 글을 줄이는 것을 먼저 하고, 부연은 선택지 설명(한 줄)에 쓴다. 설명은 트리거에 보이지 않는다.
매월
매일
매주
매월매달 같은 날
매년
이렇게 — 명사형으로 짧게 · 낱말과 길이를 맞추고, 부연은 한 줄 설명에
한 달에 한 번씩 같은 날에 반복해요
매일 반복하기
매주 선택
한 달에 한 번씩 같은 날에 반복해요
매년
이렇게 하지 않는다 — 동작을 붙이고 말투가 섞였다 — 목록에서는 다 보여도 트리거에서는 잘린다
선택지 글 — 명사형 · 동작을 붙인 글

placeholder

고르기 전의 글은 어떤 종류의 값을 고르는지 알리는 "{값의 종류} 선택" 꼴이다("결제 수단 선택"). "여기를 눌러 펼쳐 보기" 같은 막연한 글이나 라벨만 그대로 둔 글은 쓰지 않는다. 마침표는 찍지 않는다.

결제 수단 선택
이렇게 — "{값의 종류} 선택" — 무엇을 고르는 칸인지 알린다
여기를 눌러 펼쳐 보기
이렇게 하지 않는다 — 막연한 글 — 칸이 무엇을 받는지 알 수 없다
placeholder — 값의 종류 · 막연한 글

폰에서도 칸 아래 목록

폰에서도 목록은 칸 아래에 붙는다 — 시트로 바꾸지 않는다(SEED). 짧은 선택지를 화면을 덮는 시트로 열면 손이 더 가고 폼이 가려진다. 아래가 모자라면 위로 열린다. 시트가 필요한 것 — 달력 · 시각 · 아이콘 격자 · 검색해 고르는 긴 목록 — 은 Select 가 아니라 Input Button 이 연다.

9:41
거래 추가
12,000원
현대카드 M
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
10월 1일 (목)
시간
오후 3:00
이렇게 — 칸 아래 목록 — 폼이 보이고 한 번에 고른다(아래가 모자라면 위로)
9:41
거래 추가
12,000원
현대카드 M
10월 1일 (목)
시간
오후 3:00
결제 수단
현금
국민 체크카드
현대카드 M
토스뱅크 통장
카카오뱅크 통장
이렇게 하지 않는다 — 짧은 선택지를 시트로 — 손이 더 가고 폼이 가려진다(시트는 Input Button 의 몫)
폰 — 칸 아래 목록 · 짧은 선택지를 시트로

고르는 컴포넌트 고르기

고르는 컴포넌트를 고르는 표는 여기 한 곳이다 — Radio · Checkbox · Switch · Chip · Select Box · Segmented Control · Tabs · List · Searchable List · Wheel Picker · Menu 문서는 선택지 개수를 다시 적지 않고 이 절을 가리키며, 자기 짝 비교 표만 둔다. 선택지 개수의 숫자(2 ~ 4 · 5 이상)도 아래 판단 순서 하나다(2026-10-22 사용자 결정 1B · 2B — https://claude.ai/artifact/SmHD31RQxTjWbfK83nbcym).

먼저 — 이 자리는 무슨 일을 하나

하는 일고를 것Desk 예
폼에 넣을 값을 고른다 — 저장 · 다음을 눌러야 반영아래 판단 순서로 — Select Box · Select · Chip · Radio · Checkbox · Input Button거래 추가의 결제 수단 · 반복 종료 · 공개 범위
보이는 내용을 바로 거르거나 정렬하거나 다르게 본다2 ~ 4가지 고정 보기 → Segmented Control(내용 바로 위, 한 화면 하나) · 조건을 여럿 걸고 푼다 · 개수가 바뀐다 → Chip 필터 바할 일 오늘 · 이번 주 · 전체 · 완료 / 거래 검색의 카테고리 · 기간 · 금액
다른 구역 · 화면으로 옮긴다Tabs — 1차 Line · 2차 Chip Tabs가계부 · 자산 · 통계 · 예산
기능 하나를 켜고 끈다누르는 순간 바뀐다 → Switch(설정 줄이면 List 스위치 줄) · 저장해야 반영 → Checkbox결제 알림 · 고정 금액으로 기록
화면 폭 목록에서 하나를 고르고 바로 바뀐다List 라디오 줄기본 통화 · 테마
누르면 동작을 실행한다Menu(1280 이상) · Menu Sheet(1280 미만) — 값을 고르는 데 쓰지 않는다거래 상세 머리 ⋮ → 복사해 새로 쓰기 · 삭제
생활비 카드
폼에 넣을 값 저장 · 다음을 눌러야 반영 → 아래 판단 순서(Select Box · Select · Chip · Radio · Checkbox)
오늘이번 주전체완료
보이는 내용을 거르거나 다르게 본다 2 ~ 4가지 고정 보기 → Segmented Control(조건을 여럿 걸면 Chip 필터 바)
가계부자산통계예산
다른 구역으로 옮긴다 Tabs — 1차 Line · 2차 Chip Tabs
  • 결제 알림결제 예정일 하루 전 · 당일
기능 하나를 켜고 끈다 누르는 순간 바뀐다 → Switch(설정 줄은 List 스위치 줄) · 저장해야 반영 → Checkbox
  • 대한민국 원KRW
  • 미국 달러USD
화면 폭 목록에서 하나를 고르고 바로 바뀐다 List 라디오 줄
수정
복사
삭제
누르면 동작을 실행한다 Menu(1280 이상) · Menu Sheet(1280 미만) — 값을 고르는 데 쓰지 않는다
첫 갈래 — 폼 값 · 보기 거르기 · 구역 옮기기 · 켜고 끄기 · 화면 폭 목록 하나 · 실행

폼 값 — 위에서부터 판단한다

선택지마다 필요한 정보의 양, 선택지 개수, 글 길이로 고른다. 위에서부터 판단한다(SEED — 2026-10-01 사용자 결정). "이럴 때 더 맞다" 열은 SEED Radio 문서의 판단 기준이다.

순서이런 선택지컴포넌트이럴 때 더 맞다제품의 자리
1그림 · 여러 줄 설명 · 딸린 입력이 필요하다Select Box나란히 견주고 버튼으로 확정한다반복 거래 종료 · 분배 방식 · 내보내기 형식 · 휴가 부여 방법
25개 이상이거나, 한 줄 설명이면 충분하다Select사용자가 답을 이미 안다 · 선택지가 늘어날 수 있다 · 자리가 좁다결제 수단 · 통화 · 휴가 정책 · 계좌 종류 · 반복 단위 · 반복 요일
32 ~ 4개, 설명 없이 글이 짧다Chip한 줄에서 가볍게 — 고른 값이 곧 화면의 갈래일 때거래 종류 · 우선순위 · 알림 소리
32 ~ 4개, 설명 없이 글이 길다Radio · Checkbox"하나의 정답을 고른다" · "해당되는 것에 모두 체크한다" — 서류처럼 차분히 견줄 때공개 범위 · 동의. 예 / 아니오 하나는 Checkbox
4너무 많아 스크롤로 찾기 어렵다Input Button + 검색 시트(Searchable List)이름을 알고 친다결재자 · 사람 · 종목 · 카드사
  • 요일 7개도 2 — 여럿 고르는 Select 다("월, 수, 금").
  • 폼 값을 탭(Segmented Control · Tabs)으로 고르지 않는다 — 탭은 보이는 내용을 바로 거르거나 바꾸는 자리다. 폼 값의 탭은 Chip · Select 로 옮긴다.

예외 · 주의

  • 저장 폼 안의 Switch — 값이 저장 때 들어가도 누르면 화면이 바로 바뀌면 Switch 다(일정 "종일" — 날짜 칸이 바뀐다). Switch 결정(누르는 순간만) 안의 경계다 — 경계 사례는 Checkbox 의 "Checkbox 와 Switch" 표.
  • 거르기 칩은 개수 규칙이 다르다 — 폼 값 칩은 2 ~ 4(판단 순서 3), 거르기 칩은 개수가 바뀌거나 5개 이상인 축(카테고리 · 태그)에 쓴다(Chip 필터 바). 2 ~ 4가지로 고정된 보기는 Segmented Control.
  • 늘어날 수 있는 목록 — 지금 셋이어도 사용자가 더할 수 있는 목록(카테고리 · 결제 수단)은 Select 다(SEED Radio "앞으로 더 늘어날 가능성").
  • 선택지가 하나면 Checkbox — 설명 · 딸린 입력이 붙어도("고정 금액 사용").
  • 글이 칸보다 길면 다른 컴포넌트 — Segmented Control 칸 글이 줄을 바꾸면 Chip · Select 로.
  • 컨트롤을 끼운 줄(스위치 · 체크 · 라디오)에는 다른 버튼을 두지 않는다 — 줄 전체가 라벨이다(List).
  • Menu 로 값을 고르지 않는다 — 테마는 List 라디오 줄, 정렬 · 보기는 Segmented Control · Select. 고른 표시가 없는 목록으로 값을 고르면 지금 값이 안 보인다.
무기한중지할 때까지 계속 반복
횟수 지정정한 횟수만큼 반복
1 · Select Box 그림 · 여러 줄 설명 · 딸린 입력이 필요하다
현대카드 M
2 · Select 5개 이상이거나 한 줄 설명이면 충분하다
지출수입이체
3 · Chip 2 ~ 4개, 설명 없이 글이 짧다(모양만 — Chip 차례에 정한다)
3 · Radio · Checkbox 2 ~ 4개, 설명 없이 글이 길다
고르는 컴포넌트 — Select Box · Select · Chip · Radio

Select 와 Menu

Menu 는 누르는 순간 실행되는 명령 목록이고, Select 는 폼에 넣을 값을 고르는 입력이다.

SelectMenu(그 차례에)
목적폼에 넣을 값 고르기동작 실행(정렬 · 삭제 · 공유)
반영고른 뒤 저장 · 제출해야 반영누르면 바로 실행
FieldField 와 함께(라벨)Field 없이 버튼에서 연다
고른 표시오른쪽 체크없다(누르면 닫히고 실행)
원
원
미국 달러
일본 엔
유로
중국 위안
Select 폼에 넣을 값 · 오른쪽 체크 · 저장해야 반영
거래 내역
정렬 바꾸기
공유
삭제
Menu(그 차례에) 더 보기 버튼에서 · 체크 없음 · 누르면 바로 실행
Select 와 Menu — 폼의 칸 · 더 보기 버튼의 목록

코드

레시피 recipes/shadcn/components/ui/select.tsx(Select · SelectGroup · SelectItem)를 Field 안에 둔다. 선택지는 글(label)을 속성으로 받는다 — 목록이 닫혀 있어도 트리거 · 글자로 찾기(typeahead)가 그 글을 쓴다. 아래 미리보기는 스펙 값으로 그린 모습이다.

기본

import { Field } from "@/components/ui/field"
import { Select, SelectItem } from "@/components/ui/select"

<Field label="통화">
  <Select placeholder="통화 선택" value={currency} onValueChange={setCurrency}>
    <SelectItem value="KRW" label="원" description="KRW" />
    <SelectItem value="USD" label="미국 달러" description="USD" />
    <SelectItem value="JPY" label="일본 엔" description="JPY" />
    <SelectItem value="EUR" label="유로" description="EUR" />
    <SelectItem value="CNY" label="중국 위안" description="CNY" />
  </Select>
</Field>

묶음 · 아이콘 · "없음"

import { Banknote, CircleSlash, CreditCard, Landmark } from "lucide-react"
import { Select, SelectGroup, SelectItem } from "@/components/ui/select"

<Field label="결제 수단">
  <Select placeholder="결제 수단 선택" value={asset} onValueChange={setAsset}>
    <SelectGroup>
      <SelectItem value="none" label="결제 수단 없음" prefixIcon={<CircleSlash />} />
    </SelectGroup>
    <SelectGroup label="카드">
      <SelectItem value="hyundai-m" label="현대카드 M" prefixIcon={<CreditCard />} />
      <SelectItem value="shinhan-deep" label="신한카드 Deep" prefixIcon={<CreditCard />} />
    </SelectGroup>
    <SelectGroup label="계좌 · 현금">
      <SelectItem value="kb" label="국민 주계좌" description="123-45-6789" prefixIcon={<Landmark />} />
      <SelectItem value="cash" label="현금" prefixIcon={<Banknote />} />
    </SelectGroup>
  </Select>
</Field>

여럿 고르기

고른 요일마다 거래를 만들어요.
<Field label="반복 요일" description="고른 요일마다 거래를 만들어요.">
  <Select multiple placeholder="요일 선택" value={days} onValueChange={setDays}>
    {["월", "화", "수", "목", "금", "토", "일"].map((d) => (
      <SelectItem key={d} value={d} label={`${d}요일`} />
    ))}
  </Select>
</Field>

value 는 고른 순서대로 쌓인다. 트리거 글은 formatValue 로 바꾼다 — 기본은 "월요일, 수요일" · 넘치면 "월요일 외 2개".

상태

휴가 정책을 골라주세요.
<Field label="휴가 정책" invalid errorMessage="휴가 정책을 골라주세요.">
  <Select placeholder="휴가 정책 선택">…</Select>
</Field>
<Field label="통화" disabled>
  <Select defaultValue="KRW">…</Select>
</Field>
<Field label="부서" readOnly>
  <Select defaultValue="design">…</Select>
</Field>

Behavior

인터랙션동작
트리거 Click / Tap목록을 열고 닫는다. 포인터로 열면 고른 선택지가 보이게 스크롤만 하고 미리 짚지 않는다.
트리거 ↓ ↑ Enter Space목록을 열고 고른 선택지(없으면 첫 선택지)를 짚는다.
트리거 글자 키(닫힌 채)하나 고르기면 그 글자로 시작하는 다음 선택지로 값을 바로 바꾼다(열지 않는다 — 네이티브 select 와 같다).
<label> 누르기트리거로 포커스만 옮긴다 — 목록은 열지 않는다(Field).
목록 ↓ ↑다음 · 이전 선택지(막힌 선택지는 건너뛴다). Home · End 는 처음 · 끝. 글자 키는 그 글자로 시작하는 선택지로.
목록 Enter · Space · 선택지 누르기하나 고르기 — 고르고 닫는다. 여럿 고르기 — 고르거나 풀고 열어 둔다.
Esc · 바깥 누르기 · Tab닫는다. 고른 값은 그대로다. Esc 로 닫으면 트리거로 포커스가 돌아온다.
마우스 호버짚은 선택지가 따라간다(알약). 터치는 짚지 않는다 — 손가락 아래 미리 칠한 알약은 눌린 채 멈춘 것처럼 보인다.
Disabled포커스 · 열기 불가. 커서 not-allowed.
Readonly포커스는 되고 열리지 않는다. 값은 그대로 읽힌다.

Accessibility

기준검증
WCAG 1.4.3 Color contrast(글자 ≥ 4.5:1)트리거 값 fg-neutral 16.41 · 13.42, 읽기 전용 값 15.20 · 10.32. placeholder 4.18 · 4.13(누름 바탕 위 3.94 · 3.18) — 4.5:1 밑, 사용자가 대가를 알고 SEED 값을 골랐다(2026-10-16 글자 역할 D7, DESIGN v119). 목록 글 fg-neutral 16.41 · 11.62, 묶음 제목 · 설명 fg-neutral-subtle 5.50 · 5.27(알약 위 5.18 · 4.68) ✓. 비활성 fg-disabled 는 기준 밖(비활성 UI)
WCAG 1.4.11 Non-text contrast(≥ 3:1)트리거 키보드 포커스 링 stroke-focus-ring Desk 8.38 · 6.10 · HR 8.17 · 6.07, 오류 테두리 5.06 · 6.08, 고른 표시 체크 16.41 · 11.62 ✓. 누름 · 호버 · 키보드 위치 알약은 목록 바탕과 1.06 · 1.13 ⚠ — SEED 그대로 둔다(2026-10-01 사용자 결정, 키보드 위치에 링을 더하는 안은 고르지 않았다). 짚은 선택지는 화면 읽기 프로그램에 aria-activedescendant 로 알린다. 기본 1px 테두리(1.23 · 1.56)는 라벨 · 셰브론이 칸을 함께 알린다(SEED 와 같다)
WCAG 2.4.7 Focus visible트리거는 키보드 포커스에 링. 목록 안 키보드 위치는 알약(SEED — 위 1.4.11 의 ⚠)
WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24)트리거 · 선택지 모든 크기 ✓(가장 작은 선택지 medium 39)
WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44)트리거 large 52 ✓ · 선택지 large 46 ✓. medium 40 · 39 ⚠ — 1280 이상 데스크톱 웹(마우스)에서만(예외 — Table Pagination 의 고르기)
ARIA트리거 <button role="combobox" aria-haspopup="listbox" aria-expanded aria-controls> — 이름은 Field 의 라벨(<label for>), 라벨이 없으면 aria-label. 오류 aria-invalid, 필수 aria-required, 읽기 전용 aria-readonly. 목록 role="listbox"(여럿이면 aria-multiselectable) · 짚은 선택지는 aria-activedescendant, 선택지 role="option" aria-selected(막히면 aria-disabled), 묶음 role="group" + 제목 aria-labelledby. 앱은 listbox · option 대신 — Flutter SemanticsRole 에 listbox · option 이 없다. 하나 고르기 선택지는 Semantics(button: true, selected: 고름, label: 제목 + 설명)("원, 선택됨, 버튼"), 여럿 고르기는 checked: 고름("체크됨 · 체크 안 됨")을 더한다. 목록 노드는 역할을 지어내지 않고 이름만 둔다("목록 안 · n개 중 m번째" 는 스크롤 자식 수로 OS 가 붙인다). 이 꼴을 앱 테스트로 고정한다 — 선택지를 라디오(checked + inMutuallyExclusiveGroup)로 바꾸는 안은 고르지 않았다(2026-10-17 사용자 결정 — 03-B 결정 2 A)

Do / Don't

✅ Do

  • 모든 Select 를 Field 로 감싸 라벨을 단다.
  • placeholder 는 "{값의 종류} 선택".
  • "없음" 이 답이 될 수 있으면 "{칸 이름} 없음" 을 맨 앞 따로 묶음에.
  • 여럿 고르기는 최대 개수를 Field 설명에.
  • 선택지 글은 명사형으로 짧게, 부연은 한 줄 설명에.
  • 폰에서도 칸 아래 목록 — 시트는 Input Button 으로.

❌ Don't

  • placeholder 를 라벨 대신 쓰기.
  • "해당 없음" · "선택 안 함" 처럼 칸마다 다른 "없음" 글, 지우기 버튼으로 "없음" 받기.
  • "전체 선택" 처럼 다른 선택지를 바꾸는 선택지.
  • 2 ~ 4개 짧은 선택지를 Select 로 숨기기(Chip · Radio), 검색이 필요한 긴 목록을 Select 로.
  • 폼 값을 탭으로 고르기.
  • 폰에서 medium(40) 쓰기.

Specification

select.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 Select 를 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다.

Base

부위상태속성값
triggerenabled배경backgroundtransparent
테두리borderColorstroke-neutral-weak#E5E8EF다크 #404757
테두리 두께borderWidth1px상자 안쪽(inset). 오류의 2px 는 안쪽에 덧그린다
커서cursorpointer
전환 대상transitionPropertybackground-color
전환 시간transitionDurationmotion-duration-color-transition150ms
전환 곡선transitionEasingmotion-ease-easingcubic-bezier(0.35, 0, 0.35, 1)
valueenabled값 글자색foregroundfg-neutral#1A1F2E다크 #F5F6FA
굵기fontWeight400
placeholderenabledplaceholder 색foregroundfg-placeholder#767C8B다크 #838997
굵기fontWeight400
prefixIconenabled앞 아이콘 색colorfg-neutral-muted#535866다크 #B7BDCC
chevronenabled색colorfg-neutral-muted#535866다크 #B7BDCC
contentenabled최대 높이maxHeightmin(480px, 남은 화면)남은 화면 = 목록이 놓이는 쪽(아래에 다 들어가면 아래, 아니면 아래 · 위 중 넓은 쪽)의 트리거 ~ 화면 가장자리 8. 화면 키보드는 세지 않는다 — 열면 초점이 목록으로 가 키보드가 내려간다(Q-008-1). 남은 화면이 200 보다 좁아도 200 은 둔다 — 넘치면 목록 안에서 스크롤. 열면 고른 선택지가 보이게 스크롤된다
너비width트리거 폭트리거와 같은 폭 — 선택지 글은 목록 안에서 줄바꿈된다
상하 여백paddingYspacing-x28px
바깥 여백marginspacing-x28px화면 가장자리와 최소 거리(안전 영역이 있으면 그 값)
위 바깥 여백marginTopspacing-x28px트리거와의 거리 — 아래가 모자라면 위로 뒤집는다
간격gapspacing-x28px묶음 사이(위아래 8 + 선 1px + 8)
모서리radiusradius-r520px
배경backgroundbg-layer-floating#FFFFFF다크 #2D3346
그림자shadowshadow-s30 8px 24px -4px rgba(15, 18, 28, 0.10), 0 2px 6px -2px rgba(15, 18, 28, 0.05) · 다크 0 8px 24px -4px rgba(0, 0, 0, 0.50), 0 2px 6px -2px rgba(0, 0, 0, 0.25), inset 0 1px 0 0 rgba(255, 255, 255, 0.08)
z-indexzIndex200z-floating — specs/z-index.md L3. 대화상자 · 시트(101) 안에서 열어도 그 위
itemenabled좌우 여백paddingXspacing-x416px
커서cursorpointer
전환 대상transitionPropertybackground-color알약 바탕과 들임(inset)이 함께
전환 시간transitionDurationmotion-duration-color-transition150ms
전환 곡선transitionEasingmotion-ease-easingcubic-bezier(0.35, 0, 0.35, 1)
itemIconenabled색colorfg-neutral#1A1F2E다크 #F5F6FA
itemLabelenabled글자·아이콘foregroundfg-neutral#1A1F2E다크 #F5F6FA
굵기fontWeight400
itemDescriptionenabled간격gapspacing-x0_52px글 ↔ 설명
글자·아이콘foregroundfg-neutral-subtle#62697A다크 #A2A8B7
indicatorenabled색colorfg-neutral#1A1F2E다크 #F5F6FAlucide check · 선 2.5 — 앱은 2.5 굵기 글꼴 LucideIcons.check500(Q-008-2)
highlightenabled모서리radiusradius-r312px
배경backgroundtransparent누를 때 · 키보드로 옮길 때만 칠한다
groupLabelenabled좌우 여백paddingXspacing-x416px
글자·아이콘foregroundfg-neutral-subtle#62697A다크 #A2A8B7
dividerenabled높이height1px
아래 바깥 여백marginBottomspacing-x28px
좌우 바깥 여백marginXspacing-x416px좌우 16 들인다
색colorstroke-neutral-subtle#EDEFF3다크 #353B4D
triggerpressed배경backgroundbg-layer-default-pressed#F7F8FD다크 #353B4DSEED bg.transparent-pressed 의 불투명 짝. 마우스는 호버에 같은 바탕
배율scale콘텐츠 2px 거리 축소값 · 아이콘만 준다(SEED scaleScope: content) — 테두리 · 바탕은 그대로
축소 시간scaleDurationmotion-duration-pressed-scale150ms
축소 곡선scaleEasingmotion-ease-pressed-scalecubic-bezier(0, 0, 0.15, 1)
itempressed배율scale콘텐츠 2px 거리 축소누르는 동안만(호버 · 키보드 위치는 축소 없음)
highlightpressed바탕 들임insetXspacing-x28px좌우 8 들어온 알약 — 글은 알약 안쪽 8 지점에서 시작한다
배경backgroundbg-layer-floating-pressed#F7F8FD다크 #353B4D목록 바탕(bg-layer-floating) 위의 누름. 마우스 호버 · 키보드 위치도 같다
focusRingfocused링 두께width2px
링 색colorstroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84트리거 — 키보드 포커스에만(마우스 · 터치로 눌러서는 없다)
링 간격offset2px
chevronopen회전rotate180deg
indicatorselected불투명도opacity1고르지 않은 선택지에는 체크를 그리지 않는다(자리도 없다)
triggerinvalid테두리borderColorstroke-critical-solid#D72323다크 #FF84771px 위에 2px 가 나타난다(100ms)
테두리 두께borderWidth2px
triggerdisabled배경backgroundbg-disabled#F5F6FA다크 #353B4D흐리게 하지 않는다(v106)
커서cursornot-allowed
valuedisabled값 글자색foregroundfg-disabled#8A91A0다크 #838997
placeholderdisabledplaceholder 색foregroundfg-disabled#8A91A0다크 #838997
prefixIcondisabled앞 아이콘 색colorfg-disabled#8A91A0다크 #838997
chevrondisabled색colorfg-disabled#8A91A0다크 #838997
itemdisabled커서cursornot-allowed
itemIcondisabled색colorfg-disabled#8A91A0다크 #838997
itemLabeldisabled글자·아이콘foregroundfg-disabled#8A91A0다크 #838997
itemDescriptiondisabled글자·아이콘foregroundfg-disabled#8A91A0다크 #838997
indicatordisabled색colorfg-disabled#8A91A0다크 #838997
triggerreadonly배경backgroundbg-disabled#F5F6FA다크 #353B4D값은 진한 글자 그대로 — 열리지 않는다
커서cursordefault

size=large

부위상태속성값
triggerenabled최소 높이minHeight52px
좌우 여백paddingXspacing-x416px
간격gapspacing-x2_510px
모서리radiusradius-r312px
valueenabled값 글자typographytext-t516px / 22px
placeholderenabled글자typographytext-t516px / 22px
prefixIconenabled앞 아이콘size20px
chevronenabled크기size20px
itemenabled높이height46px한 줄 — 설명이 있으면 66
상하 여백paddingYspacing-x312px
간격gapspacing-x312px
itemIconenabled크기size22px
itemLabelenabled글자typographytext-t516px / 22px
itemDescriptionenabled글자typographytext-t313px / 18px / 400
indicatorenabled크기size14px
groupLabelenabled상하 여백paddingYspacing-x2_510px
글자typographytext-t414px / 19px / 400
굵기fontWeight500

size=medium

부위상태속성값
triggerenabled최소 높이minHeight40px
좌우 여백paddingXspacing-x3_514px
간격gapspacing-x28px
모서리radiusradius-r28px
valueenabled값 글자typographytext-t414px / 19px
placeholderenabled글자typographytext-t414px / 19px
prefixIconenabled앞 아이콘size16px
chevronenabled크기size16px
itemenabled높이height39px한 줄 — 설명이 있으면 57
상하 여백paddingYspacing-x2_510px
간격gapspacing-x28px
itemIconenabled크기size18px
itemLabelenabled글자typographytext-t414px / 19px
itemDescriptionenabled글자typographytext-t212px / 16px / 400
indicatorenabled크기size12px
groupLabelenabled상하 여백paddingYspacing-x28px
글자typographytext-t313px / 18px / 400
굵기fontWeight400

size=responsive

부위상태속성값
triggerenabled바뀌는 폭breakpoint1280px미만은 large, 이상은 medium 의 값을 쓴다(SEED lg) — 앱은 늘 large

SEED 와 다른 점

  • 누름 · 호버 · 키보드 위치 바탕은 불투명한 색 — 트리거 bg-layer-default-pressed, 목록 안 알약 bg-layer-floating-pressed. SEED bg.transparent-pressed 는 투명도가 있어 대비 검사기가 받지 않는다(v102). 묶음 사이 선도 불투명한 stroke-neutral-subtle(SEED stroke.neutral-muted 는 투명도가 있다).
  • 그림자는 groo shadow-s3(v105 — SEED 와 구조는 같고 값은 groo).
  • 아이콘은 lucide 선 아이콘(v106) — 셰브론 chevron-down, 고른 표시 check(선 2.5). SEED 는 채운 체크다.
  • 선택지 커서는 pointer — SEED 는 default. groo 의 누르는 줄(List · Select Box)과 맞춘다.
  • 트리거 이름은 라벨 하나 — SEED React 는 트리거에 고른 값을 따로 읽히지 않는다. groo 는 role="combobox" 의 값으로 고른 글이 읽힌다(라벨 + 값).

Migration notes

2026-10-30 — 저장된 빈 값은 "없음"

"없음 을 답으로 받기" 의 풀이 한 줄을 더했다 — 수정으로 연 기록의 빈 값(null)은 "{칸 이름} 없음", 자리 글은 새로 추가할 때만(2026-10-29 17-1 에서 웹 · 앱에 시킨 규칙). 값 변경 없음. 옛 스펙은 select.history/v-pre-cleanup-1030.md.

2026-10-25 — 목록 줄 ⋮ 를 걷은 결정에 맞춤

"고르는 컴포넌트 고르기" 표의 Menu 예 "거래 줄 ⋮ → 수정 · 복사 · 삭제" 를 "거래 상세 머리 ⋮ → 복사해 새로 쓰기 · 삭제" 로 — 목록 줄에는 ⋮ 를 두지 않는다(Menu 의 "줄의 동작", 2026-10-25 사용자 결정). 값 변경 없음.

2026-10-22 — 첫 갈래 그림

"먼저 — 이 자리는 무슨 일을 하나" 표에 그림 first-guide 를 더했다(고르는 법 1B 의 비용 줄 "사이트 그림 pick-guide 에 첫 갈래 그림 하나" — 스펙 PR1 에서 남은 것). 판단 순서 그림 pick-guide 는 그대로다. 값 변경 없음.

2026-10-22 — 고르는 컴포넌트 고르기를 한 곳에

"고르는 컴포넌트 고르기" 를 넓혀 고르는 표를 이 절 한 곳에 모았다 — 첫 갈래(이 자리는 무슨 일을 하나) 표, 판단 순서(10-01 결정 표에 SEED Radio 의 "이럴 때 더 맞다" 열), 예외 · 주의. 다른 문서(Radio · Select Box · Segmented Control · Tabs · Searchable List · Wheel Picker · Menu · Checkbox · Switch)는 선택지 개수를 지우고 이 절을 가리키며 자기 짝 비교 표만 둔다. 선택지 개수는 이 표의 2 ~ 4 · 5 이상 하나다 — Radio "2 ~ 5 · 6 이상" · Select Box "2 ~ 6 · 7 이상" · Wheel Picker "5개 이하" 를 걷었다(사용자 결정 1B · 2B — https://claude.ai/artifact/SmHD31RQxTjWbfK83nbcym). 값 변경 없음.

2026-10-17 — 앱 선택지 낭독(03-B 결정 2 A)

스펙이 웹 ARIA(listbox · option)만 적고 앱의 의미 트리를 말하지 않았다(조사 03B-004 · 005). 사용자가 비교 페이지에서 "지금 그대로"(A)를 골랐다 — 앱 PSelect 선택지(p_select.dart:933~940)의 Semantics(container, button: true, selected, checked: multiple ? selected : null, label) 를 스펙 줄로 적었다(Accessibility 의 ARIA). 코드는 바뀌지 않는다 — 앱은 하나 고르기 선택지가 button + selected · 여럿 고르기가 checked 인지 위젯 테스트로 고정한다. 하나 고르기를 라디오로 바꾸는 안(B)은 고르지 않았다. 옛 스펙은 select.history/v-pre-03e-03b.md.

2026-10-15 — 남은 화면 · 앱의 체크 굵기

구현 세션 질문 두 가지를 있는 규칙으로 정리했다.

  • "남은 화면" 은 목록이 놓이는 쪽의 트리거 ~ 화면 가장자리 8(Q-008-1). 웹(Radix 의 가용 높이) · 앱이 이미 그렇게 잰다. 열면 초점이 목록으로 가 입력 칸의 키보드가 내려가므로, 목록이 떠 있는 동안 키보드가 올라와 있는 경우는 없다 — 키보드를 세는 규칙을 두지 않는다. "Content" 절 문장을 고쳤다.
  • 고른 표시 check 선 2.5 는 그대로(Q-008-2). 앱은 굵기를 바꿀 수 있다 — lucide 의 2.5 굵기 글꼴(LucideIcons.check500)을 쓴다. 앱이 이미 탭 바 · 떠 있는 버튼에서 같은 글꼴(house500 · plus500)을 쓴다.

2026-10-01 — SEED Select 로

사용자가 비교 페이지에서 여덟 가지를 모두 SEED 로 정했다 — Select(짧은 선택지 5개 이상 · 칸 아래 목록)와 Input Button(달력 · 시각 · 아이콘 격자 · 긴 목록을 시트 · 팝오버로)으로 나눈다 · 폰에서도 칸 아래 목록 · 목록 모양 SEED 그대로 · "없음" 은 "{칸 이름} 없음" 선택지(맨 앞 따로 묶음, Select 에 지우기 버튼 없음) · 여럿 고르기를 둔다 · 고르는 컴포넌트 순서 SEED 그대로(요일 7개도 Select, 폼 값의 탭은 걷는다) · Input Button 은 1280 미만 시트 · 이상 팝오버, 달력 · 시각은 "완료" 로 확정 · 긴 목록은 Input Button + 검색 시트(Combobox 를 두지 않는다). 옛 스펙은 select.history/v-pre-seed-select.* 에 남겼다.

옛 Select새 Select
트리거 40 하나 · 모서리 4 · 회색 채운 바탕(surface-input) + 1px border-defaultlarge 52(모서리 12) · medium 40(8) · 반응형 · 투명 바탕 + 안쪽 1px stroke-neutral-weak — Input 과 같은 상자
셰브론 16 text-tertiary, 열려도 그대로20 · 16 fg-neutral-muted, 열리면 180°
포커스: 브랜드 테두리 + 30% 링 · 비활성 흐림 50%키보드 포커스 링 2px · 띄움 2 · 오류 안쪽 2px · 비활성 · 읽기 전용 bg-disabled(흐림 없음)
목록: 모서리 4 · 1px 테두리 · shadow-md · 위아래 4 · 폭은 트리거 이상 · 최대 384모서리 20 · bg-layer-floating · shadow-s3 · 위아래 8 · 트리거 폭 그대로 · 아래 8 · 최대 min(480, 남은 화면)
선택지 32 · 왼쪽 체크(32 들임) · 짚으면 채운 줄large 46 · medium 39 · 오른쪽 체크(고른 것만 — 자리도 없다) · 누름 · 호버 · 키보드 위치는 좌우 8 들인 알약
하나만 고른다하나 · 여럿(multiple) — "식비, 교통" · "식비 외 2개"
Radix Select 조각(SelectTrigger · SelectValue · SelectContent · SelectLabel · SelectSeparator · 스크롤 버튼)Select(트리거 · 목록) · SelectGroup label · SelectItem value label description prefixIcon — 묶음 사이 선은 저절로

레시피는 Radix Select 대신 Radix Popover 위에 목록(role="listbox")을 직접 짰다 — Radix Select 는 여럿 고르기를 받지 않는다.

제품은 앱 적용 단계에서 옮긴다(2026-10-01 조사 — 세 제품 코드를 읽고 일부는 크로미움에 띄워 쟀다).

  • Desk 웹 — Select 29(40 · 글자 15 · 모서리 4 · 채운 바탕, Field 라벨 연결 0/29) · 날짜 14 · 시각 6 · 월 고르기 7 · 폼 값을 고르는 Tabs 16. 고르는 칸 높이 40 / 36 / 33.5 / 32 / 28 / 20, 셰브론 7가지(하나도 안 돈다), 목록 4가지, "없음" 글 8가지(선택 안 함 · 일시불 · 가져오지 않음 · 최상위 카테고리로 두기 · 라벨이 없습니다 · 태그 없음 · 반복 없음 · 없음 — 맨 앞 · 맨 뒤 섞임). 다크에서 목록 그림자가 라이트 값, 키보드 위치 1.12:1, 키보드로 못 쓰는 고르기 4.
  • Desk 앱 — PSelect 24(칸 43 · 40 · 모서리 4) · 값 고르기 탭 15 · 칩 · 타일 단일 15무리 · 다중 11무리. 고른 상태 모양 17가지. 목록에 없는 값이면 칸이 비고 값은 그대로 저장된다(D1), 숨겨진 자산이 "선택 안 함" 으로 보이며 저장된다(D2), 반복 폼이 낡은 값을 저장한다(D3), 비활성 PSelect 가 활성과 같다(D8), 영어 모드에 한국어(D10 · D11). 계좌 종류 6칸 탭은 "마이너스통장" 이 어느 폰 폭에도 안 들어간다.
  • HR 웹 — Select 72(36 · 글자 14, 폭 세 가지, Field 라벨 연결 11/72) · 사람 고르기 7(검색 없음) · 역할 콤보박스 1. 예 / 아니오를 Select 로 받는 칸 3(→ Checkbox), 코드값(Y · N · ANNUAL · user_id)이 글로 보인다, 2 ~ 4개 폼 값 16칸이 모두 Select(→ Chip). 테두리 대비 1.16 · 셰브론 1.91.

이 페이지