입력 폼
정책·사용자·설정을 입력하는 폼의 기본 구조입니다. 라벨은 위, 필수는 *, 오류는 필드 아래, 저장은 오른쪽 아래.
Composition
이 패턴이 조합하는 컴포넌트입니다. 세부 옵션은 각 컴포넌트 페이지를 참고하세요.
1. 기본 폼
섹션 제목(.form-section)으로 그룹을 나누고, 마지막에 .form-actions.
HTML
<form class="form">
<div class="form-section">기본 정보</div>
<div class="field"><label for="pname">정책 이름 <em>*</em></label><input id="pname" placeholder="예: 외부 USB 차단" required><span class="help">2~40자</span></div>
<div class="field"><label for="pdesc">설명</label><textarea id="pdesc" placeholder="정책의 목적과 적용 범위"></textarea></div>
<div class="form-section">적용 대상</div>
<div class="form-row">
<div class="field"><label>그룹</label><div class="dropdown"><button type="button" class="trigger" aria-haspopup="listbox" aria-expanded="false">전체</button></div></div>
<div class="field"><label for="prio">우선순위</label><input id="prio" type="number" value="10" min="1" max="100"></div>
</div>
<fieldset class="radio-group" style="border:0;padding:0;margin:0"><legend class="t-label-1" style="margin-bottom:8px">탐지 시 동작</legend>
<label class="radio"><input type="radio" name="fa" checked><span>차단 후 알림</span></label>
<label class="radio"><input type="radio" name="fa"><span>알림만</span></label>
</fieldset>
<label class="checkbox"><input type="checkbox" checked><span>저장 즉시 배포<small>에이전트가 다음 체크인에서 적용합니다</small></span></label>
<div class="form-actions"><button type="button" class="btn md text">취소</button><button type="submit" class="btn md primary">정책 저장</button></div>
</form>CSS · .field, .dropdown, .menu, .menu-item, .checkbox, .checkbox-group, .radio, .radio-group, .btn, .btn-group, .form
/* JS Design System v0.6 · .field, .dropdown, .menu, .menu-item, .checkbox, .checkbox-group, .radio, .radio-group, .btn, .btn-group, .form · 토큰(var(--*))은 assets/style.css :root 참고 */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:44px;padding:0 20px;border-radius:var(--radius-md);border:1px solid transparent;font:inherit;font-size:14px;font-weight:600;line-height:1;cursor:pointer;white-space:nowrap;text-decoration:none;transition:background var(--motion-fast),border-color var(--motion-fast)}
.btn.sm{height:36px;padding:0 14px;font-size:12px}
.btn.md{height:44px;padding:0 20px;font-size:14px}
.btn.lg{height:52px;padding:0 26px;font-size:16px}
.btn.primary{background:var(--accent);color:var(--accent-on)}
.btn.primary:hover{background:var(--accent-hover)}
.btn.primary:active{background:var(--accent-pressed)}
.btn.secondary{background:transparent;border-color:var(--border-strong);color:var(--text-primary)}
.btn.secondary:hover{background:var(--fill-normal)}
.btn.secondary:active{background:var(--fill-strong)}
.btn.tertiary{background:var(--fill-alternative);color:var(--text-primary)}
.btn.tertiary:hover{background:var(--fill-normal)}
.btn.tertiary:active{background:var(--fill-strong)}
.btn.text{background:transparent;color:var(--accent-text);padding-left:8px;padding-right:8px}
.btn.text:hover{background:var(--accent-subtle)}
.btn.text:active{background:var(--fill-strong)}
.btn.danger{background:var(--danger);color:var(--danger-on)}
.btn.danger:hover{background:var(--danger-hover)}
.btn.danger:active{background:var(--danger-pressed)}
.btn.danger.secondary{background:transparent;color:var(--danger-text);border-color:var(--danger)}
.btn.danger.secondary:hover{background:var(--danger-subtle)}
.btn.danger.secondary:active{background:var(--fill-strong)}
.btn.icon{width:44px;padding:0}
.btn.icon.sm{width:36px}
.btn.icon.lg{width:52px}
.btn svg{width:18px;height:18px;flex:none}
.btn.sm svg{width:16px;height:16px}
.btn.lg svg{width:20px;height:20px}
.btn.block{width:100%}
.btn[disabled],.btn[aria-disabled="true"]{background:var(--interaction-disable);border-color:transparent;color:var(--text-disabled);cursor:not-allowed;pointer-events:none}
.btn.secondary[disabled],.btn.secondary[aria-disabled="true"]{background:transparent;border-color:var(--interaction-disable)}
.btn.danger.secondary[disabled],.btn.danger.secondary[aria-disabled="true"]{background:transparent;border-color:var(--interaction-disable);color:var(--text-disabled)}
.btn.text[disabled],.btn.text[aria-disabled="true"]{background:transparent}
.btn.loading{position:relative;color:transparent;pointer-events:none}
.btn.loading::after{content:"";position:absolute;width:16px;height:16px;border:2px solid var(--fill-strong);border-top-color:var(--accent-on);border-radius:50%;animation:spin .8s linear infinite}
.btn.secondary.loading::after,.btn.tertiary.loading::after{border-top-color:var(--accent)}
.btn.text.loading::after{border-top-color:var(--accent-text)}
.btn.danger.loading::after{border-top-color:var(--danger-on)}
.btn.danger.secondary.loading::after{border-top-color:var(--danger)}
.btn-group{display:inline-flex;gap:8px;align-items:center}
.btn-group.end{justify-content:flex-end;width:100%}
.field{display:flex;flex-direction:column;gap:6px;min-width:260px;font-size:14px}
.field label{font-weight:600}
.field label em{color:var(--danger-text);font-style:normal;margin-left:2px}
.field input,.field textarea{height:44px;border:1px solid var(--border-strong);border-radius:var(--radius-md);padding:0 14px;font:inherit;font-size:14px;background:var(--bg-surface);color:var(--text-primary);width:100%;transition:border-color var(--motion-fast)}
.field textarea{height:auto;min-height:96px;padding:10px 14px;resize:vertical;line-height:1.5}
.field input::placeholder,.field textarea::placeholder{color:var(--text-disabled)}
.field input:hover,.field textarea:hover{border-color:var(--border-strong)}
.field input:focus,.field textarea:focus-within{border-color:var(--border-focus)}
.field input:disabled,.field textarea:disabled{background:var(--bg-panel);color:var(--text-disabled);cursor:not-allowed}
.field input:read-only{background:var(--bg-subtle)}
.field .help{color:var(--text-tertiary);font-size:12px}
.field.error input,.field.error textarea,.field.error .affix{border-color:var(--danger)}
.field.error .help{color:var(--danger-text)}
.field.sm input{height:36px;font-size:12px;padding:0 12px}
.field.lg input{height:52px;font-size:16px;padding:0 16px}
.field .affix{display:flex;align-items:center;gap:8px;height:44px;padding:0 12px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface)}
.field .affix input{border:0;height:auto;padding:0;flex:1;background:transparent;outline:0;min-width:0}
.field .affix:focus-within{border-color:var(--border-focus)}
.field .affix span{color:var(--text-tertiary);font-size:12px;white-space:nowrap}
.field .counter{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.field .row{display:flex;justify-content:space-between;align-items:baseline}
.checkbox{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.checkbox input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-surface);display:grid;place-items:center;cursor:inherit;transition:background var(--motion-fast),border-color var(--motion-fast)}
.checkbox input:hover{border-color:var(--border-strong)}
.checkbox input:checked,.checkbox input:indeterminate,.checkbox input.mixed{background:var(--accent);border-color:var(--accent)}
.checkbox input:checked::after{content:"";width:12px;height:12px;background:var(--accent-on);-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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:indeterminate::after,.checkbox input.mixed::after{content:"";width:12px;height:12px;background:var(--accent-on);-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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:disabled{opacity:.4;cursor:not-allowed}
.checkbox input:disabled+span{color:var(--text-disabled)}
.checkbox small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.checkbox-group{display:flex;flex-direction:column;gap:12px}
.checkbox-group.row{flex-direction:row;gap:24px}
.radio{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.radio input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:50%;background:var(--bg-surface);cursor:inherit;display:grid;place-items:center;transition:background var(--motion-fast),border-color var(--motion-fast)}
.radio input:hover{border-color:var(--border-strong)}
.radio input:checked{border-color:var(--accent);background:var(--accent)}
.radio input:checked::after{content:"";width:8px;height:8px;border-radius:50%;background:var(--accent-on)}
.radio input:disabled{opacity:.4;cursor:not-allowed}
.radio input:disabled+span{color:var(--text-disabled)}
.radio small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.radio-group{display:flex;flex-direction:column;gap:12px}
.radio-group.row{flex-direction:row;gap:24px}
.radio.card{padding:14px 16px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--bg-surface);min-width:220px;flex-direction:row;gap:8px;box-shadow:none;position:static}
.radio.card:has(input:checked){border-color:var(--accent);background:var(--accent-subtle)}
.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}
.banner .btn{margin-left:auto}
.tbl .check .checkbox{vertical-align:middle}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.form{display:flex;flex-direction:column;gap:20px;width:100%;max-width:640px;background:var(--bg-surface);padding:24px;border-radius:var(--radius-lg);box-shadow:var(--shadow-1)}
.form .form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.form .form-row .field{min-width:0}
.form .form-section{font-size:16px;font-weight:600;margin:4px 0 -8px;padding-top:8px}
.form .form-actions{display:flex;justify-content:flex-end;gap:8px;padding-top:16px;border-top:1px solid var(--border-default)}
.form .form-actions .left{margin-right:auto}
.form .field{min-width:0}
.empty .btn{margin-top:10px}
@keyframes spin{to{transform:rotate(360deg)}}React · @jiran/ds-react
import { Button, Checkbox, Field, Form, FormActions, FormRow, FormSection, Radio, RadioGroup, Select, TextArea, TextField } from "@jiran/ds-react";
const GROUPS = [{ value: "all", label: "전체" }, { value: "sales", label: "영업팀" }, { value: "dev", label: "개발팀" }];
export default function Example() {
return (
<Form onSubmit={(e) => e.preventDefault()}>
<FormSection>기본 정보</FormSection>
<TextField id="pname" label="정책 이름" required placeholder="예: 외부 USB 차단" help="2~40자" />
<TextArea id="pdesc" label="설명" placeholder="정책의 목적과 적용 범위" />
<FormSection>적용 대상</FormSection>
<FormRow>
<Field label="그룹">
<Select options={GROUPS} defaultValue="all" label="그룹" />
</Field>
<TextField id="prio" label="우선순위" type="number" defaultValue="10" min={1} max={100} />
</FormRow>
<RadioGroup legend="탐지 시 동작" name="fa" defaultValue="block">
<Radio value="block">차단 후 알림</Radio>
<Radio value="notify">알림만</Radio>
</RadioGroup>
<Checkbox defaultChecked description="에이전트가 다음 체크인에서 적용합니다">저장 즉시 배포</Checkbox>
<FormActions>
<Button variant="text">취소</Button>
<Button type="submit">정책 저장</Button>
</FormActions>
</Form>
);
}2. 오류 상태
제출 후 오류. 필드 오류 + 상단 요약 Notice(오류 3개 이상일 때).
HTML
<form class="form" novalidate>
<div class="notice danger" role="alert" style="margin-bottom:-4px"><span class="ico"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg></span><div><b>입력값 2개를 확인해 주세요</b>정책 이름, 허용 IP</div></div>
<div class="field error"><label for="e1">정책 이름 <em>*</em></label><input id="e1" aria-invalid="true" aria-describedby="e1h"><span class="help" id="e1h">정책 이름을 입력하세요</span></div>
<div class="field error"><label for="e2">허용 IP</label><input id="e2" value="10.0.0" aria-invalid="true" aria-describedby="e2h"><span class="help" id="e2h">올바른 IPv4 형식이 아닙니다. 예: 10.0.0.12</span></div>
<div class="form-actions"><button type="button" class="btn md danger secondary left">삭제</button><button type="button" class="btn md text">취소</button><button type="submit" class="btn md primary">정책 저장</button></div>
</form>CSS · .field, .dropdown, .menu, .menu-item, .checkbox, .checkbox-group, .radio, .radio-group, .btn, .btn-group, .form
/* JS Design System v0.6 · .field, .dropdown, .menu, .menu-item, .checkbox, .checkbox-group, .radio, .radio-group, .btn, .btn-group, .form · 토큰(var(--*))은 assets/style.css :root 참고 */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:44px;padding:0 20px;border-radius:var(--radius-md);border:1px solid transparent;font:inherit;font-size:14px;font-weight:600;line-height:1;cursor:pointer;white-space:nowrap;text-decoration:none;transition:background var(--motion-fast),border-color var(--motion-fast)}
.btn.sm{height:36px;padding:0 14px;font-size:12px}
.btn.md{height:44px;padding:0 20px;font-size:14px}
.btn.lg{height:52px;padding:0 26px;font-size:16px}
.btn.primary{background:var(--accent);color:var(--accent-on)}
.btn.primary:hover{background:var(--accent-hover)}
.btn.primary:active{background:var(--accent-pressed)}
.btn.secondary{background:transparent;border-color:var(--border-strong);color:var(--text-primary)}
.btn.secondary:hover{background:var(--fill-normal)}
.btn.secondary:active{background:var(--fill-strong)}
.btn.tertiary{background:var(--fill-alternative);color:var(--text-primary)}
.btn.tertiary:hover{background:var(--fill-normal)}
.btn.tertiary:active{background:var(--fill-strong)}
.btn.text{background:transparent;color:var(--accent-text);padding-left:8px;padding-right:8px}
.btn.text:hover{background:var(--accent-subtle)}
.btn.text:active{background:var(--fill-strong)}
.btn.danger{background:var(--danger);color:var(--danger-on)}
.btn.danger:hover{background:var(--danger-hover)}
.btn.danger:active{background:var(--danger-pressed)}
.btn.danger.secondary{background:transparent;color:var(--danger-text);border-color:var(--danger)}
.btn.danger.secondary:hover{background:var(--danger-subtle)}
.btn.danger.secondary:active{background:var(--fill-strong)}
.btn.icon{width:44px;padding:0}
.btn.icon.sm{width:36px}
.btn.icon.lg{width:52px}
.btn svg{width:18px;height:18px;flex:none}
.btn.sm svg{width:16px;height:16px}
.btn.lg svg{width:20px;height:20px}
.btn.block{width:100%}
.btn[disabled],.btn[aria-disabled="true"]{background:var(--interaction-disable);border-color:transparent;color:var(--text-disabled);cursor:not-allowed;pointer-events:none}
.btn.secondary[disabled],.btn.secondary[aria-disabled="true"]{background:transparent;border-color:var(--interaction-disable)}
.btn.danger.secondary[disabled],.btn.danger.secondary[aria-disabled="true"]{background:transparent;border-color:var(--interaction-disable);color:var(--text-disabled)}
.btn.text[disabled],.btn.text[aria-disabled="true"]{background:transparent}
.btn.loading{position:relative;color:transparent;pointer-events:none}
.btn.loading::after{content:"";position:absolute;width:16px;height:16px;border:2px solid var(--fill-strong);border-top-color:var(--accent-on);border-radius:50%;animation:spin .8s linear infinite}
.btn.secondary.loading::after,.btn.tertiary.loading::after{border-top-color:var(--accent)}
.btn.text.loading::after{border-top-color:var(--accent-text)}
.btn.danger.loading::after{border-top-color:var(--danger-on)}
.btn.danger.secondary.loading::after{border-top-color:var(--danger)}
.btn-group{display:inline-flex;gap:8px;align-items:center}
.btn-group.end{justify-content:flex-end;width:100%}
.field{display:flex;flex-direction:column;gap:6px;min-width:260px;font-size:14px}
.field label{font-weight:600}
.field label em{color:var(--danger-text);font-style:normal;margin-left:2px}
.field input,.field textarea{height:44px;border:1px solid var(--border-strong);border-radius:var(--radius-md);padding:0 14px;font:inherit;font-size:14px;background:var(--bg-surface);color:var(--text-primary);width:100%;transition:border-color var(--motion-fast)}
.field textarea{height:auto;min-height:96px;padding:10px 14px;resize:vertical;line-height:1.5}
.field input::placeholder,.field textarea::placeholder{color:var(--text-disabled)}
.field input:hover,.field textarea:hover{border-color:var(--border-strong)}
.field input:focus,.field textarea:focus-within{border-color:var(--border-focus)}
.field input:disabled,.field textarea:disabled{background:var(--bg-panel);color:var(--text-disabled);cursor:not-allowed}
.field input:read-only{background:var(--bg-subtle)}
.field .help{color:var(--text-tertiary);font-size:12px}
.field.error input,.field.error textarea,.field.error .affix{border-color:var(--danger)}
.field.error .help{color:var(--danger-text)}
.field.sm input{height:36px;font-size:12px;padding:0 12px}
.field.lg input{height:52px;font-size:16px;padding:0 16px}
.field .affix{display:flex;align-items:center;gap:8px;height:44px;padding:0 12px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface)}
.field .affix input{border:0;height:auto;padding:0;flex:1;background:transparent;outline:0;min-width:0}
.field .affix:focus-within{border-color:var(--border-focus)}
.field .affix span{color:var(--text-tertiary);font-size:12px;white-space:nowrap}
.field .counter{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.field .row{display:flex;justify-content:space-between;align-items:baseline}
.checkbox{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.checkbox input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-surface);display:grid;place-items:center;cursor:inherit;transition:background var(--motion-fast),border-color var(--motion-fast)}
.checkbox input:hover{border-color:var(--border-strong)}
.checkbox input:checked,.checkbox input:indeterminate,.checkbox input.mixed{background:var(--accent);border-color:var(--accent)}
.checkbox input:checked::after{content:"";width:12px;height:12px;background:var(--accent-on);-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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:indeterminate::after,.checkbox input.mixed::after{content:"";width:12px;height:12px;background:var(--accent-on);-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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:disabled{opacity:.4;cursor:not-allowed}
.checkbox input:disabled+span{color:var(--text-disabled)}
.checkbox small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.checkbox-group{display:flex;flex-direction:column;gap:12px}
.checkbox-group.row{flex-direction:row;gap:24px}
.radio{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.radio input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:50%;background:var(--bg-surface);cursor:inherit;display:grid;place-items:center;transition:background var(--motion-fast),border-color var(--motion-fast)}
.radio input:hover{border-color:var(--border-strong)}
.radio input:checked{border-color:var(--accent);background:var(--accent)}
.radio input:checked::after{content:"";width:8px;height:8px;border-radius:50%;background:var(--accent-on)}
.radio input:disabled{opacity:.4;cursor:not-allowed}
.radio input:disabled+span{color:var(--text-disabled)}
.radio small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.radio-group{display:flex;flex-direction:column;gap:12px}
.radio-group.row{flex-direction:row;gap:24px}
.radio.card{padding:14px 16px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--bg-surface);min-width:220px;flex-direction:row;gap:8px;box-shadow:none;position:static}
.radio.card:has(input:checked){border-color:var(--accent);background:var(--accent-subtle)}
.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}
.banner .btn{margin-left:auto}
.tbl .check .checkbox{vertical-align:middle}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.form{display:flex;flex-direction:column;gap:20px;width:100%;max-width:640px;background:var(--bg-surface);padding:24px;border-radius:var(--radius-lg);box-shadow:var(--shadow-1)}
.form .form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.form .form-row .field{min-width:0}
.form .form-section{font-size:16px;font-weight:600;margin:4px 0 -8px;padding-top:8px}
.form .form-actions{display:flex;justify-content:flex-end;gap:8px;padding-top:16px;border-top:1px solid var(--border-default)}
.form .form-actions .left{margin-right:auto}
.form .field{min-width:0}
.empty .btn{margin-top:10px}
@keyframes spin{to{transform:rotate(360deg)}}React · @jiran/ds-react
import { Button, Form, FormActions, Notice, TextField } from "@jiran/ds-react";
export default function Example() {
return (
<Form noValidate onSubmit={(e) => e.preventDefault()}>
<Notice tone="danger" title="입력값 2개를 확인해 주세요" style={{ marginBottom: -4 }}>정책 이름, 허용 IP</Notice>
<TextField id="e1" label="정책 이름" required error="정책 이름을 입력하세요" />
<TextField id="e2" label="허용 IP" defaultValue="10.0.0" error="올바른 IPv4 형식이 아닙니다. 예: 10.0.0.12" />
<FormActions>
<Button variant="danger-secondary" className="left">삭제</Button>
<Button variant="text">취소</Button>
<Button type="submit">정책 저장</Button>
</FormActions>
</Form>
);
}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 { Form, FormSection, FormRow, FormActions, Field, TextField, TextArea, Select, Checkbox, CheckboxGroup, Radio, RadioGroup, Button, ButtonGroup } 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 .field, .dropdown, .menu, .menu-item, .checkbox, .checkbox-group, .radio, .radio-group, .btn, .btn-group, .form
/* JS Design System v0.6 · .field, .dropdown, .menu, .menu-item, .checkbox, .checkbox-group, .radio, .radio-group, .btn, .btn-group, .form · 토큰(var(--*))은 assets/style.css :root 참고 */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:44px;padding:0 20px;border-radius:var(--radius-md);border:1px solid transparent;font:inherit;font-size:14px;font-weight:600;line-height:1;cursor:pointer;white-space:nowrap;text-decoration:none;transition:background var(--motion-fast),border-color var(--motion-fast)}
.btn.sm{height:36px;padding:0 14px;font-size:12px}
.btn.md{height:44px;padding:0 20px;font-size:14px}
.btn.lg{height:52px;padding:0 26px;font-size:16px}
.btn.primary{background:var(--accent);color:var(--accent-on)}
.btn.primary:hover{background:var(--accent-hover)}
.btn.primary:active{background:var(--accent-pressed)}
.btn.secondary{background:transparent;border-color:var(--border-strong);color:var(--text-primary)}
.btn.secondary:hover{background:var(--fill-normal)}
.btn.secondary:active{background:var(--fill-strong)}
.btn.tertiary{background:var(--fill-alternative);color:var(--text-primary)}
.btn.tertiary:hover{background:var(--fill-normal)}
.btn.tertiary:active{background:var(--fill-strong)}
.btn.text{background:transparent;color:var(--accent-text);padding-left:8px;padding-right:8px}
.btn.text:hover{background:var(--accent-subtle)}
.btn.text:active{background:var(--fill-strong)}
.btn.danger{background:var(--danger);color:var(--danger-on)}
.btn.danger:hover{background:var(--danger-hover)}
.btn.danger:active{background:var(--danger-pressed)}
.btn.danger.secondary{background:transparent;color:var(--danger-text);border-color:var(--danger)}
.btn.danger.secondary:hover{background:var(--danger-subtle)}
.btn.danger.secondary:active{background:var(--fill-strong)}
.btn.icon{width:44px;padding:0}
.btn.icon.sm{width:36px}
.btn.icon.lg{width:52px}
.btn svg{width:18px;height:18px;flex:none}
.btn.sm svg{width:16px;height:16px}
.btn.lg svg{width:20px;height:20px}
.btn.block{width:100%}
.btn[disabled],.btn[aria-disabled="true"]{background:var(--interaction-disable);border-color:transparent;color:var(--text-disabled);cursor:not-allowed;pointer-events:none}
.btn.secondary[disabled],.btn.secondary[aria-disabled="true"]{background:transparent;border-color:var(--interaction-disable)}
.btn.danger.secondary[disabled],.btn.danger.secondary[aria-disabled="true"]{background:transparent;border-color:var(--interaction-disable);color:var(--text-disabled)}
.btn.text[disabled],.btn.text[aria-disabled="true"]{background:transparent}
.btn.loading{position:relative;color:transparent;pointer-events:none}
.btn.loading::after{content:"";position:absolute;width:16px;height:16px;border:2px solid var(--fill-strong);border-top-color:var(--accent-on);border-radius:50%;animation:spin .8s linear infinite}
.btn.secondary.loading::after,.btn.tertiary.loading::after{border-top-color:var(--accent)}
.btn.text.loading::after{border-top-color:var(--accent-text)}
.btn.danger.loading::after{border-top-color:var(--danger-on)}
.btn.danger.secondary.loading::after{border-top-color:var(--danger)}
.btn-group{display:inline-flex;gap:8px;align-items:center}
.btn-group.end{justify-content:flex-end;width:100%}
.field{display:flex;flex-direction:column;gap:6px;min-width:260px;font-size:14px}
.field label{font-weight:600}
.field label em{color:var(--danger-text);font-style:normal;margin-left:2px}
.field input,.field textarea{height:44px;border:1px solid var(--border-strong);border-radius:var(--radius-md);padding:0 14px;font:inherit;font-size:14px;background:var(--bg-surface);color:var(--text-primary);width:100%;transition:border-color var(--motion-fast)}
.field textarea{height:auto;min-height:96px;padding:10px 14px;resize:vertical;line-height:1.5}
.field input::placeholder,.field textarea::placeholder{color:var(--text-disabled)}
.field input:hover,.field textarea:hover{border-color:var(--border-strong)}
.field input:focus,.field textarea:focus-within{border-color:var(--border-focus)}
.field input:disabled,.field textarea:disabled{background:var(--bg-panel);color:var(--text-disabled);cursor:not-allowed}
.field input:read-only{background:var(--bg-subtle)}
.field .help{color:var(--text-tertiary);font-size:12px}
.field.error input,.field.error textarea,.field.error .affix{border-color:var(--danger)}
.field.error .help{color:var(--danger-text)}
.field.sm input{height:36px;font-size:12px;padding:0 12px}
.field.lg input{height:52px;font-size:16px;padding:0 16px}
.field .affix{display:flex;align-items:center;gap:8px;height:44px;padding:0 12px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--bg-surface)}
.field .affix input{border:0;height:auto;padding:0;flex:1;background:transparent;outline:0;min-width:0}
.field .affix:focus-within{border-color:var(--border-focus)}
.field .affix span{color:var(--text-tertiary);font-size:12px;white-space:nowrap}
.field .counter{margin-left:auto;font-size:12px;color:var(--text-tertiary)}
.field .row{display:flex;justify-content:space-between;align-items:baseline}
.checkbox{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.checkbox input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:var(--radius-sm);background:var(--bg-surface);display:grid;place-items:center;cursor:inherit;transition:background var(--motion-fast),border-color var(--motion-fast)}
.checkbox input:hover{border-color:var(--border-strong)}
.checkbox input:checked,.checkbox input:indeterminate,.checkbox input.mixed{background:var(--accent);border-color:var(--accent)}
.checkbox input:checked::after{content:"";width:12px;height:12px;background:var(--accent-on);-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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l5 5l10 -10%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:indeterminate::after,.checkbox input.mixed::after{content:"";width:12px;height:12px;background:var(--accent-on);-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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%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=%223.5%22 stroke-linecap=%22round%22 stroke-linejoin=%22round%22 %3E%3Cpath d=%22M5 12l14 0%22 /%3E%3C/svg%3E") center/contain no-repeat}
.checkbox input:disabled{opacity:.4;cursor:not-allowed}
.checkbox input:disabled+span{color:var(--text-disabled)}
.checkbox small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.checkbox-group{display:flex;flex-direction:column;gap:12px}
.checkbox-group.row{flex-direction:row;gap:24px}
.radio{display:inline-flex;align-items:flex-start;gap:8px;font-size:14px;cursor:pointer;line-height:1.4}
.radio input{appearance:none;-webkit-appearance:none;flex:none;width:16px;height:16px;margin:2px 0 0;border:1.5px solid var(--border-strong);border-radius:50%;background:var(--bg-surface);cursor:inherit;display:grid;place-items:center;transition:background var(--motion-fast),border-color var(--motion-fast)}
.radio input:hover{border-color:var(--border-strong)}
.radio input:checked{border-color:var(--accent);background:var(--accent)}
.radio input:checked::after{content:"";width:8px;height:8px;border-radius:50%;background:var(--accent-on)}
.radio input:disabled{opacity:.4;cursor:not-allowed}
.radio input:disabled+span{color:var(--text-disabled)}
.radio small{display:block;color:var(--text-tertiary);font-size:12px;font-weight:400}
.radio-group{display:flex;flex-direction:column;gap:12px}
.radio-group.row{flex-direction:row;gap:24px}
.radio.card{padding:14px 16px;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--bg-surface);min-width:220px;flex-direction:row;gap:8px;box-shadow:none;position:static}
.radio.card:has(input:checked){border-color:var(--accent);background:var(--accent-subtle)}
.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}
.banner .btn{margin-left:auto}
.tbl .check .checkbox{vertical-align:middle}
.tbl .actions .btn{height:32px;padding:0 10px;font-size:12px}
.action-bar .btn{height:32px;padding:0 12px;font-size:12px}
.form{display:flex;flex-direction:column;gap:20px;width:100%;max-width:640px;background:var(--bg-surface);padding:24px;border-radius:var(--radius-lg);box-shadow:var(--shadow-1)}
.form .form-row{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.form .form-row .field{min-width:0}
.form .form-section{font-size:16px;font-weight:600;margin:4px 0 -8px;padding-top:8px}
.form .form-actions{display:flex;justify-content:flex-end;gap:8px;padding-top:16px;border-top:1px solid var(--border-default)}
.form .form-actions .left{margin-right:auto}
.form .field{min-width:0}
.empty .btn{margin-top:10px}
@keyframes spin{to{transform:rotate(360deg)}}