Groo Design

Icon Picker

카테고리 · 저축 목표에 붙일 아이콘을 고르는 칸 — Input Button 이 고른 세트(148개 · 13 묶음)의 격자를 시트(1280 미만) · 팝오버(1280 이상)로 열고, 한국어 이름 · 찾는 말로 찾는다. 고른 아이콘은 List 의 카테고리 타일(40 · 아이콘 20)에 그린다.

SEED 에는 아이콘을 고르는 부품이 없다 — SEED 팀이 만든 아이콘 격자는 문서 사이트의 Icon Library(칸 ≈ 60 · 사이 16 · 아이콘 24, 칸마다 Tab)뿐이고 제품 부품이 아니다. 원칙은 Iconography 의 "당근의 모노크롬, 멀티컬러 아이콘은 총 300여개 … 가능한 현재 제작된 범위 내에서 사용하는 것을 권장" 과 "24px 크기에서는 44px 이상의 여백을 권장" 이다(당근 SEED, Apache-2.0). groo 는 lucide 전체(2,007)를 열지 않고 카테고리용 세트를 묶음으로 두고, 칸 · 고른 표시 · 키보드 · 한국어 찾기를 정했다(2026-10-09 사용자 결정). 옛 Icon Picker 스펙(40 정사각 트리거 · 팝오버 320 · 8열 × 32 · 영어 이름 찾기 · 고른 칸 브랜드 채움)을 대신한다.

수치 원본은 icon-picker.yaml이고, 아이콘 세트는 category-icons.yaml(148개 · 13 묶음 — 이름 · 찾는 말)이다. 트리거 · 시트 · 팝오버 · 찾기 칸의 나머지 값은 각 스펙이 원본이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.

9:41
카테고리 추가
카페
커피
아이콘
아이콘 이름 검색
desk.groo.app
카테고리 추가
카페
커피
아이콘 이름 검색
9:41
카테고리 추가
카페
커피
아이콘
아이콘 이름 검색
desk.groo.app
카테고리 추가
카페
커피
아이콘 이름 검색
카테고리 추가 — 아이콘 칸을 누르면 시트(폰) · 팝오버(데스크톱), 라이트 · 다크

직접 골라 보기

찾는 말("커피" · "월세" · "고양이") · 여는 자리(시트 · 팝오버) · 지금 아이콘을 고르면 스펙대로 그린 격자와 그 코드가 바뀐다. 격자에 초점을 두고 ← → ↑ ↓ 로 묶음을 넘어 옮기고 Enter 로 고를 수 있다 — 화면 읽기 프로그램이 읽는 말("커피, 선택됨")과 결과 수 알림이 함께 바뀐다.

커피

화면 읽기아이콘, 커피, 버튼, 대화상자 열림

찾는 말 — 이름 · 찾는 말 · lucide 이름에서 부분 일치(열어서 쳐 봐도 된다)
검색 결과 2개 — 커피 · 원두
여는 자리
지금 아이콘 value
모드
import { useState } from "react"
import { Field } from "@/components/ui/field"
import { IconPicker } from "@/components/ui/icon-picker"
import { DEFAULT_CATEGORY_ICON } from "@/lib/category-icons"

const [icon, setIcon] = useState<string>("coffee")

<Field label="아이콘">
  {/* 1280 미만 Bottom Sheet · 이상 Popover 408 — 레시피가 폭으로 고른다 */}
  <IconPicker value={icon} onValueChange={setIcon} />
</Field>

Anatomy

커피
아이콘
아이콘 이름 검색
칸 48 · 아이콘 24
고름 — 안쪽 2px · 선 2.5
ⓐ Trigger — Input Buttonⓑ Surface — Bottom Sheet · Popover 408ⓒ Search — Input 밑줄형ⓓ Group Headerⓔ Cell — 48ⓕ Selected
트리거(라벨 · 지금 아이콘 · 이름) · 시트 · 찾기 칸 · 묶음 머리 · 칸 48 · 고른 칸
부위설명
ⓐ Trigger트리거 — Input Button(줄 칸 폼에서는 줄 — 아래 "트리거"). 라벨 "아이콘", 앞에 지금 아이콘, 값은 그 이름("커피").
ⓑ Surface여는 자리 — 1280 미만 Bottom Sheet("아이콘") · 이상 Popover.
ⓒ Search찾기 칸 — Input 밑줄형 · 앞 돋보기 · 지우기, 위에 붙는다.
ⓓ Group Header묶음 머리 — "식비" · "카페" … 찾는 동안은 없다.
ⓔ Cell칸 — 48 · 아이콘 24. 이름은 한국어 이름.
ⓕ Selected고른 칸 — 안쪽 2px 짙은 테두리 + 선 2.5.

부위

부위설명
trigger트리거 — Input Button. 라벨 "아이콘"(Field) · 앞 지금 아이콘 · 값 = 아이콘 이름 · 뒤 chevron-down
surface여는 자리 — 1280 미만 Bottom Sheet("아이콘") · 이상 Popover(머리 없이 · 이름 "아이콘")
search찾기 칸 — Input 밑줄형 · 앞 돋보기 · 지우기. 위에 붙는다
scroll스크롤 상자 — 묶음 머리 · 격자가 넘치면 이 안에서 스크롤한다(끝 흐림). 찾기 칸은 그 밖이라 늘 위에 있다
groupHeader묶음 머리 — "식비" · "카페" … 찾는 동안은 없다
grid격자 — listbox. 칸 48 을 들어가는 만큼(폰 시트 6열 · 팝오버 7열)
cell칸 — 48 · 모서리 12. 이름은 한국어 이름
icon칸 아이콘 — 24
count결과 수 — 화면 밖 알림
empty결과 없음 — Result Section medium
focusRing키보드 포커스 링 — 칸 바깥

Properties

트리거 — Input Button

트리거는 Input Button 이다 — 라벨 "아이콘"(Field), 앞 붙이개에 지금 아이콘(large 20 · medium 16), 값에 그 아이콘의 한국어 이름("커피"), 뒤에 아래 화살표(사용자 결정 — 2026-10-01 Input Button). 이름은 "아이콘, 커피" 로 읽힌다. 아이콘이 없는 항목은 태그 + "태그", 세트 밖 아이콘이 저장된 항목은 그 아이콘 + "지금 아이콘" 이다. 크기는 반응형(1280 미만 large 52 · 이상 medium 40)이고 앱은 large 다.

폼이 줄 칸 문법(치는 값은 칸 · 고르는 값은 줄)이면 트리거는 Input Button 이 아니라 줄이다 — 줄 제목 "아이콘", 값 자리에 지금 아이콘 + 한국어 이름(세트 밖은 "지금 아이콘"), 누르면 아래 "여는 자리" 가 그대로 열린다(2026-10-23 사용자 결정 — 저축 목표 3-B, 색 줄과 같은 길).

