NumberInput

NumberInput

사용자로부터 숫자를 입력받는 컴포넌트예요. react-number-format을 기반으로 해요.

출처: 문서

본문

사용법 (Usage)

NumberInput은 react-number-format을 기반으로 해요. 원본 패키지의 NumericFormat 컴포넌트 prop 대부분을 지원해요.

NumberInput 컴포넌트는 Input 및 Input.Wrapper 컴포넌트 기능과 모든 input 요소 prop을 지원해요. NumberInput 문서에는 컴포넌트가 지원하는 모든 기능이 포함되지는 않아요. 사용 가능한 모든 기능은 Input 문서를 참고해요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput label="Quantity" placeholder="Enter quantity" />;
}

로딩 상태 (Loading state)

loading prop을 설정하면 로딩 인디케이터가 표시돼요. 기본적으로 로더는 인풋 오른쪽에 표시돼요. loadingPosition prop을 'left' 또는 'right'로 바꿔 위치를 변경할 수 있어요. API 호출, 검색, 검증 같은 비동기 작업에 유용해요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput label="Quantity" loading />;
}

제어 방식 (Controlled)

import { useState } from 'react';
import { NumberInput } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState<string | number>('');
  return <NumberInput value={value} onChange={setValue} />;
}

비제어 방식 (Uncontrolled)

NumberInput은 네이티브 input[type="number"]와 같은 방식으로 비제어 폼에서 사용할 수 있어요. 폼 제출 시 FormData 객체에 숫자 인풋 값을 포함하려면 name 속성을 설정해요. 비제어 폼에서 초기 값을 제어하려면 defaultValue prop을 사용해요.

FormData와 함께 비제어 NumberInput을 사용하는 예시:

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        const formData = new FormData(event.currentTarget);
        console.log('Number input value:', formData.get('quantity'));
      }}
    >
      <NumberInput name="quantity" />
      <button type="submit">Submit</button>
    </form>
  );
}

값 타입 (Value type)

value, defaultValue, onChange prop은 문자열 또는 숫자일 수 있어요. NumberInput 값을 숫자로 표현할 수 있는 모든 경우에 onChange 함수는 숫자로 호출돼요(예: 55, 1.28, -100). 하지만 값을 숫자로 표현할 수 없는 경우가 몇 가지 있어요.

  • 빈 상태는 빈 문자열 ''로 표현돼요
  • 단독 마이너스 부호는 문자열 '-'로 표현돼요
  • Number.MAX_SAFE_INTEGER - 1보다 크거나 Number.MIN_SAFE_INTEGER + 1보다 작은 숫자는 문자열로 표현돼요 – '90071992547409910'
  • 끝에 소수 구분 기호나 소수 0이 있는 숫자는 문자열로 표현돼요 – '0.', '0.0', '-0.00' 등

BigInt 값

NumberInput은 bigint 값도 지원해요. BigInt 모드는 value 또는 defaultValue에서 추론돼요.

  • value/defaultValue는 bigint | string
  • onChange는 bigint | string을 받음
  • min, max, step, startValue는 bigint 지원
  • BigInt 모드는 정수 전용(allowDecimal/소수 포맷팅 prop은 소수 파싱을 활성화하지 않음)

string은 중간 상태('' 또는 '-' 같은)의 폴백으로 여전히 사용돼요.

import { useState } from 'react';
import { NumberInput } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState(BigInt('12345678901234567890'));

  return <NumberInput value={value} onChange={setValue} />;
}

onChange vs onValueChange

NumberInput은 값 변경 처리를 위한 두 개의 콜백 prop을 제공해요.

  • onChange: 단순화된 값(number | string, BigInt 모드에서는 bigint | string)을 받아요. 대부분의 사용 사례에서 권장되는 콜백이에요. 값은 가능하면 숫자/bigint이고, 엣지 케이스(빈 인풋, 매우 큰 숫자, 끝 소수, 중간 BigInt 입력 상태)에서는 문자열이에요.
  • onValueChange: react-number-format의 전체 payload를 받아요. 여기에는 다음이 포함돼요:
    • floatValue: 숫자 값 (또는 undefined)
    • formattedValue: 형식화된 문자열 값 (prefix/suffix/구분 기호 포함)
    • value: 원시 비형식 문자열 값
    • 변경 소스에 대한 추가 메타데이터

형식화된 값이나 변경에 대한 메타데이터(예: 사용자 타이핑, 증감 버튼, 프로그램 방식 변경에서 온 것인지)에 접근해야 한다면 onValueChange를 사용해요. 간단한 폼 처리는 onChange로 충분해요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      prefix="$"
      onChange={(value) => console.log('Simple value:', value)}
      // onValueChange receives: { floatValue: 1234, formattedValue: '$1,234', value: '1234' }
      onValueChange={(payload) => console.log('Full payload:', payload)}
    />
  );
}

