사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
CLI 레퍼런스▾
AkanJS 레퍼런스▾
UI 레퍼런스▾

폼 UI

akanjs/ui의 폼 컨트롤입니다. 모델 폼의 상태는 스토어의 <model>Form에 있고, 생성된 setter가 필드 하나씩 값을 바꿉니다. 컨트롤은 현재 값을 보여주고 다음 값을 돌려줄 뿐입니다.
이 페이지에서 쓰는 말
<model>Form
스토어가 들고 있는, 편집 중인 레코드의 초안입니다. 예를 들면 icecreamOrderForm입니다.
st.do.set<Field>On<Model>
자동으로 생성되는 폼 setter입니다. 초안의 필드 하나에 값을 넣습니다.
controlled (제어형)
받은 value를 보여주고 바뀐 값은 onChange로 돌려주는 방식입니다. Switch만 자체 상태로도 동작합니다.
에이전트 툴
인페이지 에이전트가 호출할 수 있는 동작입니다. 참조로 넘긴 폼 setter가 툴이 됩니다.
오버라이드 슬롯
_overrides.tsx에서 컴포넌트를 라우트 하위 트리 단위로 바꿔 끼울 때 쓰는 이름입니다.
컨트롤 고르기
위에서부터 고르세요. 모델 필드는 Field.*, 라벨 행이 필요 없으면 단독 컨트롤, 마지막 동작은 Button입니다.
컴포넌트
라벨 행
에이전트 툴
오버라이드 슬롯
모델 필드 — Template 안에서
Field.*
✓
✓
모델 필드 하나에 라벨이 붙은 컨트롤 하나입니다. Template 안에서 씁니다.
단독 컨트롤 — 검색창, 필터 바, 인라인 셀
Input
✓
✓
라벨 행이 없는 텍스트·숫자·비밀번호·이메일·체크박스 입력입니다.
Select
✓
✓
✓
단일·다중·검색 선택을 하는 드롭다운입니다. label은 넣어도 되고 빼도 됩니다.
Switch
✓
켜고 끄는 boolean 토글입니다. 라벨 행은 Field.Switch가 붙여 줍니다.
Radio
✓
짧은 목록에서 하나를 고르는 라디오 버튼입니다.
ToggleSelect
✓
버튼 줄에서 하나 또는 여러 개를 고릅니다. 툴 공개는 Field.ToggleSelect가 합니다.
DatePicker
✓
네이티브 입력으로 날짜·일시·기간·시각을 받습니다. 툴 공개는 Field.Date가 합니다.
동작 — 폼을 마무리
Button
✓
스스로는 아무것도 공개하지 않습니다. onClick에 넘긴 st.tool(...) 핸들러가 에이전트의 툴입니다.
✓있음없음
함께 볼 페이지

Field