커피
large 521280 미만 · 앱 — 앞 아이콘 20
커피
medium 401280 이상 — 앞 아이콘 16
태그
아이콘이 없는 항목 — "태그"
지금 아이콘
세트 밖에 저장된 아이콘 — "지금 아이콘"
트리거 — "아이콘" · 커피 아이콘 + "커피" · 아래 화살표, large · medium

트리거

속성값비고
컴포넌트Input Buttoninput-button.yaml — 크기 반응형(large 52 · medium 40). 라벨 "아이콘"(Field), 앞 아이콘 = 지금 아이콘(large 20 · medium 16 · fg-neutral-muted), 값 = 아이콘의 한국어 이름("커피"), 뒤 chevron-down. 이름은 "아이콘, 커피"(라벨 + 값). 아이콘이 없는 항목은 태그 + "태그", 세트 밖 저장 아이콘은 그 아이콘 + "지금 아이콘"

여는 자리

1280 미만 · 앱은 Bottom Sheet(제목 "아이콘" · 닫기), 1280 이상은 Popover(머리 없이 · 이름 "아이콘" · 폭 408)다(Input Button 의 경계). 고르면 바로 닫힌다 — "완료" 를 두지 않는다(Input Button 의 "목록은 누르면 바로").

아이콘
아이콘 이름 검색
Bottom Sheet — 6열1280 미만 · 앱. 본문 312 · 칸 사이 4.8 · 끝 흐림 위 20 · 아래 80(찾기 칸은 밖) · 찾기 칸 ↔ 첫 머리 글 32
아이콘 이름 검색
Popover 408 — 7열1280 이상. 본문 360 · 칸 사이 4 · 끝 흐림 위 20 · 아래 80(찾기 칸은 밖) · 찾기 칸 ↔ 첫 머리 글 32
폰 시트 6열 · 데스크톱 팝오버 7열

여는 자리

surfacesurface 너비surface 좌우 여백surface 컴포넌트search 높이scroll 끝 흐림grid 칸 수설명
sheet (기본)—spacing-x6 24px — 본문 좌우 24(Bottom Sheet)Bottom Sheet — bottom-sheet.yaml — 제목 "아이콘" · 닫기. 고르면 닫힌다("완료" 없음)40px — 밑줄형 large — 글 18 / 24 · 돋보기 24켬 — 격자가 넘친다 — Bottom Sheet 본문의 끝 흐림(Scroll Fog overlayBody, 위 20 · 아래 80 늘 켜짐 · 2026-10-03 규칙). 찾기 칸은 스크롤 상자 밖이라 흐리지 않는다6 — 폰 본문 312 ~ 342 — 48 × 6 + 4 × 5 = 308 이 들어간다. 본문이 308 보다 좁으면(320 폰) 들어가는 만큼(5열), 태블릿 시트(432)도 6열1280 미만 · 앱 — Bottom Sheet(기본)
popover408px — 칸 48 × 7 + 사이 4 × 6 = 360 + 좌우 24 × 2 — Popover 폭 320 ~ 480 안spacing-x6 24px — 본문 좌우 24Popover — popover.yaml — 머리 없이(고르는 패널) · 이름 aria-label "아이콘" · 트리거 아래 8 · 최대 높이 60034px — 밑줄형 medium — 글 16 / 22 · 돋보기 20켬 — 격자가 넘친다 — Popover 본문의 끝 흐림(Scroll Fog overlayBody, 위 20 · 아래 80 늘 켜짐 · 2026-10-03 규칙). 찾기 칸은 스크롤 상자 밖7 — 본문 360 = 48 × 7 + 4 × 61280 이상 데스크톱 웹 — Popover

세트 — 148개 · 13 묶음

고를 수 있는 것은 category-icons.yaml 의 세트뿐이다 — 카테고리에 쓸 만한 lucide 아이콘 148개를 13 묶음으로 나누고 아이콘마다 한국어 이름과 찾는 말을 두었다(사용자 결정 15A — 비교 페이지의 초안을 다듬었다). 서버가 심는 기본 카테고리 11개의 아이콘과 기본값(태그 · 저축 목표의 돼지저금통)을 모두 담는다. 아이콘 이름은 웹(lucide-react 1.28.0)과 앱(lucide_icons_flutter 3.1.15)에 모두 있는 것만 썼다.

묶음수예
식비23식사 · 외식 · 요리 · 치킨 · 빵 · 과일 · 장보기
카페7커피 · 음료 · 맥주 · 원두
교통12버스 · 지하철 · 택시 · 주유 · 주차 · 비행기
주거14집 · 아파트 · 전기 · 수도 · 가스 · 인터넷 · 휴대폰
생활15생활용품 · 세탁 · 미용 · 택배 · 육아 · 강아지 · 고양이
쇼핑12쇼핑 · 옷 · 선물 · 전자기기 · 책
건강10병원 · 약 · 운동 · 건강 · 치과
금융16지갑 · 현금 · 저금 · 카드 · 은행 · 투자 · 대출 · 보험 · 이체 · 구독
여가15영화 · 음악 · 게임 · 공연 · 캠핑 · 수영
여행6여행 · 숙소 · 관광 · 해외
교육6교육 · 학교 · 공부 · 어학
경조사6생일 · 경조사 · 기부 · 모임 · 종교
기타6태그 · 별 · 책갈피 · 상자 · 폴더 · 그 밖

식비 23

식사utensils
외식utensils-crossed
요리chef-hat
국 · 찌개soup
샐러드salad
샌드위치sandwich
피자pizza
햄버거hamburger
치킨drumstick
고기beef
생선fish
달걀egg-fried
빵croissant
케이크cake-slice
아이스크림ice-cream-cone
과자cookie
사탕candy
과일apple
채소carrot
팝콘popcorn
우유milk
쌀 · 곡물wheat
장보기shopping-basket

카페 7

커피coffee
음료cup-soda
물glass-water
맥주beer
와인wine
칵테일martini
원두bean

교통 12

버스bus
지하철train-front
트램tram-front
자동차car
택시car-taxi-front
주유fuel
주차circle-parking
비행기plane
배ship
자전거bike
승차권ticket
길 · 이동route

주거 14

집house
아파트building
열쇠key-round
전기zap
수도droplet
가스flame
인터넷wifi
휴대폰smartphone
TVtv
가구sofa
침대bed
조명lamp
수리wrench
인테리어paint-roller

생활 15

생활용품shopping-cart
세제spray-can
세탁washing-machine
미용scissors
청소sparkles
쓰레기trash-2
택배package
우편mail
우산umbrella
육아baby
강아지dog
고양이cat
반려동물paw-print
꽃flower
식물sprout

쇼핑 12

