Skip to content

Form controls (Input · Select · Picker)

진행 중 (Form controls — work in progress)

  • 텍스트 필드(input/textarea)와 드롭다운(select)의 공유 사이즈 체계. PR #247에서 기본 높이 40→32px 통일 + ds-input--XX/ds-select--XX 스케일 정리.
  • !important 없이 사이즈가 적용됨 — 기본 규칙이 --XX 모디파이어를 :not()으로 제외해 모디파이어가 자연 specificity로 이김. (강제 최소화 설계)
  • ⚠️ --ds-textfield-*/--ds-dropdown-* 토큰이 raw hex로 정의검토 필요 사항.
규칙 (강제) · 코딩 에이전트용 — 사람은 접힌 채로 두어도 됩니다

Input · Select · 필드 구성의 강제 규칙. 상세·토큰·피커는 아래 본문.

  • 기본 32px. 사이즈 클래스를 안 주면 input/select/박스버튼은 medium 32px가 자동(이전 40→32). 요소 규칙이라 클래스 없이도 전 서비스 적용됨.
  • 사이즈는 ds-input--XX/ds-select--XX 스케일(52/48/40/32/28/24)로만. input·select가 같은 스케일이라 나란히 두면 높이가 flush. 크기(height/font/padding)는 native 규칙 + --XX 모디파이어가 담당하고 Tailwind는 마진·배치 유틸로만 — 기본 규칙이 :not([class*="ds-input--"])로 모디파이어를 제외해 --XX!important 없이 이김.
  • 필드 구성 스택: 라벨 → 필수 * → 입력칸 → 설명 → 에러(위→아래). 필수 = 라벨 끝 <span class="text-red-400">*</span> + HTML required 병행.
  • 설명 = .ds-form-help (12px·입력칸 아래 6px·항상 표시). 색은 필드 상태 자동 추종(rest muted → focus black → error red), sibling 조합자로 동작하므로 입력칸과 같은 부모의 뒤쪽 형제여야 함(JS 토글 불필요). 앱 로컬 text-b6 neutral-400/500 재조합 금지.
  • 에러 = .ds-form-error (danger red·12px·아래 6px·x-show 토글), 점프 방지 정렬 폼은 .ds-form-error-slot(min-height 17px). 입력칸 보더는 공유 ds-input--error/ds-select--error 표준(admin 로컬 .is-error 금지).
  • 숫자 = ds-input--number — 스피너를 평소 숨기고 hover/focus 시에만 노출 + tabular-nums. 숫자 처리만 담당하므로 크기(ds-input--XX)·너비(w-full)·정렬(text-center)과 조합.
  • 상태 토큰: hover border #c1c1c1 · focus border #1b1b1b(select은 ring) · disabled opacity:.45+not-allowed · error border --ds-color-danger. 이 클래스/토큰들은 공유 design-system.css가 단일 출처(앱 로컬 복제 금지).
  • 체크박스 = ds-checkbox, 라디오 = ds-radio (한 세트 — 값은 같고 모양만 사각/원형). 클래스 없는 맨 <input type="checkbox|radio">브라우저 기본 컨트롤(OS 파랑) 로 렌더돼 DS 팔레트를 벗어납니다 — 반드시 클래스를 붙이세요. 크기는 16px 기본 / --lg 20px 둘뿐이고, accent-color·직접 그린 가짜 체크박스는 금지. 선택지에 보조설명이 붙어 라벨이 2줄이 되면 --lg(20px) + items-start + mt-0.5(컨트롤을 첫 줄에 맞춤) · gap-2.5 · 제목 text-b5 / 설명 text-b6 text-label-muted · 항목 간 space-y-1.52줄 조합. 상세: 체크박스 · 라디오.
  • 스위치 = ds-switch (작은 자리는 --sm, 이 둘뿐). <input type="checkbox">에만 붙입니다 — <button>/<div>로 트랙+손잡이를 직접 그리지 마세요(키보드·스크린리더가 사라집니다). 켜짐 primary 주황 / 꺼짐 neutral-300 / disabled 45%는 CSS가 강제하므로 색을 따로 주지 마세요. 글자 라벨이 옆에 없으면 aria-label 필수. 용도 경계: 즉시 반영되는 기능 on/off만 스위치, 저장을 눌러야 반영되는 폼 값은 체크박스, 예/아니오 상태 선택은 불리언 select(Modal). 상세: 스위치.
  • 피커(날짜·월·시간): 커스텀 우선(dsDatePicker/dsTimePicker + include 한 줄), 네이티브는 대안. 모달 안 레이어·닫기 규칙은 Modal.
  • 메뉴 패널 = .ds-menu + .ds-menu__item(아이콘+라벨은 --row, 선택은 --selected). 패널의 padding·border·shadow는 .ds-menu가 가지므로 래퍼에 bg-white border rounded shadow-* py-*를 따로 붙이지 마세요. 파괴적 항목(삭제)은 ds-menu__item--danger — hover에서만 의미색 red. hover:bg-red-50 같은 즉석 유틸로 위험 항목을 만들지 마세요. 고를 수 없는 항목은 disabled(또는 aria-disabled="true")만 주면 됩니다 — 글자가 disable 색으로 내려가고 hover 배경이 사라집니다. bg-neutral-50 text-neutral-400 cursor-not-allowed 같은 조건부 유틸을 직접 붙이지 마세요.
  • 모바일(<768): ds-input--48/ds-select--48권장 기본값(손가락 타깃 — 강제 아님, 사이즈 고르기). 사이즈는 항상 사이즈 클래스(--52/--48/--40/--32…)로 고르고 픽셀을 직접 쓰지 마세요. ⚠️ 모바일에서 입력칸을 탭하면 iOS가 화면을 자동 확대합니다(글자 16px 미만) — 아직 규칙을 정하지 않은 미해결 항목이니 임의로 정하지 말고 그 섹션을 읽으세요. DS 사이즈 클래스엔 md: 프리픽스가 안 붙으므로, 모바일에서만 크기를 바꾸려면 앱 main.css@media (max-width:767px)에서 스코프 잡아 덮으세요. 상세: Modal → 모바일.

라이브 미리보기

이 페이지에 등록된 인터랙티브 컴포넌트를 한눈에 — 직접 조작해 보고, 각 항목의 **설명 더보기 →**로 상세(클래스·토큰·정책)로 이동하세요. 모두 실제 design-system.css 규칙으로 렌더됩니다.

텍스트필드 · 드롭다운 · textarea · 숫자