모듈 Template을 쓸 때 사용하는 폼 필드 네임스페이스입니다. <Field> 자체는 라벨 행이 달린 섹션 래퍼이고, 각 멤버는 라벨이 붙은 컨트롤 하나입니다. 멤버끼리는 주로 value의 형태가 다릅니다.
속성과 API
Field{ label?, desc?, nullable?, className?, containerClassName?, labelClassName?, children? }
섹션 래퍼입니다. 라벨 행을 그리고, 그 아래에 자식을 gap-4 간격의 세로 열로 쌓습니다.
Field.Label{ label, desc?, unit?, nullable?, mode? }
라벨 행입니다. 문자열 라벨의 첫 글자를 대문자로 바꾸고, desc를 툴팁으로 달고, nullable이면 (optional)을 붙입니다.
Field.Text{ value: string | null, minlength? = 2, maxlength? = 200, inputStyleType? }
한 줄 텍스트입니다. inputStyleType은 bordered(기본), borderless, underline 중 하나입니다.
Field.TextArea{ value: string | null, rows? = 3, minlength? = 2, maxlength? = 1000 }
여러 줄 텍스트이며, 기본 높이는 세 줄입니다.
Field.Email{ value: string | null, maxlength? = 80, inputStyleType? }
올바른 이메일 주소여야 하는 텍스트입니다.
Field.Phone{ value: string | null, maxlength? = 13 }
전화번호여야 하는 텍스트입니다. 기본 transform이 하이픈을 넣어 형식을 맞춥니다.
Field.Password{ value, confirmValue?, onChangeConfirm?, showConfirm?, minlength? = 8, maxlength? = 20 }
가려진 텍스트이며, 눈 아이콘으로 보이기를 켜고 끕니다. showConfirm을 켜면 값이 같아야 하는 확인 칸이 붙습니다.
Field.Number{ value: number | null, min?, max?, unit?, formatter?, parser? }
숫자 하나입니다. unit은 라벨에 표시되고, formatter / parser가 화면에 보이는 숫자를 변환합니다.
Field.DoubleNumber{ value: [number, number] | null, min?, max?, separator? }
한 줄에 놓인 숫자 두 개이며, 범위·비율·좌표 같은 값에 씁니다. min / max도 두 값 쌍으로 넘깁니다.
Field.Date{ value: Dayjs | null, min?, max?, showTime? }
브라우저 네이티브 입력으로 받는 날짜 하나입니다. showTime을 켜면 시각까지 받습니다.
Field.DateRange{ from, to, onChangeFrom, onChangeTo, onChange?, min?, max?, showTime? }
기간의 양 끝입니다. onChange(from, to)는 양 끝이 모두 정해진 뒤에만 호출됩니다.
Field.Switch{ value: boolean | null, onDesc?, offDesc? }
라벨이 붙은 boolean입니다. onDesc / offDesc가 토글 옆에서 현재 상태를 설명합니다.
Field.ToggleSelect{ items, value: I | null, nullable?, validate?, btnClassName? }
버튼 줄에서 하나를 고릅니다. items에 enumOf(...)를 넘기면 값마다 번역된 라벨이 붙습니다.
Field.MultiToggleSelect{ items, value: I[] | null, minlength?, maxlength? }
같은 버튼 줄에서 여러 개를 고릅니다. minlength / maxlength를 어기면 번역된 안내가 뜹니다.
Field.TextList{ value: string[] | null, minlength?, maxlength?, minTextlength?, maxTextlength? }
순서가 있는 문자열 목록이며, 항목마다 입력칸이 있습니다. 드래그로 순서를 바꾸고 행마다 지울 수 있습니다.
Field.Tags{ value: string[] | null, minTextlength? = 2, maxTextlength? = 10 }
순서 없는 짧은 문자열을 배지로 그리고, 그 자리에서 추가하는 입력칸이 붙습니다.
Field.List{ value: Item[] | null, onAdd, renderItem: (item, idx) => ReactNode }
내장 객체 목록입니다. 행 하나만 그리면 테두리와 추가·삭제 버튼은 필드가 그립니다.
Field.Parent{ value: Light | null, slice, renderOption, onSearch? }
관련 모델 하나를 Light 인스턴스로 담습니다. 선택지는 fetch.slice.user 같은 slice에서 불러옵니다.
Field.ParentId{ value: string | null, slice, onChange: (id, model) => void }
ID 필드용 같은 선택기입니다. id만 담고, 고른 모델은 두 번째 인자로 넘겨 줍니다.
Field.Children{ value: Light[] | null, slice, renderOption }
관련 모델 여러 개를 Light 인스턴스로 담습니다.
Field.ChildrenId{ value: string[] | null, slice, renderOption }
관련 모델 여러 개를 id로 담습니다.
  • 공통 prop. 대부분의 멤버가 value / onChange, label / desc, nullable, disabled, placeholder, transform, validate, className / labelClassName / inputClassName을 받습니다.
  • 최소 길이. Text, TextArea, Email, Password는 minlength보다 짧은 입력에 오류를 띄웁니다. 기본값은 2이고, Password는 8, nullable이면 0입니다. 이니셜처럼 짧은 값이면 낮춰 주세요.
  • setter는 참조로. onChange={st.do.setNameOnUser}처럼 넘겨야 필드가 에이전트 툴로 공개됩니다. 인라인 화살표 함수는 똑같이 동작하지만 아무것도 공개하지 않습니다.
  • 래퍼도 괜찮습니다. 값을 변형하거나, 코드를 한 줄 더 실행하거나, writeOnX로 중첩 경로에 쓰는 래퍼는 써도 됩니다. 가능하면 컨트롤의 transform prop으로 정규화하고, 나머지는 st.tool로 직접 공개하세요.
  • lib에 멤버가 더 있습니다. @libs/shared/ui의 Field는 여기에 Rich, Coordinate, Postcode, Img / Imgs, File / Files를 더한 것입니다.