쇼핑shopping-bag
옷shirt
안경glasses
시계watch
보석gem
선물gift
가게store
전자기기laptop
이어폰headphones
카메라camera
책book
가방backpack

건강 10

병원heart-pulse
종합병원hospital
약pill
진료stethoscope
주사syringe
운동dumbbell
활동activity
건강heart
치과smile
안과eye

금융 16

지갑wallet
현금banknote
동전coins
저금piggy-bank
카드credit-card
은행landmark
영수증receipt
이자percent
투자trending-up
주식chart-line
대출hand-coins
보험shield-check
세금calculator
회사briefcase
이체arrow-left-right
구독repeat

여가 15

영화film
영상clapperboard
음악music
게임gamepad-2
공연drama
미술palette
독서book-open
캠핑tent
등산mountain
수영waves-horizontal
스포츠trophy
악기guitar
파티party-popper
보드게임dice-5
취미puzzle

여행 6

여행luggage
숙소hotel
관광map
장소map-pin
해외globe
휴양tree-palm

교육 6

교육graduation-cap
학교school
문구pencil
공부notebook-pen
도서관library
어학languages

경조사 6

생일cake
경조사heart-handshake
기부hand-heart
모임users
종교church
청첩장mail-open

기타 6

태그tag
별star
책갈피bookmark
상자box
폴더folder
그 밖ellipsis

모두 148개 · 13 묶음 — 기본(아이콘 없는 항목)은 태그

세트 전체 — 13 묶음 148개, 이름과 함께

격자 · 칸

칸은 48 · 모서리 12 · 아이콘 24(fg-neutral · 선 2)이고, 놓인 폭에 들어가는 만큼 열을 둔다 — 칸 사이 최소 4, 남는 폭은 칸 사이에 고르게(폰 시트 6열 · 팝오버 7열), 줄 사이 4(사용자 결정 17A). 묶음마다 머리(14 · 500 · fg-neutral-subtle — List Header mediumWeak)를 위 12 · 아래 8 에 둔다. 칸 48 이 곧 누르는 영역이다(SEED Iconography 의 "24px 크기에서는 44px 이상").

폰 3426열 · 칸 사이 10.8 · 줄 사이 4 · 머리 위 12 아래 8
팝오버 3607열 · 칸 사이 4 · 줄 사이 4 · 머리 위 12 아래 8
칸 48 · 아이콘 24 · 사이 · 묶음 머리 — 폰 342 · 팝오버 360

격자

속성값비고
역할listbox이름 "아이콘". 묶음은 role="group" + 머리로 이름(aria-labelledby). 격자에 Tab 하나(고른 칸, 없으면 첫 칸)
칸 수들어가는 만큼칸 48 · 사이 최소 4 로 들어가는 열 — 폰 시트(본문 312 ~ 342) 6열 · 팝오버(본문 360) 7열. 남는 폭은 칸 사이에 고르게 나눈다
칸 사이 최소spacing-x1 4px칸 사이 최소 4 — 남는 폭은 칸 사이에 고르게
줄 간격spacing-x1 4px줄 사이 4
찾기 칸 ↔ 격자spacing-x3 12px찾는 동안(묶음 머리 없음) 격자 위 12 — 묶음 머리 위 여백(12)과 같다. 스크롤 상자의 흐림 여백(위 20)이 그 바깥에 더해져 찾기 칸 ↔ 격자는 32, 묶음으로 볼 때 찾기 칸 ↔ 첫 머리 글도 32 로 두 보기가 같다
키보드← → · ↑ ↓ · Home · End · Enter · Space← → 는 차례대로(줄 끝에서 다음 줄 · 다음 묶음으로), ↑ ↓ 는 보이는 위아래 줄의 가장 가까운 칸(묶음을 넘는다), Home · End 는 그 줄의 처음 · 끝, Enter · Space 로 고른다. 찾기 칸에서 ↓ 는 격자의 첫 칸으로. Esc 는 찾는 말이 있으면 먼저 지우고(초점은 찾기 칸), 없으면 닫는다

칸

속성값비고
크기48px
모서리radius-r3 12px
배경transparent
커서pointer
터치 영역48 × 48AA ✓ · AAA ✓ — SEED Iconography "24px 크기에서는 44px 이상"
이름한국어 이름"커피" — 영어 lucide 이름을 이름으로 두지 않는다. 마우스는 같은 글의 툴팁
역할option

칸 아이콘

속성값비고
아이콘24pxlucide 기본 격자 — v106 의 24
선 굵기2pxlucide 기본
색color-fg-neutral #1A1F2E · 다크 #F5F6FA시트 · 팝오버 위 16.41 · 다크 11.62

묶음 머리

속성값비고
글자text-t4 14px / 19pxList Header mediumWeak — 14 · 500
굵기500
글자·아이콘color-fg-neutral-subtle #62697A · 다크 #A2A8B7시트 · 팝오버 위 5.50 · 다크 5.27
위 여백spacing-x3 12px
아래 여백spacing-x2 8px

고른 표시

지금 값의 칸은 안쪽에 2px 짙은 테두리(stroke-neutral-contrast)를 덧그리고 아이콘을 선 2.5 로 둔다 — Select Box 의 고른 상자(v113)와 같은 말이다(사용자 결정 17A). 바탕은 칠하지 않아 아이콘의 모양 · 색이 그대로 보인다(반전 · 브랜드 채움은 고르지 않았다). 열면 고른 칸이 보이게 스크롤한다.

라이트커피를 골랐다 — 바탕은 칠하지 않는다(시트 · 팝오버 면 그대로)
다크커피를 골랐다 — 바탕은 칠하지 않는다(시트 · 팝오버 면 그대로)
고른 칸 — 안쪽 2px 짙은 테두리 · 선 2.5, 라이트 · 다크

고름

selectedcell 테두리cell 테두리 두께icon 선 굵기설명
unselected (기본)———안 고름
selectedcolor-stroke-neutral-contrast #1A1F2E · 다크 #F5F6FA — 시트 · 팝오버 위 16.41 · 다크 11.622px — 안쪽에 덧그린다 — 아이콘이 밀리지 않는다(Select Box v113 의 고른 테두리와 같다). 바탕은 칠하지 않는다(반전 · 브랜드 채움은 고르지 않았다 — 17B)2.5px — v106 — 선택은 선 2.5고름 — 지금 값. 안쪽 2px 짙은 테두리 + 선 2.5

찾기