min과 max

min과 max prop으로 인풋 값을 제한할 수 있어요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput min={10} max={20} label="Enter value between 10 and 20" />;
}

클램프 동작 (Clamp behavior)

기본적으로 값은 인풋이 블러될 때 클램프돼요. clampBehavior="strict"로 설정하면 min/max 범위 밖의 값을 입력할 수 없어요. 이 옵션은 촘촘한 min과 max(예: min={10}과 max={20})가 있으면 문제가 생길 수 있어요. 값 클램핑을 완전히 비활성화해야 한다면 clampBehavior="none"을 설정해요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput min={0} max={100} clampBehavior="strict" defaultValue={50} />;
}

경계 콜백 (Boundary callbacks)

onMinReached와 onMaxReached를 사용해 값이 min 또는 max 경계에 도달할 때 함수를 호출할 수 있어요. 이 콜백은 사용자가 컨트롤이나 키보드 화살표로 max 이상 증가하거나 min 이하로 감소하려 할 때 트리거돼요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <NumberInput
      min={0}
      max={100}
      onMinReached={() => console.log('Minimum value reached')}
      onMaxReached={() => console.log('Maximum value reached')}
    />
  );
}

포커스 시 모두 선택 (Select all on focus)

selectAllOnFocus로 설정하면 필드가 포커스를 받을 때 전체 인풋 값을 자동으로 선택해요. 사용자가 값을 편집하기보다 교체할 것으로 예상될 때 유용해요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput selectAllOnFocus defaultValue="100" />;
}

접두사와 접미사 (Prefix and suffix)

prefix와 suffix prop으로 인풋 값의 시작이나 끝에 주어진 문자열을 추가할 수 있어요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput prefix="$ " label="With prefix" />
      <NumberInput suffix=" RUB" label="With suffix" mt="md" />
    </>
  );
}

음수 (Negative numbers)

기본적으로 음수는 허용돼요. allowNegative={false}로 설정하면 양수만 허용할 수 있어요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput allowNegative={false} label="Negative number are not allowed" />;
}

소수 (Decimal numbers)

기본적으로 소수는 허용돼요. allowDecimal={false}로 설정하면 정수만 허용할 수 있어요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput allowDecimal={false} label="Decimals are not allowed" />;
}

소수 자릿수 (Decimal scale)

decimalScale은 허용되는 소수 자릿수를 제어해요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput decimalScale={2} label="You can enter only 2 digits after decimal point" />;
}

고정 소수 자릿수 (Fixed decimal scale)

fixedDecimalScale로 설정하면 항상 고정된 소수 자릿수를 표시해요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput fixedDecimalScale decimalScale={2} defaultValue={4.5} label="Always show 2 digits after decimal point" />;
}

소수 구분 기호 (Decimal separator)

decimalSeparator로 소수 구분 기호 문자를 변경할 수 있어요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput decimalSeparator="," decimalScale={2} label="Custom decimal separator" />;
}

천 단위 구분 기호 (Thousand separator)

thousandSeparator prop으로 천 단위를 문자로 구분할 수 있어요. thousandsGroupStyle으로 그룹핑 로직을 제어할 수 있는데, thousand, lakh, wan, none 값을 받아요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput thousandSeparator label="Thousands are separated with a comma" />
      <NumberInput thousandSeparator=" " label="Thousands are separated with a space" mt="md" />
    </>
  );
}

블러 시 선행 0 제거 (Trim leading zeros on blur)

기본적으로 인풋이 포커스를 잃으면 선행 0이 제거돼요(예: 00100은 100이 됨). trimLeadingZeroesOnBlur={false}로 설정하면 이 동작을 비활성화할 수 있어요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput trimLeadingZeroesOnBlur label="Leading zeros removed on blur" />
      <NumberInput trimLeadingZeroesOnBlur={false} label="Leading zeros preserved" mt="md" />
    </>
  );
}

왼쪽·오른쪽 섹션 (Left and right sections)

NumberInput은 leftSection과 rightSection prop을 지원해요. 이 섹션들은 인풋 래퍼 안에서 절대 위치로 렌더링돼요. 아이콘, 인풋 컨트롤 또는 다른 요소를 표시하는 데 사용할 수 있어요.

섹션 스타일과 콘텐츠를 제어하려면 다음 prop을 사용할 수 있어요.

  • rightSection/leftSection – 인풋의 해당 쪽에 렌더링할 React 노드
  • rightSectionWidth/leftSectionWidth – 오른쪽 섹션의 너비와 인풋 해당 쪽의 패딩을 제어해요. 기본적으로 컴포넌트 size prop에 의해 제어돼요.
  • rightSectionPointerEvents/leftSectionPointerEvents – 섹션의 pointer-events 속성을 제어해요. 비대화형 요소를 렌더링하고 싶다면 none으로 설정해 클릭이 인풋으로 통과하게 해요.