apps/koyo/lib/icecreamOrder/IcecreamOrder.Template.tsx

Input

라벨 행 없이 입력칸만 필요한 자리(검색창, 필터 바, 인라인 셀 편집)에 쓰는 제어형(controlled) 입력입니다. 멤버마다 오버라이드 슬롯이 따로 있어 Input, InputTextArea, InputPassword, InputEmail, InputNumber, InputCheckbox를 하나씩 따로 바꿔 입힐 수 있습니다.
속성과 API
valuestring
현재 값입니다. 입력칸은 값을 따로 복사해 두지 않습니다.
onChange(value, event?) => void
바뀐 문자열을 받습니다.
validate(value) => boolean | string
유효하면 true를 반환합니다. false나 메시지를 반환하면 입력칸 아래에 오류가 뜹니다.
nullableboolean
값이 비어 있어도 경고를 띄우지 않습니다.
inputStyleType"bordered" | "borderless" | "underline"기본값 "bordered"
입력칸을 그리는 표면 모양입니다.
iconReactNode
입력칸 앞에 붙는 아이콘입니다.
onPressEnter(value, event) => void
Enter를 누르면 호출됩니다. form 요소 없이 검색창을 만들 때 씁니다.
onPressEscape(event) => void
Escape를 누르면 입력칸의 포커스를 뺀 뒤 호출됩니다.
Input.TextArea / Password / Email{ value: string, validate, onChange? }
쓰는 법은 같지만 validate가 필수입니다. Email은 형식이 틀린 주소도 거절합니다.
Input.Number{ value: number | null, onChange, formatter?, parser? }
숫자 또는 null을 받습니다. formatter / parser가 화면에 보이는 글자를 변환합니다.
Input.Checkbox{ checked, onChange: (checked, event) => void }
primary 색을 입힌 네이티브 체크박스입니다.
  • 네이티브 속성은 그대로 전달됩니다. placeholder, maxLength, autoFocus 같은 속성은 <input>에 그대로 붙습니다.
apps/koyo/ui/Search.tsx

Select

일반 값, { label, value } 쌍, enumOf(...) 클래스를 받는 제어형 드롭다운이며 단일·다중·검색 선택을 지원합니다. 옵션 목록은 document.body에 portal로 따로 그려져 필드 너비에 맞춰 뜨므로, 스크롤되는 모달이나 테이블 안에서도 잘리지 않습니다.
속성과 API
valueT | T[]
선택된 값입니다. multiple을 켜면 배열입니다.
onChange(value, prev) => void
새 값과 직전 값을 받습니다.
optionsT[] | { label, value }[] | enumOf class
선택지입니다. enum은 원래 값을 그대로 보여주므로, 번역된 라벨이 필요하면 쌍으로 넘기세요.
label / descReactNode
필드 위에 붙는 선택적 라벨 행입니다. desc는 도움말 툴팁이 됩니다.
multipleboolean
여러 값을 고를 수 있게 합니다.
searchableboolean
라벨로 선택지를 거르는 검색칸을 붙입니다. 이때 문자열이 아닌 값은 쌍으로 넘겨야 합니다.
onSearch(text) => void
입력이 멈추고 300ms 뒤에 호출되며, 로컬 필터 대신 동작합니다.
nullableboolean
목록에 비우기 행을, 필드에 지우기 버튼을 붙입니다.
loadingboolean
선택지를 불러오는 동안 빈 목록 안내 대신 스피너를 보여줍니다.
onOpen() => void
목록이 열릴 때 호출됩니다. 선택지를 늦게 불러올 때 씁니다.
renderOption / renderSelected(value) => ReactNode
목록의 행과 고른 값을 직접 그립니다.
placeholder / emptystring / ReactNode
아무것도 고르지 않았을 때의 안내 문구와, 선택지가 없을 때 목록에 보일 내용입니다.
disabledboolean
목록 열기와 고르기를 막습니다. 공개된 툴도 함께 내려갑니다.
apps/koyo/ui/StatusFilter.tsx

Switch