찾기 칸은 Input 밑줄형이다 — 시트 large 40 · 팝오버 medium 34, 앞 돋보기 · 지우기, placeholder "아이콘 이름 검색", 이름 "아이콘 검색". 치는 대로 세트를 거른다 — 찾는 말과 이름 · 찾는 말 · lucide 이름을 소문자로 바꾸고 공백을 뺀 뒤 부분 일치다("커피" → 커피 · 원두, "월세" → 집, "coffee" → 커피). 결과는 세트 차례 그대로 묶음 머리 없이 늘어놓고, 개수는 화면 밖으로 알린다("검색 결과 2개"). 0건이면 격자 자리에 Result Section medium — "'{검색어}'에 대한 아이콘이 없어요" 다(사용자 결정 16A).

아이콘
커피
“커피”검색 결과 2개 — 치기를 멈추면 화면 밖으로 한 번 읽는다. 찾기 칸 ↔ 격자 32
아이콘
월세
“월세”검색 결과 1개 — 치기를 멈추면 화면 밖으로 한 번 읽는다. 찾기 칸 ↔ 격자 32
아이콘
유니콘
'유니콘'에 대한 아이콘이 없어요

다른 말로 찾거나 묶음에서 골라주세요.

“유니콘”Result Section medium — role="status"
"커피" → 커피 · 원두 · "월세" → 집 · "유니콘" → 결과 없음

찾기 칸

속성값비고
모양underlineInput 밑줄형 — 시트 · 팝오버에 입력이 하나뿐인 목록 위 찾기(Searchable List 와 같다). 앞 돋보기 · 지우기 · placeholder "아이콘 이름 검색" · 이름 "아이콘 검색". 목록이 스크롤해도 위에 붙는다
찾는 방법이름 · 찾는 말 · id찾는 말과 이름 · 찾는 말 · lucide 이름을 소문자로 바꾸고 공백을 뺀 뒤 부분 일치("커피" → 커피 · 원두, "월세" → 집). 들고 있는 표라 치는 대로 바로 거른다 — 결과는 세트 차례 그대로, 묶음 머리 없이

결과 수

속성값비고
보이기화면 밖aria-live="polite" — 치기를 멈추고 500ms 뒤 "검색 결과 2개" 를 한 번 읽는다. 0건은 Result Section 이 알린다

결과 없음

속성값비고
크기Result Section medium격자 자리 가운데 — 제목 "'{검색어}'에 대한 아이콘이 없어요" · 설명 "다른 말로 찾거나 묶음에서 골라주세요." · role="status"

State

상태모습
enabled칸 바탕 없음
hovered웹 — 누름과 같은 바탕 bg-layer-floating-pressed(v106)
focused키보드 포커스 — 칸 바깥 링 2px · 띄움 2px. 격자에 Tab 하나(고른 칸, 없으면 첫 칸)
pressed같은 바탕 + 칸 2px 거리 축소(v104). 손을 떼면 고르고 닫힌다

트리거의 상태(누름 · 오류 · 막힘 · 읽기 전용)는 Input Button 그대로다.

안 고름고름
기본
호버(웹)
포커스(키보드)
누름
라이트호버 = 누름 바탕(bg-layer-floating-pressed) · 누름은 2px 축소 · 포커스 링은 칸 바깥
안 고름고름
기본
호버(웹)
포커스(키보드)
누름
다크호버 = 누름 바탕(bg-layer-floating-pressed) · 누름은 2px 축소 · 포커스 링은 칸 바깥
상태 — 기본 · 호버 · 포커스 · 누름 × 안 고름 · 고름

상태 — 안 고름

상태cell 배경cell 배율cell 축소 시간cell 축소 곡선링 두께링 간격링 색
enabled 기본transparent——————
hovered 웹 — 누름과 같은 바탕(v106)color-bg-layer-floating-pressed #F7F8FD · 다크 #353B4D — 시트 · 팝오버(bg-layer-floating) 위의 누름 색 — Menu 와 같다——————
focused 키보드 포커스 — 칸 바깥 링(roving — 격자에 Tab 하나)transparent———2px2px — 칸 바깥 — 키보드 포커스에만color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
pressed 바탕 + 칸 2px 거리 축소(v104) — 손을 떼면 고르고 닫힌다color-bg-layer-floating-pressed #F7F8FD · 다크 #353B4D칸 2px 거리 축소 — 기준 48 — Feedback 의 눌림 피드백(v104). 모션 줄이기면 하지 않는다motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)———

상태 — 고름

상태cell 배경cell 테두리cell 테두리 두께cell 배율cell 축소 시간cell 축소 곡선icon 선 굵기링 두께링 간격링 색
enabled 기본transparentcolor-stroke-neutral-contrast #1A1F2E · 다크 #F5F6FA — 시트 · 팝오버 위 16.41 · 다크 11.622px — 안쪽에 덧그린다 — 아이콘이 밀리지 않는다(Select Box v113 의 고른 테두리와 같다). 바탕은 칠하지 않는다(반전 · 브랜드 채움은 고르지 않았다 — 17B)———2.5px — v106 — 선택은 선 2.5———
hovered 웹 — 누름과 같은 바탕(v106)color-bg-layer-floating-pressed #F7F8FD · 다크 #353B4D — 시트 · 팝오버(bg-layer-floating) 위의 누름 색 — Menu 와 같다color-stroke-neutral-contrast #1A1F2E · 다크 #F5F6FA — 시트 · 팝오버 위 16.41 · 다크 11.622px — 안쪽에 덧그린다 — 아이콘이 밀리지 않는다(Select Box v113 의 고른 테두리와 같다). 바탕은 칠하지 않는다(반전 · 브랜드 채움은 고르지 않았다 — 17B)———2.5px — v106 — 선택은 선 2.5———
focused 키보드 포커스 — 칸 바깥 링(roving — 격자에 Tab 하나)transparentcolor-stroke-neutral-contrast #1A1F2E · 다크 #F5F6FA — 시트 · 팝오버 위 16.41 · 다크 11.622px — 안쪽에 덧그린다 — 아이콘이 밀리지 않는다(Select Box v113 의 고른 테두리와 같다). 바탕은 칠하지 않는다(반전 · 브랜드 채움은 고르지 않았다 — 17B)———2.5px — v106 — 선택은 선 2.52px2px — 칸 바깥 — 키보드 포커스에만color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA
pressed 바탕 + 칸 2px 거리 축소(v104) — 손을 떼면 고르고 닫힌다color-bg-layer-floating-pressed #F7F8FD · 다크 #353B4Dcolor-stroke-neutral-contrast #1A1F2E · 다크 #F5F6FA — 시트 · 팝오버 위 16.41 · 다크 11.622px — 안쪽에 덧그린다 — 아이콘이 밀리지 않는다(Select Box v113 의 고른 테두리와 같다). 바탕은 칠하지 않는다(반전 · 브랜드 채움은 고르지 않았다 — 17B)칸 2px 거리 축소 — 기준 48 — Feedback 의 눌림 피드백(v104). 모션 줄이기면 하지 않는다motion-duration-pressed-scale 150msmotion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1)2.5px — v106 — 선택은 선 2.5———

