SelectN
옵션 목록을 열어 하나를 고릅니다. 옵션이 5개 이하이고 항상 보여야 하면 Radio · Select Button, 고르는 것이 아니라 실행이면 Dropdown 을 씁니다.
1. Default
닫힌 기본 상태입니다. 고른 값이 없으면 트리거에 placeholder 를 둡니다. 눌러서 열면 항목이 8개를 넘는 목록은 메뉴 안에서 스크롤됩니다(최대 높이 320).
HTML
<div class="dropdown">
<button type="button" class="trigger" aria-haspopup="listbox" aria-expanded="false"><span class="placeholder">그룹 선택</span></button>
<ul class="menu" role="listbox" aria-label="그룹">
<li class="menu-item" role="option">전체</li>
<li class="menu-item" role="option">영업팀</li>
<li class="menu-item" role="option">개발팀</li>
<li class="menu-item" role="option">인프라팀</li>
<li class="menu-item" role="option">보안팀</li>
<li class="menu-item" role="option">기획팀</li>
<li class="menu-item" role="option">디자인팀</li>
<li class="menu-item" role="option">QA팀</li>
<li class="menu-item" role="option">고객지원팀</li>
<li class="menu-item" role="option">재무팀</li>
<li class="menu-item" role="option">인사팀</li>
<li class="menu-item" role="option">법무팀</li>
</ul>
</div>CSS · .dropdown, .menu, .menu-item
/* JS Design System v0.6 · .dropdown, .menu, .menu-item · 토큰(var(--*))은 assets/style.css :root 참고 */
.dropdown{position:relative;display:inline-block}
.dropdown .trigger{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 12px 0 14px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface);font:inherit;font-size:14px;color:var(--text-primary);cursor:pointer;min-width:200px;justify-content:space-between}
.dropdown .trigger:hover{border-color:var(--border-strong)}
.dropdown .trigger .placeholder{color:var(--text-disabled)}
.dropdown .trigger::after{content:"";width:16px;height:16px;flex:none;background:var(--text-secondary);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M6 9l6 6l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M6 9l6 6l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;transition:transform var(--motion-fast)}
.dropdown.open .trigger{border-color:var(--border-focus)}
.dropdown.open .trigger::after{transform:rotate(180deg)}
.dropdown .trigger[disabled]{opacity:.4;cursor:not-allowed}
.dropdown .menu{position:absolute;top:calc(100% + 4px);left:0;display:none;z-index:5;min-width:100%}
.dropdown.open .menu{display:block}
.dropdown.up .menu{top:auto;bottom:calc(100% + 4px)}
.menu{min-width:200px;padding:6px;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);box-shadow:var(--shadow-2);list-style:none;margin:0;max-height:min(320px,60vh);overflow-y:auto;overscroll-behavior:contain}
.menu-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:6px;font-size:14px;cursor:pointer;color:var(--text-primary);white-space:nowrap}
.menu-item:hover{background:var(--bg-subtle)}
.menu-item.on{background:var(--accent-subtle);color:var(--accent-text);font-weight:600}
.menu-item.danger{color:var(--danger-text)}
.menu-item[aria-disabled="true"]{opacity:.4;cursor:not-allowed;pointer-events:none}
.menu-item .hint{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.menu-item svg{width:16px;height:16px;flex:none;color:var(--text-tertiary)}
.pagination-bar .dropdown .trigger{height:32px;min-width:120px;font-size:12px}React · @jiran/ds-react
import { Select } from "@jiran/ds-react";
const GROUPS = ["전체", "영업팀", "개발팀", "인프라팀", "보안팀", "기획팀", "디자인팀", "QA팀", "고객지원팀", "재무팀", "인사팀", "법무팀"]
.map((label) => ({ value: label, label }));
export default function Example() {
return <Select options={GROUPS} placeholder="그룹 선택" label="그룹" keepMounted />;
}2. Open / Selected
닫힘과 열림을 나란히 둔 모습입니다. 열린 메뉴는 role=listbox, 고른 항목은 .on + aria-selected.
HTML
<div class="dropdown">
<button type="button" class="trigger" aria-haspopup="listbox" aria-expanded="false"><span class="placeholder">그룹 선택</span></button>
<ul class="menu" role="listbox" aria-label="그룹">
<li class="menu-item" role="option">전체</li>
<li class="menu-item" role="option">영업팀</li>
<li class="menu-item" role="option">개발팀</li>
<li class="menu-item" role="option">인프라팀</li>
<li class="menu-item" role="option" aria-disabled="true">외주(권한 없음)</li>
</ul>
</div>
<div class="dropdown open">
<button type="button" class="trigger" aria-haspopup="listbox" aria-expanded="true">영업팀</button>
<ul class="menu" role="listbox" aria-label="그룹">
<li class="menu-item" role="option">전체</li>
<li class="menu-item on" role="option" aria-selected="true">영업팀</li>
<li class="menu-item" role="option">개발팀</li>
<li class="menu-item" role="option">인프라팀</li>
<li class="menu-item" role="option" aria-disabled="true">외주(권한 없음)</li>
</ul>
</div>CSS · .dropdown, .menu, .menu-item
/* JS Design System v0.6 · .dropdown, .menu, .menu-item · 토큰(var(--*))은 assets/style.css :root 참고 */
.dropdown{position:relative;display:inline-block}
.dropdown .trigger{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 12px 0 14px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface);font:inherit;font-size:14px;color:var(--text-primary);cursor:pointer;min-width:200px;justify-content:space-between}
.dropdown .trigger:hover{border-color:var(--border-strong)}
.dropdown .trigger .placeholder{color:var(--text-disabled)}
.dropdown .trigger::after{content:"";width:16px;height:16px;flex:none;background:var(--text-secondary);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M6 9l6 6l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M6 9l6 6l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;transition:transform var(--motion-fast)}
.dropdown.open .trigger{border-color:var(--border-focus)}
.dropdown.open .trigger::after{transform:rotate(180deg)}
.dropdown .trigger[disabled]{opacity:.4;cursor:not-allowed}
.dropdown .menu{position:absolute;top:calc(100% + 4px);left:0;display:none;z-index:5;min-width:100%}
.dropdown.open .menu{display:block}
.dropdown.up .menu{top:auto;bottom:calc(100% + 4px)}
.menu{min-width:200px;padding:6px;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);box-shadow:var(--shadow-2);list-style:none;margin:0;max-height:min(320px,60vh);overflow-y:auto;overscroll-behavior:contain}
.menu-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:6px;font-size:14px;cursor:pointer;color:var(--text-primary);white-space:nowrap}
.menu-item:hover{background:var(--bg-subtle)}
.menu-item.on{background:var(--accent-subtle);color:var(--accent-text);font-weight:600}
.menu-item.danger{color:var(--danger-text)}
.menu-item[aria-disabled="true"]{opacity:.4;cursor:not-allowed;pointer-events:none}
.menu-item .hint{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.menu-item svg{width:16px;height:16px;flex:none;color:var(--text-tertiary)}
.pagination-bar .dropdown .trigger{height:32px;min-width:120px;font-size:12px}React · @jiran/ds-react
import { useState } from "react";
import { Select } from "@jiran/ds-react";
const GROUPS = [
{ value: "all", label: "전체" },
{ value: "sales", label: "영업팀" },
{ value: "dev", label: "개발팀" },
{ value: "infra", label: "인프라팀" },
{ value: "ext", label: "외주(권한 없음)", disabled: true },
];
export default function Example() {
const [group, setGroup] = useState<string | null>("sales");
return (
<>
<Select options={GROUPS} placeholder="그룹 선택" label="그룹" keepMounted />
<Select options={GROUPS} value={group} onChange={setGroup} label="그룹" defaultOpen />
</>
);
}3. Disabled
권한이 없거나 선택할 값이 없는 상태입니다.
HTML
<div class="dropdown"><button type="button" class="trigger" aria-haspopup="listbox" aria-expanded="false" disabled><span class="placeholder">권한 없음</span></button></div>CSS · .dropdown, .menu, .menu-item
/* JS Design System v0.6 · .dropdown, .menu, .menu-item · 토큰(var(--*))은 assets/style.css :root 참고 */
.dropdown{position:relative;display:inline-block}
.dropdown .trigger{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 12px 0 14px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface);font:inherit;font-size:14px;color:var(--text-primary);cursor:pointer;min-width:200px;justify-content:space-between}
.dropdown .trigger:hover{border-color:var(--border-strong)}
.dropdown .trigger .placeholder{color:var(--text-disabled)}
.dropdown .trigger::after{content:"";width:16px;height:16px;flex:none;background:var(--text-secondary);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M6 9l6 6l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M6 9l6 6l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;transition:transform var(--motion-fast)}
.dropdown.open .trigger{border-color:var(--border-focus)}
.dropdown.open .trigger::after{transform:rotate(180deg)}
.dropdown .trigger[disabled]{opacity:.4;cursor:not-allowed}
.dropdown .menu{position:absolute;top:calc(100% + 4px);left:0;display:none;z-index:5;min-width:100%}
.dropdown.open .menu{display:block}
.dropdown.up .menu{top:auto;bottom:calc(100% + 4px)}
.menu{min-width:200px;padding:6px;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);box-shadow:var(--shadow-2);list-style:none;margin:0;max-height:min(320px,60vh);overflow-y:auto;overscroll-behavior:contain}
.menu-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:6px;font-size:14px;cursor:pointer;color:var(--text-primary);white-space:nowrap}
.menu-item:hover{background:var(--bg-subtle)}
.menu-item.on{background:var(--accent-subtle);color:var(--accent-text);font-weight:600}
.menu-item.danger{color:var(--danger-text)}
.menu-item[aria-disabled="true"]{opacity:.4;cursor:not-allowed;pointer-events:none}
.menu-item .hint{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.menu-item svg{width:16px;height:16px;flex:none;color:var(--text-tertiary)}
.pagination-bar .dropdown .trigger{height:32px;min-width:120px;font-size:12px}React · @jiran/ds-react
import { Select } from "@jiran/ds-react";
export default function Example() {
return <Select options={[]} placeholder="권한 없음" disabled />;
}Props
HTML 에서는 클래스와 속성으로, React(@jiran/ds-react)에서는 같은 이름의 prop(variant · size …)으로 변형·상태를 지정합니다.
| Prop / Class | Type · Values | Default | Description |
|---|---|---|---|
| .dropdown | container | — | position:relative. 열림은 .open |
| .trigger | button | — | 고른 값을 표시. 값이 없으면 안에 .placeholder |
| .menu | ul | — | role=listbox · aria-label 필수 |
| .menu-item | li | — | role=option. 고른 항목 .on + aria-selected, 비활성 aria-disabled |
Usage
React: @jiran/ds-react 패키지를 설치하고 style.css 를 한 번 import 하면 위 React 탭 코드를 그대로 쓸 수 있습니다(React Package). HTML: assets/style.css 를 연결하고(일부만 필요하면 아래 컴포넌트 CSS 복사), 동작이 필요하면 assets/ds.js 를 함께 넣습니다 — 이 페이지의 프리뷰가 그 스크립트로 동작합니다. 색은 전부 토큰(var(--accent) 등)을 참조하므로 --accent-* 만 제품 컬러로 바꾸면 됩니다.
React @jiran/ds-react
npm i @jiran/ds-react
// main.tsx — 앱 진입점에서 한 번
import "@jiran/ds-react/style.css";
// 컴포넌트
import { Select } from "@jiran/ds-react";HTML
<!-- 1) 토큰 + 전체 컴포넌트 스타일 한 번에 연결 -->
<link rel="stylesheet" href="assets/style.css">
<!-- 동작(드롭다운·탭·달력·표 정렬/선택·토스트 등) — 순수 JS 한 파일. React 제품은 불필요 -->
<script src="assets/ds.js" defer></script>
<!-- 2) 또는 아래 "Component CSS" 만 프로젝트 CSS 에 붙이고,
:root 토큰은 Foundations › Design Token 에서 복사 -->Component CSS .dropdown, .menu, .menu-item
/* JS Design System v0.6 · .dropdown, .menu, .menu-item · 토큰(var(--*))은 assets/style.css :root 참고 */
.dropdown{position:relative;display:inline-block}
.dropdown .trigger{display:inline-flex;align-items:center;gap:8px;height:40px;padding:0 12px 0 14px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface);font:inherit;font-size:14px;color:var(--text-primary);cursor:pointer;min-width:200px;justify-content:space-between}
.dropdown .trigger:hover{border-color:var(--border-strong)}
.dropdown .trigger .placeholder{color:var(--text-disabled)}
.dropdown .trigger::after{content:"";width:16px;height:16px;flex:none;background:var(--text-secondary);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M6 9l6 6l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2224%22 height=%2224%22 viewBox=%220 0 24 24%22 fill=%22none%22 stroke=%22black%22 stroke-width=%222%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M6 9l6 6l6 -6%22 /%3E%3C/svg%3E") center/contain no-repeat;transition:transform var(--motion-fast)}
.dropdown.open .trigger{border-color:var(--border-focus)}
.dropdown.open .trigger::after{transform:rotate(180deg)}
.dropdown .trigger[disabled]{opacity:.4;cursor:not-allowed}
.dropdown .menu{position:absolute;top:calc(100% + 4px);left:0;display:none;z-index:5;min-width:100%}
.dropdown.open .menu{display:block}
.dropdown.up .menu{top:auto;bottom:calc(100% + 4px)}
.menu{min-width:200px;padding:6px;background:var(--bg-surface);border:1px solid var(--border-default);border-radius:var(--radius-md);box-shadow:var(--shadow-2);list-style:none;margin:0;max-height:min(320px,60vh);overflow-y:auto;overscroll-behavior:contain}
.menu-item{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:6px;font-size:14px;cursor:pointer;color:var(--text-primary);white-space:nowrap}
.menu-item:hover{background:var(--bg-subtle)}
.menu-item.on{background:var(--accent-subtle);color:var(--accent-text);font-weight:600}
.menu-item.danger{color:var(--danger-text)}
.menu-item[aria-disabled="true"]{opacity:.4;cursor:not-allowed;pointer-events:none}
.menu-item .hint{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.menu-item svg{width:16px;height:16px;flex:none;color:var(--text-tertiary)}
.pagination-bar .dropdown .trigger{height:32px;min-width:120px;font-size:12px}