<button role="switch">로 그린 boolean이라 포커스와 Space/Enter 토글을 브라우저가 처리합니다. checked를 넘기면 제어형으로, defaultChecked를 넘기면 자체 상태로 동작합니다. 라벨 행이 필요한 모델 필드라면 Field.Switch를 쓰세요.
속성과 API
checkedboolean
제어형으로 쓸 때의 상태입니다. 빼면 스위치가 자체 상태를 가집니다.
defaultCheckedboolean기본값 false
자체 상태로 동작할 때의 시작 위치입니다.
onChange(checked: boolean) => void
바뀐 위치를 받습니다. 폼 setter를 참조로 넘기면 에이전트에 공개됩니다.
variant"primary" | "accent" | "success"기본값 "primary"
켜졌을 때의 색입니다. 꺼졌을 때는 항상 bg-muted입니다.
disabledboolean
토글을 막고 흐리게 표시합니다. 공개된 툴도 함께 내려갑니다.
  • 툴이 되는 것은 폼 setter뿐입니다. set<Field>On<Model>는 공개되지만, 아래 setNotify 같은 일반 스토어 setter는 컨트롤에 data-akan-action / data-akan-state 표시만 붙습니다.
  • Dropdown 메뉴 항목 안에서는 <li>에 data-dropdown-keep-open을 붙이세요. 그래야 스위치를 눌러도 메뉴가 닫히지 않습니다.
apps/koyo/ui/NotifyToggle.tsx

Radio

role="radiogroup" 안의 role="radio" 버튼으로 하나를 고르며, 화살표 키를 누르면 포커스와 선택이 함께 움직입니다. 자식마다 자기 value를 갖고, 그룹은 그 값으로 선택을 맞춥니다. 숫자 value는 어떤 자식도 그 값을 갖지 않을 때만 index로 해석됩니다.
속성과 API
valuestring | number | null
선택된 자식의 value입니다. 그 값을 가진 자식이 없으면 위치로 읽습니다.
onChange(value, idx) => void
고른 자식의 value와 index를 받습니다. 화살표 키로 옮길 때도 호출됩니다.
disabledboolean
모든 선택지를 비활성화합니다.
childrenReactNode | ReactElement[]
선택지이며 보통 Radio.Item입니다. 점과 행은 그룹이 그리고, 자식은 본문만 그립니다.
Radio.Item{ value, children, className?, checked?, onChange? }
선택지 하나의 본문입니다. 그룹과 별개인 오버라이드 슬롯 RadioItem을 가집니다.
  • 에이전트 툴은 없습니다. 모델 필드라면 툴을 공개하는 Field.ToggleSelect를 쓰세요.
apps/koyo/ui/PlanPicker.tsx

ToggleSelect

드롭다운 대신 눌리는 버튼 줄로 고르는 선택이며, 선택지가 적고 짧을 때 맞습니다. ToggleSelect는 하나, ToggleSelect.Multi는 여러 개를 고릅니다. 버튼 줄에는 돌아갈 빈 상태가 없으므로 nullable과 validate는 필수이고, 호출하는 쪽에서 둘 다 정합니다.
속성과 API
itemsstring[] | number[] | { label, value, disabled? }[]
버튼 목록입니다. Field.ToggleSelect는 enumOf(...)도 받아 값마다 번역합니다.
valueI
선택된 값입니다.
nullableboolean
필수입니다. 선택을 비울 수 있는지 정하며, 켜면 선택된 버튼을 다시 눌렀을 때 onClear가 호출됩니다.
validate(value) => boolean | string
필수입니다. true를 반환하거나, 줄 아래에 띄울 메시지를 반환합니다.
onChange / onClear(value, idx) => void / () => void
고르기와 비우기입니다. onClear는 nullable일 때만 호출됩니다.
disabledboolean
모든 버튼을 비활성화합니다. 항목의 disabled는 그 버튼 하나만 막습니다.
renderItem(item, { selected, disabled, onToggle }) => ReactNode
버튼 하나를 직접 그립니다. onToggle이 그 칸의 동작이므로, 그린 요소에 직접 연결하세요.
ToggleSelect.Multi{ items, value: string[] | number[], nullable, validate, onChange }
여러 개를 고르는 형태이며, 별도의 오버라이드 슬롯 ToggleSelectMulti를 가집니다.
  • 에이전트 툴은 없습니다. 모델 필드라면 Field.ToggleSelect / Field.MultiToggleSelect를 쓰세요. 라벨 행을 붙이고 setter도 공개합니다.