플레이그라운드에서 크기·채움(fill)·error·disabled를 바꿔 보세요 — 같은 상태가 input·select·textarea에 동시 반영(입력 시 hover/focus도 실제 동작). 맨 아래 숫자(ds-input--number) 행은 스피너가 평소 숨겨졌다가 hover/focus 시 나타나는 걸 직접 확인할 수 있고, 컨트롤의 숫자 토글로 플레이그라운드에서도 켤 수 있습니다.

52px
48px
40px
32px · 기본
28px
24px
숫자ds-input--number — 스피너는 hover/focus 시에만, tabular-nums

현재: 32px · 입력값: — 크기·상태를 바꿔 보세요 (실제 .ds-input/.ds-select). 설명(.ds-form-help)은 입력/드롭다운 아래 6px, focus 시 muted→black. error 토글 시 같은 설명 줄이 red로 바뀌고 보더도 red.

설명 더보기 → 사이즈 스케일 · 토큰 · 숫자 입력 · 필드 구성(설명·에러)

메뉴 패널 (ds-menu)

커스텀 옵션 목록(트리·체크·라벨/구분선)용 플로팅 패널. 선택 항목은 회색 바로 강조됩니다.

팀 선택


선택된 항목은 회색 바로 강조됩니다

파괴적 항목은 ds-menu__item--danger — 삭제처럼 되돌리기 어려운 항목에 얹어요. 평상시엔 일반 항목과 똑같고 hover에서만 의미색 red(--color-secondary-red)로 경고해요. 버튼의 ds-btn--red와 같은 의미 축이에요. 아래 "삭제"에 마우스를 올려 보세요.

메뉴 항목에 hover 유틸을 직접 붙이지 마세요

hover:bg-red-50 hover:text-red-600 같은 즉석 유틸로 위험 항목을 만들지 마세요 — ds-menu__item--danger가 그 역할이에요. (admin 조직 트리 우클릭 메뉴가 유틸 조합에서 이 변형으로 이관됐어요.)

설명 더보기 → 클래스 · 토큰

달력 (날짜 · 월 선택)

날짜 = 커스텀 달력(우선) · 시스템 기본(type="date")은 대안 · 월 = 커스텀 월 선택기. 직접 눌러 보세요.

설명 더보기 → DS 정책 · 구성

시간 입력 (Time input)

커스텀 시간 입력 다이얼로그 — 오전/오후 드롭다운 + 시·분 키보드 입력 + ▲▼ 스텝. 12시간제로 표시하고 24시간제로 저장합니다. 트리거를 눌러 보세요 (17 입력 → 오후 5시 자동 변환).

근무 시작 시간 — 커스텀 시간 입력

클릭 → 시/분 키보드 입력 · ▲▼ 스텝(시 1↔12·분 0↔59 랩) · 오전/오후 드롭다운. 17 입력 → 오후 5시 자동 변환. 숫자 아님/무변경이면 확인 비활성. 저장은 24시간제 HH:MM.

설명 더보기 → DS 정책 · 구성

날짜 + 시간 결합

네이티브 datetime-local 대신, 한 팝업에 달력 + 커스텀 시간 폼을 합친 결합 뷰입니다(공지 만료일시 등). 트리거를 눌러 보세요 — 달력(좌)에서 날짜, 시간 폼(우)에서 오전·오후/시·분.

설명 더보기 → 결합 정책

필드 구성 (Field anatomy)

하나의 텍스트 필드는 위에서 아래로 라벨 → (필수 *) → 입력칸 → 설명 → 에러 순서로 쌓입니다. 설명과 에러는 **같은 자리(입력칸 아래 6px·12px)**를 공유해서, 에러가 떴다 사라져도 레이아웃이 흔들리지 않습니다.

회사 이메일을 입력하면 초대 메일이 발송돼요.

입력칸을 클릭(focus)하면 설명(.ds-form-help)이 muted→black, 에러 토글 시 red로 바뀝니다 — 상태 색은 CSS sibling 조합자로 자동. 설명과 에러는 같은 6px·12px 슬롯을 공유.