모션

전환시간곡선대상비고
호버 · 누름 — 바탕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 거리 축소 — 모션 줄이기면 하지 않는다

Guidelines

고른 세트만 연다

lucide 전체를 열지 않는다 — 첫 화면이 "a-arrow-down …" 이 되고, 2,000개는 사실상 찾기로만 고른다(사용자 결정 15A — 전체 + 추천 묶음 · 지금 전체는 고르지 않았다). 세트에 없는 아이콘이 필요하면 category-icons.yaml 에 묶음 · 이름 · 찾는 말과 함께 더한다 — 웹 · 앱 둘 다에 있는 lucide 이름이어야 한다.

아이콘
아이콘 이름 검색
이렇게 — 고른 세트(148개 · 13 묶음)만 — 첫 화면이 식비 · 카페 … 로 열려 찾지 않고도 고른다
아이콘
아이콘 이름 검색
a-arrow-down · a-arrow-up · a-large-small … 2,007개
이렇게 하지 않는다 — lucide 전체(2,007) — 첫 화면이 'a-arrow-down …' 이라 사실상 찾기로만 고른다(지금 웹 · 앱)
세트 첫 화면(식비 · 카페 …) · 전체 첫 화면(a-arrow-down …)

한국어로 찾는다

이름은 한국어 하나("커피"), 찾는 말은 사람들이 그 아이콘을 부를 말("카페 · 아메리카노 · 라떼")이다. 이름은 칸의 이름(보조 기술 · 툴팁)과 트리거의 값 글로 쓴다 — 영어 lucide 이름("coffee")을 화면에 내지 않는다. 이름 · 찾는 말은 번역 문구에 둔다.

화면 읽기커피, 선택됨, 3개 중 1번째

이렇게 — 칸 이름은 한국어 이름 — '커피, 선택됨'. 트리거 값도 같은 글

화면 읽기a-arrow-down, 버튼

이렇게 하지 않는다 — 영어 lucide 이름을 이름으로 — 'a-arrow-down' 이 그대로 읽히고 툴팁에 뜬다(지금 웹)
칸 이름 — "커피" · 지금의 "a-arrow-down"

기본은 태그 — "없음" 은 두지 않는다

"없음" 을 고르는 칸을 두지 않는다 — 아이콘이 없는 카테고리는 어차피 태그 아이콘 하나로 그린다(2026-10-04 List 결정). 새 항목은 기본 아이콘을 골라 둔 채 연다 — 카테고리는 태그(tag), 저축 목표처럼 쓰는 자리에 맞는 기본이 있으면(지금 돼지저금통) 그것. 이미 "" · null 로 저장된 항목은 태그로 그리고, 트리거에도 태그 + "태그" 로 보인다.

세트 밖 아이콘은 그대로

세트를 두기 전에 저장된 세트 밖 아이콘은 지우지도 바꾸지도 않는다 — 그대로 그린다(웹 DynamicIcon · 앱 lucideByName). 트리거는 그 아이콘 + "지금 아이콘" 이고 격자에는 고른 칸이 없다. 격자에서 고르면 그 아이콘으로 바뀌고, 고르지 않고 닫으면 그대로다. 서버가 심은 home 은 세트의 house(집)와 같은 아이콘이라 집을 고른 것으로 보인다(lucideAliases).

키보드 — 2차원

격자는 칸마다 Tab 이 서지 않는다 — 격자에 Tab 하나(고른 칸, 없으면 첫 칸)로 들어와 화살표로 옮긴다. ← → 는 차례대로(줄 끝에서 다음 줄 · 다음 묶음으로), ↑ ↓ 는 보이는 위아래 줄의 가장 가까운 칸(묶음 머리를 넘는다), Home · End 는 그 줄의 처음 · 끝, Enter · Space 로 고른다. 찾기 칸에서 ↓ 는 격자의 첫 칸으로 간다. 열면 초점은 고른 칸(없으면 첫 칸)이다 — 폰에서 키보드가 올라와 격자를 가리지 않게, 찾기 칸은 눌러야 친다.

카페
교통

화면 읽기격자에 Tab 하나 — 화살표로 옮기고 Enter · Space 로 고른다

언제 쓰나

이런 자리쓰는 것
카테고리 · 저축 목표에 아이콘을 붙인다Icon Picker
아이콘을 보이기만 한다List 의 카테고리 타일
색을 붙인다Color Swatch

글

라벨 "아이콘", 시트 제목 "아이콘", 찾기 칸 placeholder "아이콘 이름 검색"(마침표 없이), 결과 없음 제목 "'{검색어}'에 대한 아이콘이 없어요" · 설명 "다른 말로 찾거나 묶음에서 골라주세요.". 결과 수는 "검색 결과 2개" 로 읽힌다.

코드

레시피 recipes/shadcn/components/ui/icon-picker.tsx 를 Field 안에 둔다 — 트리거는 레시피 input-button.tsx(InputButton · useInputButtonSurface), 시트 · 팝오버는 bottom-sheet.tsx · popover.tsx, 찾기 칸은 input.tsx 다. 세트는 recipes/shadcn/lib/category-icons.ts — category-icons.yaml 에서 scripts/gen-category-icons.mjs 가 만든 표(npm run gen:category-icons — institution-colors 와 같은 틀, npm run verify 의 check:category-icons 가 어긋나면 멈춘다)와 찾기 함수다. 세트 밖에 저장된 아이콘은 lucide 의 DynamicIcon 으로 그리고, lucide 에도 없는 이름이면 기본 태그로 그린다. 아래 미리보기는 스펙 값으로 그린 모습이다.

  • IconPicker — 트리거 + 여는 자리 + 찾기 + 격자. value(저장된 lucide 이름 — null · "" 이면 태그) · onValueChange(id)(고르면 — 닫는 것은 스스로 한다) · size(트리거 — "responsive" 기본 · "large" · "medium") · disabled · id. Field 안이면 라벨이 이름이 되고 오류 · 막힘을 받는다(Input Button). 2차원 화살표 · 결과 수 알림 · 결과 없음 · 고른 칸 스크롤을 스스로 한다.
  • category-icons.ts — CATEGORY_ICONS(세트 — { id, group, name, aliases, lucideAliases? }) · CATEGORY_ICON_GROUPS(묶음 차례) · DEFAULT_CATEGORY_ICON("tag") · findCategoryIcon(id)(id 또는 lucideAliases 로 찾는다 — 세트 밖이면 null) · searchCategoryIcons(query)(이름 · 찾는 말 · id 부분 일치, 세트 차례).

카테고리 — 아이콘

태그

화면 읽기눌러서 열고 고르면 바로 닫혀요 — "완료" 가 없어요.