apps/koyo/ui/SizePicker.tsx

DatePicker

브라우저 자체의 <input type="date">로 받는 날짜라, 달력·로케일·터치 키보드가 모두 플랫폼의 것입니다. min과 max는 브라우저가 지킵니다. 네이티브 필드는 특정 날짜만 흐리게 만들 수 없으므로, disabledDate에 걸린 선택은 경고 토스트와 함께 거절됩니다.
속성과 API
valueDayjs | null
현재 값입니다.
onChange(value: Dayjs | null) => void
바뀐 값을 받습니다.
showTimeboolean
네이티브 입력을 datetime-local로 바꿉니다.
min / maxDayjs | null
고를 수 있는 가장 이른 값과 가장 늦은 값이며, 브라우저가 지킵니다.
disabledDate(date: Dayjs) => boolean | null | undefined
거절할 날짜에 true를 반환합니다. 흐리게 보이지 않고, 고를 때 검사합니다.
defaultValueDayjs
피커가 마운트될 때, 그리고 이 값이 바뀔 때마다 onChange로 보내집니다.
DatePicker.RangePicker{ value: [Dayjs | null, Dayjs | null], onChange, showTime?, disabledDate? }
양 끝을 튜플 하나로 받습니다. 비어 있는 반대쪽은 현재 시각으로 채웁니다. 슬롯은 DatePickerRangePicker입니다.
DatePicker.TimePicker{ value: Dayjs | null, onChange, disabled?, disabledDate? }
시각만 받으며, 날짜는 value가 가진 날을 유지합니다. 슬롯은 DatePickerTimePicker입니다.
  • 에이전트 툴은 없습니다. 모델 필드라면 Field.Date / Field.DateRange를 쓰세요. 라벨 행이 달린 자체 네이티브 입력을 그리고 setter도 공개합니다.
apps/koyo/ui/PeriodFilter.tsx

Button

버튼 컴포넌트는 이것 하나뿐입니다. onClick이 동기면 평범한 버튼이고, promise를 반환하면 같은 버튼이 로딩·성공·오류 상태를 거치며 그동안 중복 클릭을 막습니다. 비동기용 버튼을 따로 고를 필요가 없습니다.
속성과 API
onClick(event, { onError }) => Promise<Result> | Result
선택입니다. promise를 반환하면 비동기 상태가 켜지고, 그 밖의 값이면 평범한 버튼으로 남습니다.
onSuccess(result) => void
성공 체크 표시가 0.7초 동안 보인 뒤 결과와 함께 호출됩니다.
loadingMode"hold" | "replace"기본값 "hold"
버튼 크기는 변하지 않습니다. hold는 스피너를 겹쳐 그리고, replace는 문구가 있는 표시로 교차 전환합니다.
showErrorboolean기본값 true
onError 메시지를 버튼 아래에 띄웁니다. 끄면 어디에도 표시되지 않으니 직접 토스트로 알리세요.
variant / size / shape / outlineButtonVariants기본값 "primary" / "md" / "default"
buttonRecipe의 모양입니다. 색, 크기, 모서리 모양, 외곽선 여부를 정합니다.
type"button" | "submit" | "reset"기본값 "button"
네이티브 type입니다. 기본값이 button이라 클릭해도 바깥 form이 제출되지 않습니다.
disabledboolean
네이티브 prop입니다. 로딩 중과 성공 체크가 보이는 동안에도 비활성화됩니다.
  • 던지지 않고 실패 알리기. onError("<dictionary key>")를 호출하세요. 성공 체크는 건너뛰고, 버튼 아래에 번역된 메시지가 뜹니다.
  • reject되면 조용히 돌아갑니다. 버튼은 체크 표시 없이 원래 상태로 돌아가고, 에러는 예외를 던진 스토어 액션이나 fetch가 이미 보여 줍니다.
  • 바꿔 입히는 방법은 둘입니다. 오버라이드 슬롯 Button은 컴포넌트 전체를, 레시피 슬롯 recipes.button은 모양만 바꿉니다.
apps/koyo/ui/Actions.tsx

MIT 라이선스 하에 배포되었습니다.

내 AI에 이 문서 연결하기

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js 모든 권리 보유.시스템 관리자bassman