파트클래스 / 마크업스펙
라벨<label> + text-b5 font-medium text-label-assistive12px(b5) · 입력칸 위 6px(mb-1.5)
필수 표시라벨 끝에 <span class="text-red-400">*</span>red-400(#ff5f53) · 입력칸엔 HTML required도 함께
입력칸input.ds-input--40사이즈 스케일
입력칸 에러 보더ds-input--error / ds-select--errorborder = --ds-color-danger(focus 시 ring)
설명(description).ds-form-help12px · 입력칸 아래 6px · 항상 표시 · 색은 필드 상태 추종(rest=muted → focus=black → error=red)
에러 메시지.ds-form-error (높이 예약은 .ds-form-error-slot)danger red(#e63224) · 12px · 입력칸 아래 6px · x-show로 토글

설명 (description) — .ds-form-help

입력 규칙·맥락을 항상 보여 주는 보조 줄입니다(x-show 불필요). 12px·입력칸 아래 6px은 고정이고, 색은 필드 상태를 따라갑니다 — 평소엔 눈에 안 띄게, 입력 중엔 또렷하게, 에러일 땐 경고로:

필드 상태설명 색토큰
기본 (focus out)muted 회색--ds-label-fg-muted (#8f8f8f)
선택 (focus in)black--ds-label-black (#1b1b1b)
에러danger red--ds-color-danger (#e63224)
html
<input class="ds-input--40 w-full" placeholder="user@example.com" />
<p class="ds-form-help">회사 이메일을 입력하면 초대 메일이 발송돼요.</p>

상태 색은 CSS 하나로 자동 동작합니다(JS 토글 불필요) — .ds-form-help가 입력칸의 :focus·ds-input--error를 sibling 조합자로 읽습니다. 따라서 .ds-form-help는 입력칸과 같은 부모의 (뒤쪽) 형제여야 합니다(필드 구성 스택 그대로). 에러는 focus보다 우선(에러+focus여도 red).

왜 표준 클래스인가 — 그동안 설명 줄은 앱마다 text-b6 text-neutral-400/500 mt-0.5~1.5로 제각각이었습니다(색·간격 드리프트). .ds-form-help크기(12px)·간격(6px)·상태 색(muted→black→red)을 한 곳에 고정해 에러 줄과 세로 위치가 정확히 맞도록 통일합니다.

에러 메시지 — .ds-form-error / .ds-form-error-slot

유효성 에러를 입력칸 아래에 띄웁니다. danger red·12px·아래 6px. 보통 x-show로 조건부 노출하며, **입력칸 보더는 ds-input--error**로 같이 빨강 처리합니다(메시지+보더 한 쌍).

html
<input class="ds-input--40 w-full"
       :class="errors.email && 'ds-input--error'" />
<!-- 토글형: 에러일 때만 자리 차지 -->
<p x-show="errors.email" class="ds-form-error" x-text="errors.email"></p>
  • .ds-form-errorx-show로 보였다 사라지는 기본형. 평소엔 자리를 차지하지 않습니다.
  • .ds-form-error-slotmin-height:17px빈 상태에도 한 줄 높이를 예약합니다. 에러 토글 시 폼이 위아래로 점프하면 안 되는 정렬 폼(예: 직원 추가/수정 위저드)에 사용.

단일 출처 정비 — 두 메시지 클래스는 원래 apps/admin/src/styles/main.css에만 있던 로컬 규칙이었고, 이번에 공유 design-system.css로 승격했습니다. 앞으로 전 앱이 같은 클래스를 씁니다(앱 로컬 복제 금지). 입력칸 보더는 admin의 .is-error가 아니라 **공유 ds-input--error**를 표준으로 합니다.

단일 출처

파트파일
스타일packages/static/styles/design-system.css (native input/select/textarea 규칙 + .ds-input--XX/.ds-select--XX)
토큰--ds-textfield-* (border/hover/focus/radius/fg/placeholder) · --ds-dropdown-* (bg/border/text/radius) · --ds-dropdown-chevron

토큰 소유권: 필드 룩(테두리·radius·글자·placeholder·chevron)은 --ds-textfield-*/--ds-dropdown-* = DS 소유. 사이즈(height/font/padding)는 native 규칙 + --XX 모디파이어에 직접 정의. 마진 등 배치는 Tailwind 유틸.

기본값 — 32px (PR #247)

사이즈 클래스를 안 주면 medium 32px가 기본입니다. (이전 40px → 32px로 변경; design-system.css가 단일 출처, Figma는 색만 미러)

요소기본 heightfont비고
input (--XX 없음)32px12pxpadding 0 12px
select (--XX 없음)32px12pxchevron 우측 16px
textareaauto (min 5rem)12pxpadding 10px 16px, resize: vertical
박스 버튼min-height 32px.ds-tab-item(40px)·.icon-btn(정사각) 제외

강제 없이 동작: 기본 규칙은 input:not(...):not([class*="ds-input--"]) / select:not(.ds-select--24)…:not(.ds-select--52)사이즈 모디파이어를 제외합니다. 그래서 ds-input--48 같은 클래스를 주면 !important 없이도 기본 32px 규칙과 경쟁하지 않고 그대로 적용됩니다.

사이즈 스케일

텍스트 필드와 드롭다운이 같은 스케일(52/48/40/32/28/24)이라, 어떤 사이즈든 나란히 두면 높이가 flush하게 맞습니다.

ds-input--XX

클래스heightpaddingfont
ds-input--5252px0 16px14px
ds-input--4848px0 16px14px
ds-input--4040px0 16px14px
ds-input--3232px0 12px12px
ds-input--2828px0 12px12px
ds-input--2424px0 8px11px

textarea는 위 height를 무시하고 항상 auto-height(min 4rem, padding 8px 12px).

ds-select--XX

클래스heightfontpadding-leftchevron
ds-select--5252px14px16px우 16px
ds-select--4848px14px16px우 16px
ds-select--4040px14px16px우 16px
ds-select--3232px12px16px우 16px
ds-select--2828px12px12px우 10px
ds-select--2424px11px8px우 8px

스타일 변형: ds-select--fill-gray · ds-select--fill-white · ds-select--submenu(11px).

어떤 사이즈를 고르나 — 포인터 vs 손가락

스케일 안에서 고르는 기본 기준은 입력 장치입니다. 강제 매핑이 아니라 근거 있는 출발점이니, 화면이 다른 걸 요구하면 다른 값을 고르고 이유를 남기세요.

맥락권장 사이즈
데스크톱 폼·모달·필터ds-input--40 / ds-select--40 (조밀한 툴바는 ds-input--32)마우스 포인터 기준. 32가 요소 기본값
모바일 화면 · 모바일 풀페이지 모달 · 직원 앱ds-input--48 / ds-select--48손가락 타깃. 40은 포인터 기준이라 오탭이 난다
표 안 인라인 편집 · 마이크로 그리드ds-input--28 / ds-input--24행 높이에 맞춤

사이즈는 항상 이 클래스로 지정하세요. 픽셀을 직접 쓰면(Tailwind h-12 등) 높이만 먹고 보더·패딩·폰트가 함께 오지 않습니다.

폼 컨트롤은 숫자 표기(--24/28/32/40/48/52, 숫자가 곧 높이 px)이고, 버튼영문 표기(ds-btn--xs/sm/md/ml/lg/xl)입니다. 높이가 같아도 클래스 이름은 계열마다 다릅니다.

⚠️ 글자 크기는 아직 미해결입니다. 사이즈 클래스가 정한 글자는 14px인데, iOS Safari는 16px 미만인 입력창을 탭하면 화면을 자동 확대합니다. 즉 지금 모바일 웹에서 입력칸을 탭하면 확대가 뜹니다(알고 두는 상태). 16px로 올리는 것도, viewport로 확대를 막는 것도 각각 대가가 있어 아직 규칙을 정하지 않았습니다 — 임의로 정하지 말고 Modal → iOS 자동 확대의 정리를 먼저 읽으세요.

같은 화면에서 두 스케일을 오가는 법(마크업은 데스크톱 기준, 모바일 구간에서만 덮기)은 → Modal → 모바일.

상태

상태적용스타일
hover:hover:not(:disabled)border --ds-textfield-border-hover (#c1c1c1)
focus:focusborder --ds-textfield-border-focus (#1b1b1b), select은 focus ring
disabled:disabledopacity: 0.45, cursor: not-allowed, bg alt
error.ds-input--error / .ds-select--errorborder --ds-color-danger (+ focus 시 ring)

숫자 입력 (ds-input--number)

<input type="number">용 변형. 네이티브 증감 화살표(스피너)를 평소엔 숨기고 hover/focus 시에만 노출합니다 — 평소엔 가운데 정렬 숫자가 화살표에 밀리지 않고 깔끔하게, 조작 시엔 step 단위 증감(▲▼)이 가능. 숫자 정렬용 tabular-nums도 포함합니다. (키보드 /는 화살표 노출과 무관하게 항상 동작.)

조합형 변형 — 이 클래스는 숫자 고유 처리(스피너 + tabular-nums)만 담당합니다. 크기는 ds-input--XX, 너비는 w-full, 가운데 정렬은 text-center 유틸과 함께 씁니다(정렬은 선택 — 왼쪽 정렬 금액 입력 등엔 빼면 됨).

항목처리
스피너 (기본)숨김 — webkit ::-webkit-*-spin-button { appearance: none } + Firefox appearance: textfield
스피너 (hover/focus)노출 — appearance: auto로 ▲▼ 복귀(주로 webkit에서 동작). :not(:disabled) 가드
숫자 정렬font-variant-numeric: tabular-nums — 세로로 쌓인 숫자 칸의 자리수 정렬
html
<input type="number" x-model.number="wardSettingsForm.ratioDay" min="1" step="0.5"
       class="ds-input--40 ds-input--number w-full text-center">

라이브 미리보기는 상단 라이브 미리보기의 플레이그라운드에서 숫자 토글로 확인하세요.

흩어진 핵을 대체 — 그동안 숫자 인풋 스피너는 앱마다 제각각이었습니다(admin .spinner-on-hover, .patient-count-input·.sched-grid input[type=number] 항상 숨김, team-manager 인라인 style="-moz-appearance:textfield"). ds-input--number는 그중 검증된 hover/focus 노출 동작(admin .spinner-on-hover)을 공유 DS로 승격한 것으로, 앞으로 숫자 인풋은 이 단일 기준으로 통일합니다.

체크박스 · 라디오

둘은 한 세트예요. 크기(16px)·테두리·선택 색·hover·focus·disabled 값이 완전히 같고, 모양만 다릅니다 — 체크박스는 사각(radius 2px) + 흰 체크, 라디오는 원형 + 가운데 흰 점. 한 화면에 섞여 나와도 어긋나지 않게 값을 공유해요.

체크박스 .ds-checkbox
라디오 .ds-radio
html
<label class="flex items-center gap-1.5">
  <input type="checkbox" class="ds-checkbox" x-model="form.shared" /> 공개
</label>

<label class="flex items-center gap-1.5">
  <input type="radio" class="ds-radio" name="rowBasis" :checked="basis === 'date'" @change="basis = 'date'" /> 날짜 기준
</label>
클래스크기비고
ds-checkbox16px사각 2px · 선택 시 검정 채움 + 흰 체크
ds-checkbox--lg20px손가락 타깃이 필요한 자리(메신저 그룹 선택 등)
ds-radio16px원형(링 12px) · 선택 시 검정 채움 + 흰 점 4px
ds-radio--lg20px링 16px · 점 6px

클래스를 빼먹으면 OS 파랑이 나옵니다

class를 안 붙인 <input type="checkbox">/<input type="radio">브라우저 기본 컨트롤로 렌더돼요. 선택 색이 macOS/Windows의 파랑이라 DS 팔레트 밖 색이 화면에 섞입니다. 입력 요소 자체는 DS가 :not([type="checkbox"]):not([type="radio"])의도적으로 제외하고 있어서, 클래스 없이는 아무것도 적용되지 않아요.

선택지에 설명이 붙는 경우 (2줄 조합)

선택지마다 "무엇인지"를 한 줄 더 설명해야 하면 라벨을 제목 + 보조설명 2줄로 쌓아요. 연차 정책처럼 고르는 순간 계산 규칙이 바뀌는 설정에서는 제목만으로 판단이 안 서기 때문이에요.

html
<div class="space-y-1.5">
  <label class="flex items-start gap-2.5 cursor-pointer">
    <input type="radio" name="policy" class="ds-radio ds-radio--lg mt-0.5 shrink-0" />
    <span class="min-w-0 flex-1">
      <span class="block text-b5 text-label-normal">공휴일을 연차에서 제외</span>
      <span class="block text-b6 text-label-muted mt-0.5">5인 이상 사업장은 공휴일이 법정 유급휴일이에요</span>
    </span>
  </label>
</div>
클래스
컨트롤 크기--lg (20px) — 라디오·체크박스 공통선택지 한 칸이 2줄(≈36px)이라 16px 기본은 글자 덩어리에 눌려 작아 보여요. 한 줄짜리는 기본 16px 그대로
컨트롤 정렬items-start + mt-0.5 (제목 크기와 무관하게 항상)설명이 붙어 라벨이 2줄이 되므로 가운데가 아니라 첫 줄에 맞춰요(items-center면 두 줄 사이로 내려가 어긋나요). 오프셋 2px 은 광학 보정이에요 — 글자는 줄 상자 안에서 아래로 치우쳐 앉기 때문에, 컨트롤을 줄 상자 위에 딱 맞추면 글자보다 위에 뜬 것처럼 보입니다. 계산상 높이가 같아도(14px 제목 ≈ 20px 컨트롤) 빼지 마세요
컨트롤 ↔ 글자gap-2.5 (10px)한 줄짜리(6px)보다 넓혀요 — 글자 덩어리가 커진 만큼
제목 / 설명text-b5 (13) / text-b6 (12) text-label-muted · mt-0.5필드 라벨·설명과 같은 축
선택지 간격space-y-1.5 (6px)한 항목이 2줄이라 항목 사이가 줄 간격과 구분돼야 해요
글자 묶음min-w-0 flex-1없으면 긴 설명이 컨트롤을 밀어냅니다
  • 설명이 없는 선택지에는 쓰지 마세요 — 한 줄이면 기본 조합으로 충분해요.
  • 선택지마다 카드 테두리가 필요할 만큼 설명이 길고 이미지·아이콘이 붙는다면 라디오가 아니라 선택 카드예요.
  • 체크박스도 같은 조합을 씁니다(다중 선택 + 항목별 설명) — 크기도 ds-checkbox--lg 로 맞춰요. 한 화면에 둘이 섞여 나올 때 한쪽만 16px 이면 바로 티가 납니다.

체크박스 vs 라디오 — 무엇을 쓰나

여러 개를 동시에 켤 수 있으면 체크박스, 여럿 중 하나만 고르면 라디오예요. 선택지가 2개이고 서로 반대말이면(공개/비공개) 라디오 2개보다 체크박스 1개가 낫습니다. 선택지가 5개를 넘어가면 라디오 대신 드롭다운을 고려하세요. 선택지마다 설명 한 줄이 붙어야 하면 선택 카드가 맞습니다.

스위치 (.ds-switch)

기능을 켜고 끄는 컨트롤이에요. 체크박스와 달리 누른 즉시 반영되는 자리에 씁니다 — 저장 버튼을 눌러야 적용되는 폼 값에는 체크박스를, "출근 현황 겹쳐보기"처럼 켜자마자 화면이 바뀌는 자리에는 스위치를 쓰세요.

기본 .ds-switch 32×20
작은 --sm 26×16
html
<!-- 라벨을 <label>로 감싸면 글자를 눌러도 토글돼요 -->
<label class="flex items-center gap-2">
  <span class="text-b6 text-neutral-600">출근 현황</span>
  <input type="checkbox" class="ds-switch ds-switch--sm"
         :checked="showAttendance" @change="toggleAttendance()"
         aria-label="출근 현황 토글">
</label>
클래스트랙손잡이쓰는 곳
ds-switch32×2016px기본 — 설정 목록, 모달 안 옵션 행
ds-switch ds-switch--sm26×1612px툴바처럼 세로 공간이 빠듯한 줄
  • 켜짐은 primary 주황, 꺼짐은 neutral-300(hover neutral-400), disabled는 45% 불투명도예요. 색을 따로 지정하지 마세요.
  • <input type="checkbox">에만 붙습니다. <button>이나 <div>로 스위치 모양을 직접 그리지 마세요 — 키보드 조작과 스크린리더가 공짜로 따라옵니다.
  • 글자 라벨이 옆에 없으면 aria-label이 필수예요.

스위치 · 체크박스 · 토글 버튼 — 어떤 걸 쓰나

  • 스위치 — 기능 on/off, 즉시 반영 (출근 현황 겹쳐보기, 알림 받기)
  • 체크박스 — 폼 값, 저장을 눌러야 반영 (약관 동의, 목록 다중 선택)
  • 상태 선택(예/아니오) — 스위치가 아니라 불리언 select를 씁니다 → 모달 규칙

사용된 토큰

토큰용도
--ds-textfield-border#e5e5e5input/textarea 기본 테두리
--ds-textfield-border-hover#c1c1c1hover 테두리
--ds-textfield-border-focus#1b1b1bfocus 테두리
--ds-textfield-radius4px필드 radius (중앙 조정점)
--ds-textfield-fg / --ds-textfield-placeholder#1b1b1b / #8f8f8f글자 / placeholder
--ds-dropdown-bg/border/text/radius#fff / #e5e5e5 / #1b1b1b / 4pxselect 박스
--ds-dropdown-chevronSVG data-uri (#8f8f8f stroke)select 화살표

검토 필요 사항

  • 🟡 --ds-textfield-*/--ds-dropdown-*가 raw hex로 정의 (primitive 미연결)--color-neutral-100/-900 등 primitive를 var()로 참조하면 primitive 한 곳만 고쳐도 모든 입력칸에 전파되는 진짜 단일 출처가 됩니다. 단, 지금 단순 전환하면 안 되는 트레이드오프가 있습니다: --ds-*는 의도적으로 runtime-safe literal 세트(토큰 생성기가 hex만 export하고, @theme 처리·tree-shaking에 의존하지 않게 둔 집합)입니다. 실측상 일부 앱 빌드에선 primitive가 :root에서 가지치기됩니다(--color-label-white가 module-manager·inventory 빌드에서 제거됨) → bare var()는 그 앱들에서 배경이 깨지고, fallback var(--color-x, #hex)는 fallback hex가 사실상 사본이라 전파가 다시 끊깁니다. 올바른 해법 = primitive를 전 앱 :root에 항상 emit(예: @theme static)하도록 정비한 뒤 --ds-*를 일괄 연결 — DS 토큰 아키텍처 차원의 별도 작업으로 보류합니다(목표 & 로드맵).

적용 범위 & 도입 현황

이 컴포넌트는 DS에 등록된 변수(크기 24~52 · 상태 · 토큰)를 골라 쓰는 전역 폼 컨트롤입니다. 적용은 두 층으로 나뉩니다.

  • 전역 기본 (자동 · 전 서비스)input/select/textarea 요소 규칙이라, 클래스를 안 붙여도 모든 입력칸이 기본 32px 룩을 자동으로 받습니다. 마이그레이션과 무관하게 이미 전 서비스에 적용됨.
  • 명시적 사이즈 전환 (도입 진행 중) — 기본 32px가 아닌 크기가 필요한 곳에만 ds-input--XX/ds-select--XX를 부여. 궁극 목표는 전 서비스 정합이며, 현재 진행 상황은 아래와 같습니다.
시점전환된 곳적용 사이즈
PR #247admin · general-affairs 폼 31개 (x-model 전량 보존)--32
모달 마이그레이션직원 추가/수정 위저드 · 근태 모달 (hr-system · admin)--40
이후잔여 모달 · 앱 전역진행 예정

"사용처 목록"이 아니라 도입 진행 상황으로 기록합니다 — 기본 룩은 이미 전역이라, 추적할 가치가 있는 건 "기본과 다른 크기로 의도적으로 전환한 곳"뿐입니다. 전체로 퍼져도 이 표는 낡지 않습니다.

네이티브 select로 표현이 안 되는 커스텀 옵션 목록(트리·체크 표시·그룹 라벨/구분선)은 공유 컴포넌트 .ds-menu 패널을 씁니다. OS 기본 드롭다운 대신 직접 띄우는 플로팅 메뉴로, 실제 직원 수정 모달의 팀/역할 선택에 사용됩니다. (design-system.css에 등록된 공유 컴포넌트 — 라이브 미리보기는 상단 라이브 미리보기 참고)

클래스역할
.ds-menu패널 컨테이너 (흰 바탕·라인·그림자·radius)
.ds-menu--scrollable최대 12rem 높이 + 세로 스크롤
.ds-menu__item메뉴 항목 (button/a)
.ds-menu__item--row좌우 배치 (체크 아이콘 등 우측 정렬)
.ds-menu__item--selected선택 상태 — 30% 회색 틴트의 꽉 찬 사각 바 (Figma 13413:9425)
.ds-menu__divider구분선
.ds-menu__label그룹 라벨 (10px, muted)
.ds-menu__hint안내/빈 상태 텍스트

토큰: 배경·라인·radius는 --ds-dropdown-*, 선택 틴트는 --ds-hover-tint-30, 구분선은 --ds-line-normalselect와 같은 드롭다운 토큰 계열을 공유합니다.

ms-dropdown (앱 로컬 — 공유 DS 아님)

다중 선택용 .ms-dropdown(커스텀 트리거+패널)은 admin·team-manager의 src/styles/main.css에만 정의된 앱 로컬 패턴으로, 공유 design-system.css에는 없습니다. PR #247에서 연장근무 상태 selectms-dropdown으로 교체. 트리거(.ms-dropdown__trigger)는 Tooltip의 compact 툴팁 대상이라 선택 라벨이 hover로 표시됩니다. 공유 DS로 끌어올릴지는 별도 검토 대상이며, **현재 공유 표준은 ds-menu**입니다.

피커 (Picker) — 날짜 · 월 · 시간

폼 컨트롤의 세 번째 패밀리. 텍스트 한 줄(Input)·선택지(Select)와 달리 날짜·월·시간을 고르는 입력입니다 — 날짜 = 커스텀 달력(dsDatePicker — 네이티브 type="date"는 대안), 월 = 커스텀 팝오버(.ds-menu), 시간 = 커스텀 드롭다운(dsTimePicker). 모달·페이지 공용이며, 라이브 조작은 상단 라이브 미리보기에서. 모달 안에서 쓸 때의 레이어·닫기 규칙은 Modal §7.

달력 (날짜 · 월 선택)

DS 정책 — 날짜는 커스텀 우선(시스템 기본은 대안), 월은 커스텀

  • 날짜 선택 = 커스텀 달력(우선) — 결합 피커와 동일한 커스텀 달력(연도 드롭다운 + 월 이동 ‹ › + 삭제·오늘 + 오늘=채움·다른 날=테두리)을 씁니다. 앞으로 날짜 입력은 커스텀으로 통일 예정이며 커스텀을 우선합니다. 시스템 기본 <input type="date">는 대안으로 유지(네이티브 지원·모바일 휠·로케일이 필요하거나 아직 미전환인 화면).
  • 월 선택 = 커스텀 컴포넌트 — 네이티브 <input type="month">는 브라우저 지원이 약해(Firefox 등 미지원), .ds-menu 팝오버 + 연도 .ds-select + 월 그리드의 커스텀 월 선택기를 씁니다.

라이브 미리보기는 상단 라이브 미리보기에서 직접 조작해 보세요 — 날짜(커스텀·우선): 연도 드롭다운 + 월 이동 + 삭제·오늘, 오늘=채움·다른 날=테두리. 날짜(시스템 기본·대안): 브라우저 네이티브 달력(OS/브라우저별 외형). : 커스텀 팝오버(트리거 아래 6px·우측정렬).

날짜 선택기 구성 (커스텀 — dsDatePicker)

네이티브 type="date"를 대체하는 공유 커스텀 달력. Alpine 컴포넌트 dsDatePicker(@hereby/components, createAuthComponentsPlugin에 등록 — 전 앱 사용 가능) + 공유 마크업 partial(packages/components/html/ds-date-picker.html) + .ds-datepicker* CSS(design-system.css)로 구성. 사용처는 호스트 div + include 한 줄이면 됩니다(로직·외형·마크업 모두 공유, 중복 없음).

파트내용
트리거readonly input.ds-input--40(우측 달력 아이콘). 누르면 달력 열림
팝오버x-teleport="body"로 모달 밖에 렌더 + position:fixed로 트리거 바로 아래 배치 → 모달의 overflow-y-auto·filter탈출해 절대 안 잘림(모달 밖으로 넘어가도 OK). dim 없음, z = 모달 위 1레이어(--ds-z-modal-top)
그리드연도 드롭다운 + 월 이동 ‹ › + 일자. 선택&오늘 = 채움 / 선택&다른 날 = 테두리
푸터.ds-btn-text--sm — 삭제(assistive) · 오늘(primary)
닫기Esc · 바깥 클릭 · 날짜 선택 → 피커만 닫힘(모달은 유지)
YYYY-MM-DD 문자열 — 호스트의 x-modelx-modelable="value"로 바인딩

사용법 (사용처 마크업):

html
<div class="ds-datepicker" x-data="dsDatePicker({ max: '9999-12-31' })"
     x-modelable="value" x-model="form.someDate">
  <!-- include "../../packages/components/html/ds-date-picker.html" -->
</div>
  • 옵션: dsDatePicker({ min, max, placeholder })min/maxYYYY-MM-DD(범위 밖 날짜 비활성).
  • 컴팩트 트리거: 호스트에 사이즈 모디파이어를 더해 트리거 높이를 줄입니다(기본 40px). ds-datepicker--32(32px) → 필터 줄의 ds-select--32/ds-input--32와 정렬(예: 근태 현황 탭 기간 필터). ds-datepicker--sm(28px) → 더 좁은 인라인 행(예: 스케줄러 희망일 목록).
  • 비활성: 호스트에 x-effect="disabled = (조건)".
  • 선택 시 파생 로직(예: 신고일 → 차기기한 자동계산): dsDatePicker({ onChange: () => fn() })유저 선택 시에만 발화(pick/clear/today, 다음 tick에 x-model 동기화 후). 네이티브 @change와 동일하게 프로그램적 로드엔 안 뜸.

원본: completion-form.html · 컴포넌트 DsDatePicker.ts · 마크업 ds-date-picker.html.

날짜 범위 필터 (프리셋 버튼)

리스트/현황 화면의 기간(from ~ to) 필터는 dsDatePicker 2개(시작·종료) + 프리셋 버튼 줄로 구성합니다.

파트마크업·동작
시작·종료 피커dsDatePicker 2개, 둘 다 ds-datepicker--32(필터 줄 높이 정렬). 교차 clamp — 시작에 `x-effect="max = (to
프리셋 버튼이번주 · 지난주 · 이번달 · 지난달 · 최근 3개월 | 전체. 각 버튼 text-c1(11px)·rounded-lg, 활성 하이라이트는 공유 datePresetBtnClass(active, key)(활성=진한 라인 / 비활성=회색 라인)
초기화 버튼ds-btn ds-btn--icon ds-btn--muted ds-btn--sm(새로고침 아이콘, title="기간 초기화"). `:disabled="!(from
  • 동작: 프리셋 클릭 → resolveDatePresetRange(preset)from/to를 계산하고 활성 key(certDatePreset/docMgmtDatePreset 등 상태 필드 1개)를 저장 후 재조회. 피커에서 날짜를 직접 고르면 onChange에서 프리셋 key를 비워(수동 선택 = 프리셋 해제) 즉시 재조회. 초기화는 from/to/프리셋을 함께 비움.
  • resolveDatePresetRange·datePresetBtnClass는 앱 공유 헬퍼 — 새 필터도 이 둘을 재사용하세요(프리셋 세트·하이라이트가 자동 일치).

원본: 증명서 필터 documents.html (setCertDatePreset) · 문서관리 document-management.html (setDocMgmtDatePreset).

월 선택기 구성 (커스텀)

파트마크업
트리거필드 모양 div(role="button", h-32 · --ds-textfield-radius · 달력 아이콘). select가 아니라 div라 전역 버튼/인풋 규칙과 충돌 없음
팝오버 표면.ds-menu (240px, padding 12px) — 트리거 우측 정렬(right-0), top-full + 마진 6px(모달 밖 넘침 방지)
연도.ds-select.ds-select--32 (현재 −10년 ~ +1년, 12개)
월 그리드4×3 버튼. 선택 --color-label-normal(검정 채움) · 이번 달 --color-label-normal 테두리 · 기본 --color-line-normal 테두리
푸터.ds-btn-text--sm — 닫기(assistive) · 이번 달(primary)

원본: apps/admin/src/views/modals/employee-attendance.html (mpOpen / mpYear / setAttendanceMonth).

왜 커스텀을 우선하나: 날짜+시간 결합 피커가 커스텀 달력을 쓰는 이상, 단독 날짜도 같은 커스텀으로 맞춰야 외형·동작이 일관됩니다(연도 드롭다운·삭제·오늘·선택 강조 등 제품이 통제). 그래서 앞으로 커스텀으로 통일하고 커스텀을 우선합니다. 단 네이티브 type="date"대안으로 유지 — 접근성·모바일 휠·로케일이 공짜이고, 아직 전환 안 된 화면은 네이티브 그대로 둡니다. (전환은 점진적; 신규/시각통일 필요 화면부터 커스텀)

시간 입력 (Time input)

DS 정책 — 시각은 기본 네이티브, 안내형 입력은 커스텀 드롭다운

  • 기본 = 네이티브 <input type="time"> — 단순 시각 입력은 네이티브를 씁니다(ds-input--40 tabular-nums). DS 테두리·높이 자동, 모바일 휠 공짜. 예: 근태 수정 모달.
  • 커스텀 시간 드롭다운(dsTimePicker) — 안내형 12시간 입력(오전/오후 + 시/분 + 증감 스텝 + 유효성)이 필요할 때. 트리거를 누르면 인풋 아래 드롭다운이 뜹니다(날짜 피커와 동일 — x-teleport 모달 위·안 잘림·dim 없음). 근무 설정의 시작/종료시간이 이 패턴.

라이브 미리보기는 상단 라이브 미리보기에서 직접 — 17 입력 → 오후 5시 자동 변환, ▲▼ 스텝(시 1↔12·분 0↔59 랩), 숫자 아님/무변경이면 확인 비활성.

구성 (커스텀 — dsTimePicker)

날짜 피커와 같은 형태의 공유 컴포넌트 — Alpine 팩토리 dsTimePicker + 공유 마크업 partial(packages/components/html/ds-time-picker.html) + .ds-timepicker* CSS. 사용처는 호스트 div + include 한 줄:

html
<div class="ds-timepicker" x-data="dsTimePicker()" x-modelable="value" x-model="form.startTime">
  <!-- include "../../packages/components/html/ds-time-picker.html" -->
</div>
파트마크업
트리거readonly input.ds-input--40(우측 시계 아이콘·tabular-nums) — 누르면 드롭다운 오픈. 클릭 트리거라 OS time 피커 대신 커스텀 뷰가 뜸
드롭다운x-teleport="body" + position:fixed로 트리거 바로 아래 → 모달 overflow·filter 탈출해 안 잘림, dim 없음, z = 모달 위 1레이어(--ds-z-modal-top). Esc·바깥클릭·취소 = 피커만 닫음(모달 유지)
오전/오후.ds-select--40 드롭다운(오전/오후) — 단일선택이라 드롭다운
시 · 분.ds-input--40(가운데정렬·tabular-nums·키보드 입력) + 위·아래 스텝 아이콘 버튼(간격 6px). 시 1↔12·분 0↔59 랩어라운드
푸터.ds-btn--solid --white-bk(취소) · .ds-btn--solid --dark(확인) --md. 확인은 유효 + 변경 시에만 활성

값 변환: 표시 = 12시간제(오전/오후 + 시 + 분), 저장 = 24시간제 HH:MM — 호스트의 x-model에 바인딩. 시 칸은 24시간제 입력도 허용해(17 → 오후 5시) blur 시 정규화 — 자정 = 오전 12, 정오 = 오후 12. 확인 활성 조건 = 시(0–23)·분(0–59) 순수 숫자 AND 원래 값에서 변경.

원본: shift-form.html(시작/종료) · 컴포넌트 DsTimePicker.ts · 마크업 ds-time-picker.html.

날짜 + 시간 결합

결합 뷰 = 한 팝업에 [달력 + 시간]. 네이티브 <input type="datetime-local">은 OS·브라우저마다 외형/동작이 달라(공지 만료일시가 이 케이스) 쓰지 않고, 그 "한 view 결합" 경험을 DS로 재현합니다 — 트리거 클릭 → 한 팝업에 커스텀 달력 그리드(좌) + 커스텀 시간 폼(우, 오전·오후 + 시·분 + 스텝) → 확인 시 YYYY-MM-DD HH:mm 저장.

⚠️ 결합 뷰는 달력 그리드를 시간 폼과 한 팝업에 인라인으로 그립니다 — 네이티브 시스템 달력은 브라우저 자체 팝업이라 한 팝업에 못 넣기 때문. 단일 날짜는 같은 커스텀 달력을 dsDatePicker 드롭다운으로 띄웁니다(외형·동작 일관). 즉 커스텀 달력 그리드는 단독·결합 모두 쓰며, 차이는 단독=teleport 드롭다운 / 결합=한 팝업 인라인입니다.

라이브는 상단 라이브 미리보기에서.

마이그레이션 대상: 현재 datetime-local을 쓰는 곳(예: 공지 작성 만료일시)은 이 조합(시스템 달력 + 커스텀 시간 폼)으로 교체 권장.

목표 & 로드맵

항목현재목표
기본 32px 통일✅ (40→32)유지
input/select 사이즈 스케일 정합✅ 52~24 동일유지
사이즈 모디파이어 강제 제거!important 없음유지
시맨틱 토큰 → primitive 연결🟡 runtime-safe literal (의도적)primitive를 전 앱 :root에 항상 emit(@theme static 등)한 뒤 일괄 연결 — 별도 DS 작업
chevron 색 토큰화🟡 data-uri 한계 (값은 on-palette #8f8f8f=neutral-400)빌드타임 색 주입(생성 단계)만 가능<select>는 pseudo-element 불가라 mask-image 안 됨. 보류(ROI 낮음)

변경 이력

날짜내용
2026-07-07날짜 범위 필터(프리셋 버튼) 패턴 신설(#date-range-filter) — 리스트 기간 필터를 dsDatePicker 2개(시작·종료, x-effect 교차 clamp) + 프리셋 줄(이번주/지난주/이번달/지난달/최근 3개월 | 전체) + --icon --muted 초기화 버튼으로 문서화. 프리셋 계산·하이라이트는 공유 헬퍼 resolveDatePresetRange/datePresetBtnClass 재사용. 증명서 필터(admin)·문서관리(team-manager) 도입 (design/adjust-table)
2026-06-30필드 구성(anatomy) 섹션 신설 — 설명·에러 표기 표준화 — 텍스트 필드의 라벨→필수→입력칸→설명→에러 스택을 필드 구성으로 문서화(라이브 <DemoFieldAnatomy>). 설명 줄 = 새 공유 클래스 .ds-form-help(12px·아래 6px·항상 표시, 색은 필드 상태 추종 rest muted→focus black→error red, sibling 조합자로 JS 없이 동작) — 그동안 text-b6 neutral-400/500 mt-0.5~1.5로 제각각이던 걸 단일 표준으로 고정. 에러 메시지 = .ds-form-error/.ds-form-error-slot을 admin main.css 로컬 → 공유 design-system.css로 승격(danger·12px·아래 6px, slot은 min-height:17px로 점프 방지). 입력칸 보더는 admin .is-error가 아니라 공유 ds-input--error 표준으로 명시
2026-06-26숫자 입력 변형(ds-input--number) 신설<input type=number>용 공유 변형. 네이티브 스피너를 평소 숨기고 hover/focus 시에만 노출(admin .spinner-on-hover 동작을 공유 DS로 승격) + tabular-nums. 크기·너비·정렬(ds-input--XX·w-full·text-center)과 조합. 숫자 입력 섹션·라이브 미리보기(<DemoFormControls> 숫자 토글) 추가, 흩어진 patient-count-input·sched-grid·인라인 -moz-appearance 핵을 대체할 단일 기준으로 명시. 도입: scheduler ward-settings 비율 입력(admin·tm)
2026-06-26시간 입력 피커 = teleport 드롭다운으로 전환(dsTimePicker) — 기존 dim 중앙 다이얼로그(.ds-modal-backdrop--top + .ds-mini-modal)를 날짜 피커와 동일한 인풋 아래 드롭다운(x-teleport·fixed·안 잘림·dim 없음·Esc/바깥클릭=피커만 닫힘)으로. te* 인라인 로직을 공유 dsTimePicker(팩토리+partial+.ds-timepicker* CSS)로 추출 → shift-form(시작/종료)·notice-form(만료시각) 적용. #time-input "구성" 갱신, ## 피커 인트로 "시간=다이얼로그"→"드롭다운"
2026-06-26커스텀 날짜 달력 컴포넌트(dsDatePicker) 신설 + 앱 적용 — 네이티브 type=date 대체 공유 컴포넌트(Alpine DsDatePicker + 공유 마크업 partial + .ds-datepicker* CSS)를 "날짜 선택기 구성"으로 문서화. x-teleport="body" + fixed 드롭다운이라 모달 overflow·filter 탈출해 안 잘림(dim 없음·모달 위 1레이어·Esc/바깥클릭=피커만 닫힘). 사용처는 호스트 div + include 한 줄. 데스크톱 폼 12필드 전수 적용(ga·admin·hr·inventory·tm) + 좁은 행용 --sm(28px) 변형. 정정: ## 피커 인트로 "날짜=네이티브"→"커스텀 달력", #datetime의 "단일 날짜=네이티브(커스텀 안 만듦)" 모순 제거(단독=드롭다운/결합=인라인)
2026-06-25Input · Select · Picker 3패밀리로 정리 — 제목에 Picker 추가, ## 피커 (Picker) 상위 섹션 신설하고 달력(날짜·월)·시간 입력을 그 아래로 그룹화(#calendar/#time-input 앵커 유지). 그리고 "시간 입력 (Time input)" 신설 — 커스텀 시간 다이얼로그(오전/오후 드롭다운 + 시/분 키보드 입력 + ▲▼ 스텝 + 17→오후5 정규화 + 숫자아님/무변경 시 확인 비활성) 라이브(<DemoTimeInput>)·구성·값변환·날짜+시간 결합 정책. 기본=네이티브 type=time, 안내형=커스텀 다이얼로그(ds-mini-modal). 날짜+시간 결합 라이브(<DemoDateTime>, {#datetime}) 추가 — 결합 뷰가 라이브 예시 없던 것 보완
2026-06-19라이브 미리보기 = 컴포넌트 갤러리로 확장(폼 컨트롤·메뉴 패널·달력 3종을 상단에 모으고 각 항목 설명 더보기 앵커 → 상세 섹션) · 메뉴 패널(ds-menu) 섹션 신설(__item--selected 포함) · "달력(날짜·월 선택)" 섹션 신설(날짜=시스템 기본 type=date, 월=커스텀 월 선택기 — <DemoDatePicker> 라이브) · "사용처" → "적용 범위 & 도입 현황"으로 재구성(모달 마이그레이션 반영) · ms-dropdown=앱 로컬 정정 · 미리보기에 textarea·드롭다운 fill 변형 추가 · 검토 필요 사항 정리 — ① primitive 연결 항목을 runtime-safe literal 트레이드오프로 보강(보류), ②chevron(on-palette·data-uri 한계)·③24px(일관 literal px·이슈 아님)·④!important-free(정상)는 검토 완료로 제거
2026-06-08Form controls 페이지 신규 작성 + 라이브 미리보기(<DemoFormControls>) (PR #247 기준)
PR #247폼 필드·박스 버튼 기본 40→32px 통일 · ds-input 사이즈 스케일(28/40/48/52) 추가 · admin+general-affairs 31개 폼 ds-select/ds-input 마이그레이션 · select chevron 렌더 버그 수정