import { useState } from "react"
import { Field } from "@/components/ui/field"
import { IconPicker } from "@/components/ui/icon-picker"
import { DEFAULT_CATEGORY_ICON } from "@/lib/category-icons"

const [icon, setIcon] = useState<string>(category?.icon || DEFAULT_CATEGORY_ICON) // 새 카테고리는 태그로 시작

<Field label="아이콘">
  <IconPicker value={icon} onValueChange={setIcon} />
</Field>

이름 찾기 · 세트 밖 아이콘

findCategoryIcon("coffee")
커피
findCategoryIcon("home") — 서버 시드의 옛 이름
집
findCategoryIcon("a-arrow-down") → null
지금 아이콘
"" · null — 아이콘이 없는 항목
태그
트리거 값 — 세트 안 "커피" · 세트 밖 "지금 아이콘"
import { findCategoryIcon, searchCategoryIcons } from "@/lib/category-icons"

findCategoryIcon("coffee")?.name // "커피"
findCategoryIcon("home")?.id // "house" — 서버 시드의 옛 이름
findCategoryIcon("a-arrow-down") // null — 세트 밖, 트리거는 "지금 아이콘"
searchCategoryIcons("월세").map((it) => it.name) // ["집"]

Behavior

인터랙션동작
트리거 누르기 · Enter · Space1280 미만 시트 · 이상 팝오버를 연다. 초점은 고른 칸(없으면 첫 칸), 고른 칸이 보이게 스크롤
칸 누르기 · Enter · Space그 아이콘을 고르고(onValueChange) 닫는다 — 초점은 트리거로
← → · ↑ ↓ · Home · End격자 안에서 옮긴다(고르지 않는다) — 2차원, 묶음을 넘는다
Tab격자에 한 번 — 찾기 칸 ↔ 격자 ↔ (시트) 닫기
찾기 칸에 치기치는 대로 거른다 — 결과 수를 한 번 알린다, 0건은 Result Section
찾기 칸에서 ↓격자의 첫 칸으로
Esc찾는 말이 있으면 지운다 · 비었으면 닫는다(값은 그대로) — 초점은 트리거로
바깥 누르기 · 끌어내리기닫는다 — 값은 그대로

Accessibility

기준검증
WCAG 1.4.3 Color contrast(글자 ≥ 4.5:1)묶음 머리 fg-neutral-subtle 시트 · 팝오버 위 5.50 · 다크 5.27 ✓. 트리거 · 찾기 칸은 Input Button · Input 의 검증
WCAG 1.4.11 Non-text contrast(≥ 3:1)칸 아이콘 fg-neutral 16.41 · 다크 11.62, 고른 테두리 stroke-neutral-contrast 16.41 · 11.62 ✓. 호버 · 누름 바탕은 장식(고름은 테두리 · 굵기로)
WCAG 1.4.1 Use of color고른 칸은 테두리 · 선 굵기로 알린다 — 색만이 아니다 ✓
WCAG 2.1.1 Keyboard격자 2차원 화살표 · Enter · Space · Esc ✓ — 칸마다 Tab 이 서지 않는다
WCAG 4.1.3 Status messages결과 수 · 결과 없음을 aria-live="polite" · role="status" 로 한 번 ✓
WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24)칸 48 ✓
WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44)칸 48 ✓ · 트리거 large 52 ✓(medium 40 은 1280 이상 데스크톱 웹만)
ARIA트리거 <button> · aria-haspopup="dialog" · aria-expanded · 이름 "아이콘, 커피"(Field 라벨 + 값). 시트 · 팝오버 role="dialog" · 이름 "아이콘". 격자 role="listbox" · 이름 "아이콘", 묶음 role="group" · aria-labelledby(머리), 칸 role="option" · aria-selected · 이름 = 한국어 이름 · roving tabindex. 칸 아이콘은 aria-hidden. 앱은 칸마다 Semantics(selected: …, label: "커피", button: true) + 키보드 포커스

Do / Don't

✅ Do

  • 고른 세트(category-icons.yaml)만 묶음으로 연다.
  • 한국어 이름 · 찾는 말로 찾고, 칸 이름도 한국어로.
  • 칸 48 · 아이콘 24, 고른 칸은 안쪽 2px 짙은 테두리.
  • 트리거는 Input Button — 지금 아이콘 + 그 이름.
  • 1280 미만은 시트, 이상은 팝오버 — 고르면 바로 닫는다.
  • 격자는 2차원 화살표, 결과 수를 알린다.

❌ Don't

  • lucide 전체를 열기 · 영어 이름으로만 찾기.
  • "없음" 칸 · 빈 아이콘 저장.
  • 칸 32 · 아이콘 16 · 고른 칸을 반전 · 브랜드 색으로.
  • 칸마다 Tab · 폰에서 팝오버.
  • 세트 밖으로 저장된 아이콘을 몰래 바꾸기.
  • 트리거에 아이콘만 두고 이름을 빼기.

Specification

icon-picker.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 Icon Picker 를 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다. 세트는 category-icons.yaml, 트리거 · 시트 · 팝오버 · 찾기 칸의 나머지 값은 각 스펙의 YAML 이다.

Base