import { NumberInput } from '@mantine/core';
import { CurrencyEthIcon } from '@phosphor-icons/react';

function Demo() {
  const icon = <CurrencyEthIcon size={16} />;
  return (
    <>
      <NumberInput leftSection={icon} label="With left section" />
      <NumberInput rightSection={icon} label="With right section" mt="md" />
    </>
  );
}

증감 컨트롤 (Increment/decrement controls)

기본적으로 오른쪽 섹션은 증가·감소 버튼이 차지해요. 숨기려면 hideControls prop을 설정해요. 오른쪽 섹션에 기본 컨트롤을 대체할 무엇이든 렌더링하려면 rightSection prop을 사용할 수도 있어요.

import { NumberInput } from '@mantine/core';
import { ChartScatterIcon } from '@phosphor-icons/react';

function Demo() {
  return (
    <>
      <NumberInput hideControls label="Hide controls" />
      <NumberInput
        rightSection={<ChartScatterIcon size={16} />}
        rightSectionPointerEvents="none"
        label="Custom right section"
        mt="md"
      />
    </>
  );
}

홀드 시 증감 (Increment/decrement on hold)

stepHoldDelay와 stepHoldInterval prop으로 증감 컨트롤을 클릭하고 누르고 있을 때의 동작을 정의할 수 있어요.

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput stepHoldDelay={500} stepHoldInterval={100} label="Step on hold" defaultValue={100} />
      <NumberInput
        stepHoldDelay={500}
        stepHoldInterval={(t) => Math.max(1000 / t ** 2, 25)}
        label="Step the value with interval function"
        defaultValue={100}
      />
    </>
  );
}

커스텀 증감 컨트롤 (Custom increment and decrement controls)

increment와 decrement 함수가 있는 ref를 얻어 커스텀 컨트롤을 만들 수 있어요.

import { useRef } from 'react';
import { NumberInput, Group, Button, NumberInputHandlers } from '@mantine/core';

function Demo() {
  const handlersRef = useRef<NumberInputHandlers>(null);
  return (
    <>
      <NumberInput
        value={100}
        step={2}
        handlersRef={handlersRef}
        label="Click buttons to change value"
      />
      <Group mt="md">
        <Button onClick={() => handlersRef.current?.decrement()} variant="default">
          Decrement by 2
        </Button>
        <Button onClick={() => handlersRef.current?.increment()} variant="default">
          Increment by 2
        </Button>
      </Group>
    </>
  );
}

오류 상태 (Error state)

import { NumberInput } from '@mantine/core';

function Demo() {
  return (
    <>
      <NumberInput label="Boolean error" error />
      <NumberInput label="With error message" error="Invalid name" mt="md" />
    </>
  );
}

성공 상태 (Success state)

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput label="Number Input" error="Looks good!" />;
}

비활성 상태 (Disabled state)

import { NumberInput } from '@mantine/core';

function Demo() {
  return <NumberInput label="Disabled input" disabled />;
}

Styles API

NumberInput은 Styles API를 지원해요. classNames prop으로 컴포넌트의 내부 요소에 스타일을 추가할 수 있어요.

Styles API 셀렉터:

  • wrapper – Input의 루트 요소
  • input – 인풋 요소
  • section – 왼쪽·오른쪽 섹션
  • bottomSection – 인풋 테두리 하단 안쪽에 렌더링되는 아래쪽 섹션 요소
  • root – 루트 요소
  • label – 라벨 요소
  • required – 라벨 안에 렌더링되는 필수 별표 요소
  • description – 설명 요소
  • error – 오류 요소
  • success – 성공 요소
  • controls – 증가·감소 버튼 래퍼
  • control – 증가·감소 버튼

요소 ref 가져오기 (Get element ref)

import { useRef } from 'react';
import { NumberInput } from '@mantine/core';

function Demo() {
  const ref = useRef<HTMLInputElement>(null);
  return <NumberInput ref={ref} />;
}

접근성 (Accessibility)

label prop 없이 NumberInput을 사용하면 스크린 리더가 제대로 알리지 못해요.

// Inaccessible input – screen reader will not announce it properly
import { NumberInput } from '@mantine/core';
function Demo() { return <NumberInput placeholder="Quantity" />; }

aria-label을 설정하면 인풋을 접근 가능하게 만들 수 있어요. 이 경우 라벨은 보이지 않지만 스크린 리더가 알려줘요.

// Accessible input – it has aria-label
import { NumberInput } from '@mantine/core';
function Demo() { return <NumberInput aria-label="Quantity" />; }

label prop이 설정되어 있으면 인풋은 접근 가능하며 aria-label을 설정할 필요가 없어요.

// Accessible input – it has associated label element
import { NumberInput } from '@mantine/core';
function Demo() { return <NumberInput label="Quantity" />; }

더 알아보기 (Learn more)