바로 실행해보기: ⚡ StackBlitz — Vanilla · Vue · React | 📦 CodeSandbox
"문자열인데 숫자야." 업무 시스템엔 이런 필드가 꼭 있습니다 — 금액, 수량,
단가. 그리고 매번 같은 걸 손으로 다시 만들죠: 실시간 천 단위 콤마(10,000),
앞자리 0 제거, 오른쪽 정렬, 모바일 숫자 키패드, 그리고 콤마가 생기고 사라져도
튀지 않는 커서까지.
numkey는 그 인풋을 한 번에 끝냅니다:
- 커서 안전 실시간 포맷팅 —
1,234,567중간에 타이핑해도 그룹이 재배치되는 동안 커서가 기대한 자리에 그대로 - 문자열 우선 값 모델 — 정식 값은 순수 숫자 문자열(
"1234567.89"). IEEE 754 부동소수점을 거치지 않아 금액에 안전 - 앞자리 0 정리(
007→7), 전각 숫자 정규화(123→123— 한글/일본어 IME), 붙여넣기 정제(₩ 1,234원→1234) - 오른쪽 정렬 +
inputmode자동 설정 (옵트아웃 가능) - IME 안전 — 조합 중에는 값을 건드리지 않음
- 의존성 0, TypeScript 우선, ESM/CJS 듀얼 + CDN 글로벌 빌드
- 순수
<script>(JSP/PHP 등 서버 렌더링), Vue 3, React 모두 지원
같은 폼에서 한/영 키 오타도 처리해야 한다면? numkey의 형제 kokey를 보세요.
스크립트 태그 한 줄, 나머지는 마크업입니다. 나중에 DOM에 추가되는 인풋까지 자동으로 바인딩됩니다.
<script src="https://cdn.jsdelivr.net/npm/@devslab/numkey"></script>
<input data-numkey> <!-- 정수: 1,234,567 -->
<input data-numkey="2"> <!-- 소수 2자리: 1,234.56 -->
<input data-numkey data-numkey-negative> <!-- 음수 허용 -->
<input data-numkey data-numkey-align="left"> <!-- 왼쪽 정렬 유지 -->서버가 렌더링한 값(<input data-numkey value="1234567">)은 로드 시
포맷됩니다. 제출 전에 원본 값을 읽으려면:
<script>
const raw = numkey.getValue(document.querySelector('#amount')) // "1234567"
</script>(또는 서버에서 콤마를 제거해도 됩니다 — POST되는 값은 표시 값입니다.)
data-numkey가 스위치입니다. 인풋을 바인딩시키는 것이 바로 이 속성이고
(자동 초기화가 input[data-numkey]를 감시합니다), 값은 최대 소수 자릿수를
겸합니다 — 빈 값이면 정수 전용. 나머지 data-numkey-* 속성들은 전부
data-numkey가 있는 인풋에서만 읽히는 옵션이라, 단독으로는 아무것도 하지
않습니다:
<input data-numkey> <!-- ON, 정수: 1,234,567 -->
<input data-numkey="2"> <!-- ON, 소수 2자리: 1,234.56 -->
<input data-numkey="2" data-numkey-negative> <!-- 옵션은 겹쳐 씀 -->
<input data-numkey-locale="auto"> <!-- ✗ 아무것도 안 함 — data-numkey 없음 -->
<input> <!-- 평범한 인풋, 건드리지 않음 -->| 속성 | 의미 |
|---|---|
data-numkey |
스위치 — 인풋을 바인딩; 값은 최대 소수 자릿수 (빈 값 = 정수) |
data-numkey-negative |
앞자리 마이너스 허용 |
data-numkey-align="left" |
자동 오른쪽 정렬 옵트아웃 |
data-numkey-group="4" |
그룹 크기 (기본 3, 만 단위는 4) |
data-numkey-separator=" " |
그룹 구분자 (기본 ,) |
data-numkey-point="," |
필드에 표시되는 소수점 (기본 .) |
data-numkey-locale |
로케일에서 구분자 유도 — 아래 참조 |
data-numkey-korean |
실시간 한글 금액 병기 ("150만") — 아래 참조 |
data-numkey-korean-entry |
만/억 축약 입력 허용 ("3만5천" → blur → "35,000") — 아래 참조 |
data-numkey-name="amount" |
정식 값을 전송하는 hidden 인풋 — 아래 참조 |
data-numkey-min / data-numkey-max |
최소/최대 — blur 시에만 적용 (입력 중 간섭 없음) |
기본 동작은 고정 표시입니다: 방문자 브라우저 설정이 무엇이든 모두가
1,234,567.89를 봅니다 — 업무 폼이 보통 원하는 동작이죠.
data-numkey-locale을 붙인 필드만 로케일 구분자를 따릅니다:
<!-- 모두가 1,234,567.89 — 기본값, 로케일 무관 -->
<input data-numkey="2">
<!-- 방문자의 브라우저 언어를 따름:
독일어 브라우저 → 1.234.567,89
한국어 브라우저 → 1,234,567.89 -->
<input data-numkey="2" data-numkey-locale="auto">
<!-- 모든 방문자에게 독일식으로 고정 -->
<input data-numkey="2" data-numkey-locale="de-DE">로케일이 바꾸는 건 그리는 방식뿐입니다. 정식 값은 항상
"1234567.89" — 세 경우 모두 numkey.getValue(el)이 같은 문자열을
돌려줍니다. 일반 폼 POST는 표시 값을 전송하므로, 로케일을 쓰는 폼은
data-numkey-name(아래)으로 전송하거나 서버에서 정규화하세요.
은행·핀테크 UI가 금액 필드 옆에 그리는 "150만" 힌트 — 프로젝트마다 손으로 다시 만드는 바로 그것:
<input data-numkey data-numkey-korean>
<!-- 1500000 입력 시:
<input value="1,500,000"> <span class="numkey-korean">150만</span> -->
<input data-numkey data-numkey-korean="#my-hint"> <!-- 기존 요소 사용 -->속성이 빈 값이면 인풋 바로 뒤에 <span class="numkey-korean">을 생성합니다
(무스타일 — "원"은 CSS로: .numkey-korean::after { content: " 원" }).
값을 주면 기존 요소의 CSS 셀렉터로 해석합니다. 같은 엔진을 순수 함수로도
쓸 수 있습니다:
import { toKorean } from '@devslab/numkey'
toKorean('1500000') // "150만"
toKorean('927483041001') // "9,274억 8,304만 1,001"
toKorean('100000001') // "1억 1" — 0인 그룹은 생략역방향도 됩니다 — 부동산·주식 앱에서 실제로 입력하는 축약형을 순수 함수로, 또는 필드에서 직접:
import { fromKorean } from '@devslab/numkey'
fromKorean('3만5천') // "35000"
fromKorean('1.5억') // "150000000"
fromKorean('삼십오만') // "350000"<input data-numkey data-numkey-korean-entry>
<!-- 3만5천 입력 중에는 건드리지 않고 (IME 조합과 안 싸움),
blur에 35,000으로 변환 -->getValue와 data-numkey-name hidden 동기화(아래)는 초안이 화면에 있는
동안에도 파싱된 값을 봅니다.
일반 폼 POST는 필드에 보이는 그대로 — 1,234,567 — 전송하고, 서버는 매번
콤마를 제거해야 합니다. data-numkey-name은 정리된 정식 값을 항상 담고
있는 hidden 인풋을 생성합니다:
<form method="post">
<!-- 보이는 인풋에는 name이 없고, hidden이 amount=1234567을 전송 -->
<input data-numkey data-numkey-name="amount" value="1234567">
</form>hidden 값은 입력 중간 상태에서도 정리된 상태를 유지하므로 (1,234.는
1234로 전송), 어느 순간 제출되든 서버는 깨끗한 숫자를 받습니다.
type="text"인풋을 쓰세요. numkey가inputmode를 설정해 모바일에서 숫자 키패드가 뜹니다.type="number"는 커서 API가 없어 포맷팅과 충돌합니다.
npm install @devslab/numkeyimport { format, parse, bind, observe } from '@devslab/numkey'
format('1234567.5', { decimals: 2 }) // "1,234,567.5"
parse('₩ 1,234,567원') // "1234567"
bind(document.querySelector('#amount'), { decimals: 2 }) // 요소 하나
observe() // 모든 [data-numkey]<script setup>
import { ref } from 'vue'
import { NumkeyInput, vNumkey } from '@devslab/numkey/vue'
const amount = ref('') // 항상 정식 값: "1234567"
</script>
<template>
<!-- v-model은 정식 값을 받고, 필드에는 1,234,567이 보입니다 -->
<NumkeyInput v-model="amount" :decimals="2" negative />
<!-- 일반 인풋에는 디렉티브 -->
<input v-numkey="2">
</template>import { NumkeyInput, useNumkey } from '@devslab/numkey/react'
// Controlled: value/onValueChange는 정식 값 문자열로 통신
const [amount, setAmount] = useState('')
<NumkeyInput value={amount} onValueChange={setAmount} decimals={2} negative />
// Uncontrolled: ref 콜백 훅
<input ref={useNumkey({ decimals: 2 })} defaultValue="1234567" />| 옵션 | 기본값 | |
|---|---|---|
decimals |
0 |
최대 소수 자릿수 (0 = 정수만) |
negative |
false |
앞자리 마이너스 허용 |
group |
3 |
그룹당 자릿수 (만 단위 그룹핑은 4) |
separator |
"," |
표시용 그룹 구분자 |
decimalPoint |
"." |
표시용 소수점 (정식 값은 항상 .) |
locale |
— | 옵트인: Intl로 separator/decimalPoint 유도 — "auto"(브라우저 언어) 또는 BCP 47 태그. 지정하지 않으면 방문자 브라우저와 무관하게 표시가 고정됩니다 (업무 폼의 기본 요구). 명시한 separator/decimalPoint가 우선. |
min / max |
— | 최소/최대 — blur 시에만 적용. 입력 도중 클램핑하면 min이 10인 필드에 50을 칠 수 없게 되니까요 |
parse(display, opts?) |
표시 값/붙여넣기 → 정식 값 "1234567.89" |
format(canonical, opts?) |
정식 값 → 표시 값 "1,234,567.89" |
finalize(canonical) |
입력 중간 상태 정리 ("1234." → "1234") |
toKorean(canonical, opts?) |
한글 금액 병기 ("1500000" → "150만") |
fromKorean(text) |
만/억 축약 → 정식 값 ("3만5천" → "35000") |
bind(el, opts?) |
실시간 포맷팅 연결; 해제 함수 반환 |
observe(root?) |
현재/미래의 모든 [data-numkey] 바인딩 |
getValue(el, opts?) |
바인딩된 인풋의 정식 값 |
setValue(el, canonical, opts?) |
정식 값을 포맷된 표시 값으로 기록 |
applyToInput(el, opts?) |
커서 보존 1회 재포맷 (빌딩 블록) |
createRefBinder(opts?) |
어떤 프레임워크에서든 쓰는 ref 콜백 팩토리 |
- 유럽식 포맷도 옵션으로 지원:
{ separator: '.', decimalPoint: ',' }이면1.234.567,89로 표시되고 정식 값은"1234567.89"로 유지됩니다. - 구분자 옆 백스페이스/Delete는 한 번에 인접한 숫자를 지웁니다 (구분자는 건너뛰고, 재포맷이 나머지를 처리).
- 로드맵: 인도식 lakh 그룹핑 (
12,34,567— 비균일 그룹 크기).
이슈·PR 환영합니다 — 개발 환경 셋업과 규칙(문자열 우선 값 모델, IME 안전, 한 글자씩 타이핑하는 regression 테스트)은 CONTRIBUTING.md를 보세요.
MIT © devslab