부위상태속성값
triggerenabled컴포넌트componentInput Buttoninput-button.yaml — 크기 반응형(large 52 · medium 40). 라벨 "아이콘"(Field), 앞 아이콘 = 지금 아이콘(large 20 · medium 16 · fg-neutral-muted), 값 = 아이콘의 한국어 이름("커피"), 뒤 chevron-down. 이름은 "아이콘, 커피"(라벨 + 값). 아이콘이 없는 항목은 태그 + "태그", 세트 밖 저장 아이콘은 그 아이콘 + "지금 아이콘"
searchenabled모양variantunderlineInput 밑줄형 — 시트 · 팝오버에 입력이 하나뿐인 목록 위 찾기(Searchable List 와 같다). 앞 돋보기 · 지우기 · placeholder "아이콘 이름 검색" · 이름 "아이콘 검색". 목록이 스크롤해도 위에 붙는다
찾는 방법match이름 · 찾는 말 · id찾는 말과 이름 · 찾는 말 · lucide 이름을 소문자로 바꾸고 공백을 뺀 뒤 부분 일치("커피" → 커피 · 원두, "월세" → 집). 들고 있는 표라 치는 대로 바로 거른다 — 결과는 세트 차례 그대로, 묶음 머리 없이
groupHeaderenabled위 여백paddingTopspacing-x312px
아래 여백paddingBottomspacing-x28px
글자·아이콘foregroundfg-neutral-subtle#62697A다크 #A2A8B7시트 · 팝오버 위 5.50 · 다크 5.27
글자typographytext-t414px / 19pxList Header mediumWeak — 14 · 500
굵기fontWeight500
gridenabled줄 간격rowGapspacing-x14px줄 사이 4
칸 수columns들어가는 만큼칸 48 · 사이 최소 4 로 들어가는 열 — 폰 시트(본문 312 ~ 342) 6열 · 팝오버(본문 360) 7열. 남는 폭은 칸 사이에 고르게 나눈다
역할rolelistbox이름 "아이콘". 묶음은 role="group" + 머리로 이름(aria-labelledby). 격자에 Tab 하나(고른 칸, 없으면 첫 칸)
키보드keyboard← → · ↑ ↓ · Home · End · Enter · Space← → 는 차례대로(줄 끝에서 다음 줄 · 다음 묶음으로), ↑ ↓ 는 보이는 위아래 줄의 가장 가까운 칸(묶음을 넘는다), Home · End 는 그 줄의 처음 · 끝, Enter · Space 로 고른다. 찾기 칸에서 ↓ 는 격자의 첫 칸으로. Esc 는 찾는 말이 있으면 먼저 지우고(초점은 찾기 칸), 없으면 닫는다
칸 사이 최소columnGapMinspacing-x14px칸 사이 최소 4 — 남는 폭은 칸 사이에 고르게
찾기 칸 ↔ 격자searchGapspacing-x312px찾는 동안(묶음 머리 없음) 격자 위 12 — 묶음 머리 위 여백(12)과 같다. 스크롤 상자의 흐림 여백(위 20)이 그 바깥에 더해져 찾기 칸 ↔ 격자는 32, 묶음으로 볼 때 찾기 칸 ↔ 첫 머리 글도 32 로 두 보기가 같다
cellenabled크기size48px
모서리radiusradius-r312px
배경backgroundtransparent
커서cursorpointer
터치 영역touchTarget48 × 48AA ✓ · AAA ✓ — SEED Iconography "24px 크기에서는 44px 이상"
역할roleoption
이름name한국어 이름"커피" — 영어 lucide 이름을 이름으로 두지 않는다. 마우스는 같은 글의 툴팁
iconenabled아이콘size24pxlucide 기본 격자 — v106 의 24
색colorfg-neutral#1A1F2E다크 #F5F6FA시트 · 팝오버 위 16.41 · 다크 11.62
선 굵기strokeWidth2pxlucide 기본
countenabled보이기display화면 밖aria-live="polite" — 치기를 멈추고 500ms 뒤 "검색 결과 2개" 를 한 번 읽는다. 0건은 Result Section 이 알린다
emptyenabled크기sizeResult Section medium격자 자리 가운데 — 제목 "'{검색어}'에 대한 아이콘이 없어요" · 설명 "다른 말로 찾거나 묶음에서 골라주세요." · role="status"
cellhovered배경backgroundbg-layer-floating-pressed#F7F8FD다크 #353B4D시트 · 팝오버(bg-layer-floating) 위의 누름 색 — Menu 와 같다
focusRingfocused링 두께width2px
링 색colorstroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84
링 간격offset2px칸 바깥 — 키보드 포커스에만
cellpressed배경backgroundbg-layer-floating-pressed#F7F8FD다크 #353B4D
배율scale칸 2px 거리 축소기준 48 — Feedback 의 눌림 피드백(v104). 모션 줄이기면 하지 않는다
축소 시간scaleDurationmotion-duration-pressed-scale150ms
축소 곡선scaleEasingmotion-ease-pressed-scalecubic-bezier(0, 0, 0.15, 1)

selected=selected

부위상태속성값
cellenabled테두리borderColorstroke-neutral-contrast#1A1F2E다크 #F5F6FA시트 · 팝오버 위 16.41 · 다크 11.62
테두리 두께borderWidth2px안쪽에 덧그린다 — 아이콘이 밀리지 않는다(Select Box v113 의 고른 테두리와 같다). 바탕은 칠하지 않는다(반전 · 브랜드 채움은 고르지 않았다 — 17B)
iconenabled선 굵기strokeWidth2.5pxv106 — 선택은 선 2.5

surface=sheet

부위상태속성값
surfaceenabled좌우 여백paddingXspacing-x624px본문 좌우 24(Bottom Sheet)
컴포넌트componentBottom Sheetbottom-sheet.yaml — 제목 "아이콘" · 닫기. 고르면 닫힌다("완료" 없음)
searchenabled높이height40px밑줄형 large — 글 18 / 24 · 돋보기 24
scrollenabled끝 흐림scrollFog켬격자가 넘친다 — Bottom Sheet 본문의 끝 흐림(Scroll Fog overlayBody, 위 20 · 아래 80 늘 켜짐 · 2026-10-03 규칙). 찾기 칸은 스크롤 상자 밖이라 흐리지 않는다
gridenabled칸 수columns6폰 본문 312 ~ 342 — 48 × 6 + 4 × 5 = 308 이 들어간다. 본문이 308 보다 좁으면(320 폰) 들어가는 만큼(5열), 태블릿 시트(432)도 6열

surface=popover

부위상태속성값
surfaceenabled너비width408px칸 48 × 7 + 사이 4 × 6 = 360 + 좌우 24 × 2 — Popover 폭 320 ~ 480 안
좌우 여백paddingXspacing-x624px본문 좌우 24
컴포넌트componentPopoverpopover.yaml — 머리 없이(고르는 패널) · 이름 aria-label "아이콘" · 트리거 아래 8 · 최대 높이 600
searchenabled높이height34px밑줄형 medium — 글 16 / 22 · 돋보기 20
scrollenabled끝 흐림scrollFog켬격자가 넘친다 — Popover 본문의 끝 흐림(Scroll Fog overlayBody, 위 20 · 아래 80 늘 켜짐 · 2026-10-03 규칙). 찾기 칸은 스크롤 상자 밖
gridenabled칸 수columns7본문 360 = 48 × 7 + 4 × 6

SEED 와 다른 점

  • groo 가 정한 컴포넌트다 — SEED 에는 아이콘 고르기 부품 · 화면이 없다(카테고리를 고르는 SEED 예는 글자를 치는 시트다). 세트를 두는 것은 SEED Iconography 의 "현재 제작된 범위 내에서" 를 따랐다.
  • 칸 48 · 고른 칸 안쪽 2px 짙은 테두리 — SEED 문서 사이트의 Icon Library 는 칸 ≈ 60 · 사이 16 · 1px 테두리이고 고른 칸을 bg.brand-weak 틴트(흰 바탕과 1.10:1)로만 보인다. 아이콘 24 는 같다.
  • 2차원 화살표 · 격자에 Tab 하나 — SEED 문서 사이트 격자는 칸마다 Tab(652개)이고 화살표가 없다.
  • 한국어 이름 · 찾는 말 — SEED Icon Library 도 이름 · 키워드(한글 포함)로 찾는다. groo 는 칸의 이름까지 한국어다(SEED 는 icon_a_uppercase_outline_fill 같은 이름).
  • 결과 수를 알린다 — SEED Icon Library 는 설명 글("{검색어}로 검색한 결과입니다.")만 바꾼다.

Migration notes

2026-10-09 — 고른 세트 · 한국어 찾기 · Input Button 으로 다시 쓴다

사용자가 입력 비교 페이지에서 정했다 — 고른 세트(15A — 카테고리용 150개 안팎을 묶음으로, 비교 페이지의 148개 · 13 묶음이 초안), 한국어 이름 + 찾는 말 · 없으면 "'{검색어}'에 대한 아이콘이 없어요"(16A), 칸 48 · 아이콘 24 · 고른 칸 안쪽 2px 짙은 테두리 · 트리거는 Input Button(17A). 그리고 "따라오는 것" — "없음" 을 걷고 기본은 태그(2026-10-04) · 2차원 roving · 결과 수 알림 · 칸 이름 한국어 · 빈 결과 해요체 · 트리거 이름 "아이콘, 커피". 전체 + 추천(15B) · 지금 전체(15C) · 묶음만(16B) · 영어(16C) · 반전(17B) · 지금 모양(17C)은 고르지 않았다. 초안에서 다듬은 것 — 하트의 이름을 기본 카테고리 "건강" 에 맞추고(옛 "마음" · 맥박은 "활동"), 서류 가방을 "회사"(옛 "일"), 수영의 lucide 이름을 waves-horizontal(옛 waves 는 lucideAliases), 집에 서버 시드의 home 을 lucideAliases 로 더했다. 옛 스펙은 icon-picker.history/v-pre-seed-input.* 에 남겼다.

옛 Icon Picker새 Icon Picker
트리거 40 정사각(sm 32 · lg 48) · 아이콘 18 · 없으면 "—"Input Button — 라벨 "아이콘" · 지금 아이콘 · 이름 글, large 52 · medium 40
늘 팝오버 320(sm 288 · lg 384)1280 미만 Bottom Sheet · 이상 Popover 408
lucide 2,000+ · 영어 이름 부분 일치 · 100개까지 + "총 N개 중 100개 표시"세트 148개 · 13 묶음 · 한국어 이름 · 찾는 말 · 결과 수 알림 · 0건 Result Section
8열 · 칸 32 · 사이 4 · 최대 높이 240 · 고른 칸 primary 채움칸 48 · 아이콘 24 · 들어가는 만큼(6 · 7열) · 고른 칸 안쪽 2px stroke-neutral-contrast
칸마다 Tab · 이름 = 영어 id격자에 Tab 하나 · 2차원 화살표 · 이름 = 한국어
"없음" 줄걷음 — 기본 태그

제품은 앱 적용 단계에서 옮긴다(2026-10-09 조사 — Desk 웹은 크로미움에 띄워 쟀고, Desk 앱은 위젯 테스트로 쟀다). 아이콘 고르기는 웹 2곳(shared/ui/icon-picker.tsx) · 앱 2곳(shared/widgets/p_icon_picker.dart) — 카테고리 · 저축 목표다. HR · SSO 에는 없다.

  • 자리 — 웹 카테고리(widgets/category-manage/ui/CategoryEditDialog.tsx:318-321 — "없음" 이면 "" 저장, 새 카테고리 기본 "tag" :73) · 저축 목표(widgets/asset-full/ui/SavingGoalAddDialog.tsx:423-428 — "없음" 이면 "piggy-bank"). 앱 카테고리(features/category/presentation/category_edit_dialog.dart:481 — "" 저장 · 표시는 태그) · 저축 목표(features/saving_goal/presentation/saving_goal_edit_dialog.dart:297-301 — "piggy-bank").
  • 영어로만 찾는다 — name.includes(query)(icon-picker.tsx:49-59)라 "커피" 0건 · "coffee" 1건이다. 칸 이름은 title 의 영어 id("a-arrow-down"), 트리거 이름은 0(낭독 "button"), 2,007칸이 모두 Tab 정지점이라 200번 눌러도 팝오버를 못 나간다(:113-128 · 168-185, D8). 앱도 "커피" 0건이고 칸 · 트리거 이름 · 고른 상태가 0 이다(p_icon_picker.dart:34-50 · 166-190, D7).
  • 폰에서도 팝오버 — 웹은 390 에서도 팝오버 320 이라 서랍 안에서 위로 열려 폼을 덮는다(icon-picker.tsx:130, D16). 앱은 시트 "아이콘 선택"(8열 × 41.3 · 아이콘 16)이다.
  • 트리거 — 웹은 Field 의 [&>*]:w-full(shared/ui/field.tsx:60) 때문에 폭 전체 × 40 에 아이콘 18 만 있고 바탕이 bg-page(대화상자와 1.08)다. 앱은 40 정사각 · isButton 0. Input Button 으로.
  • 격자 · 고른 칸 — 웹 8열 · 칸 32 · 아이콘 16 · 처음 120개, 스크롤마다 +120(2,007) · 고른 칸 회색 + 파란 고리, 바닥 10px "전체 2007개 · 스크롤해서 더 보기". 앱 8열 × 41.3 · 1,977개 · 고른 칸 브랜드 옅은 바탕 + 1.5px 테두리. 빈 결과 "검색 결과가 없습니다"(합니다체 — D26).
  • 웹 · 앱이 다른 아이콘 수 — 웹 lucide-react 1.28.0 은 2,007 · 앱은 1,977 이라 웹에만 있는 13개(circle-euro … user-shield)는 앱에서 태그로 떨어지고, 앱 주석은 "1,100+" 다(p_icon_picker.dart:14, S7). 세트는 둘 다에 있는 이름만 쓴다.
  • 아이콘이 없을 때 그리기가 다섯 — 빈 칸(widgets/category-manage/ui/CategoryManager.tsx:781 → shared/ui/groo/primitives.tsx:76-82 — ?? "tag" 가 "" 를 못 잡는다) · 첫 글자(shared/ui/category-tile.tsx:78) · 태그 · "?" · "—", 크기 11 ~ 20(D22). List 의 결정(태그 하나 · 20)대로 — findCategoryIcon 이 null 이고 저장 값이 비었으면 태그다.

이전 기록

  • 2026-10-01 — 검색칸을 Input 의 앞 아이콘으로. 아이콘을 절대 위치로 겹쳐 그리던 검색칸을 Input prefixIcon · clearable · aria-label="아이콘 검색" 으로 바꿨다(제품은 그대로였다). 트리거를 Input Button 으로 옮기는 일은 이번에 했다.
  • 2026-05 — 처음 둠. desk-front shared/ui/icon-picker.tsx 를 그대로 끌어올렸다(lucide-react/dynamic 의 iconNames · DynamicIcon · 100개 제한).

이 페이지