数字输入
Mantine Core 的独立组件,请不要替换为 JGuang UI/Base UI 的同名 API。
安装
npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1
入口引入 @mantine/core/styles.css,并在根组件使用 MantineProvider;通过 forceColorScheme 同步深浅色主题。
使用与验证
在左侧选择组件,在预览中操作它的状态;使用“查看代码”检查真实源码。安装后执行类型检查和构建,再验证键盘焦点、手机布局与深浅色主题。
来源
https://github.com/mantinedev/mantine/tree/f0e2bc6d28b0f0b8aaefb5b859937bf77f539df7 MIT 许可证;不包括付费内容。下方提供上游英文参考内容,中文使用指南已适配;上游详细参考暂保留英文。
版本与兼容性
固定的 Mantine Core 9.7.1 要求 React 和 React DOM 满足 ^19.2.0,并配套 @mantine/hooks@9.7.1。此条件来自实际 npm 包的 peerDependencies。React 18 或其他不兼容版本不能强行安装;先向项目负责人说明升级影响并确认方案,不使用 --force/--legacy-peer-deps 掩盖冲突。入口的原生 Provider 开启 theme.respectReducedMotion,并用 forceColorScheme 控制深浅色。
适用场景
通过格式化及步进操作编辑数值。
本地示例使用的原生参数
以下参数由本地、经过类型检查的 JSX 示例提取,按所属组件区分。不是全部属性清单;完整签名和继承关系见下方固定源码参考。
| 组件参数 | 说明 |
|---|---|
NumberInput.disabled | 禁用用户输入或操作,不等同于只读。 |
NumberInput.error | 本地错误展示,本身不执行校验。 |
NumberInput.readOnly | 阻止编辑并保留只读交互。 |
NumberInput.label | 可见标签或当前组件的标签内容。 |
NumberInput.defaultValue | 非受控初始值,不是持续更新的受控值。 |
NumberInput.min | 数值下界。 |
交互与状态检查
当前场景:默认、禁用、校验错误、只读。切换支持的禁用、错误或只读场景后检查真实行为;无此原生状态的组件不会被强行添加统一属性。验证键盘焦点和退出方式,修改值后按原生回调签名连接业务状态。
业务接入边界
onChange 可能返回数值或字符串,包括空值和输入中的中间状态;不要在每次按键时强制 Number 转换。number/bigint 模式以固定版本的泛型类型为准。
宿主配置
先安装固定版本 @mantine/core 和 @mantine/hooks,在入口引入 @mantine/core/styles.css,并使用 MantineProvider。示例的 theme 和 variant 是 JGuang 编写的宿主参数,不是所有原生组件都接受的统一 API;通过 forceColorScheme 同步应用主题。源码面板中的原生实现仅供阅读,使用 npm 导出,不要单独复制内部实现。此批只覆盖 Core,不包括日期、图表和表单扩展包。上游详细说明保留英文,不宣称全文中文化。
可运行用法
以下代码使用资源原生 API;不是把其他设计系统的同名组件替换进来。
import { useState } from 'react';
import * as M from '@mantine/core';
import '@mantine/core/styles.css';
function ComponentPreview({ variant = 'Default' }: { variant?: string }) {
const disabled = variant === "Disabled";
const invalid = variant === "Invalid";
const readOnly = variant === "Read only";
return <M.NumberInput disabled={disabled} error={invalid ? "Check the quantity / 请检查数量" : undefined} readOnly={readOnly} label="Quantity / 数量" defaultValue={5} min={0} />;
}
export default function Example({ variant = 'Default', theme = 'light' }: { variant?: string; theme?: 'light' | 'dark' }) {
return <M.MantineProvider forceColorScheme={theme} theme={{ respectReducedMotion: true }}><ComponentPreview variant={variant} /></M.MantineProvider>;
}
可用状态与示例
默认、禁用、校验错误、只读
示例遵循 Mantine 原生 API。支持多状态的输入组件可将示例中的 variant 改为 Disabled、Invalid 或 Read only;这些只是示例标签,不是新增的组件属性。改造时保留组件要求的数据、受控值、store 和复合子组件结构。
Usage
NumberInput is based on react-number-format.
It supports most of the props from the NumericFormat component in the original package.
Loading state
Set loading prop to display a loading indicator. By default, the loader is displayed on the right side of the input.
You can change the position with the loadingPosition prop to 'left' or 'right'. This is useful for async operations like API calls, searches, or validations:
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 can be used with uncontrolled forms the same way as a native input[type="number"].
Set the name attribute to include number input value in FormData object on form submission.
To control the initial value in uncontrolled forms, use the defaultValue prop.
Example usage of uncontrolled NumberInput with FormData:
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
label="Enter quantity"
name="quantity"
defaultValue="1"
min="1"
max="100"
/>
<button type="submit">Submit</button>
</form>
);
}
Value type
The value, defaultValue, and onChange props can be either string or number. In all cases
when the NumberInput value can be represented as a number, the onChange function is called
with a number (for example 55, 1.28, -100, etc.). But there are several cases when
it is not possible to represent the value as a number:
- Empty state is represented as an empty string –
'' - A standalone minus sign is represented as a string –
'-' - Numbers that are larger than
Number.MAX_SAFE_INTEGER - 1or smaller thanNumber.MIN_SAFE_INTEGER + 1are represented as strings –'90071992547409910' - Numbers with trailing decimal separators or trailing decimal zeros are represented as strings –
'0.','0.0','-0.00', etc.
BigInt values
NumberInput also supports bigint values. BigInt mode is inferred from value or defaultValue:
value/defaultValuecan bebigint | stringonChangereceivesbigint | stringmin,max,step, andstartValuesupportbigint- BigInt mode is integer-only (
allowDecimal/decimal formatting props do not enable decimal parsing)
string is still used as a fallback for intermediate states (for example '' or '-').
onChange vs onValueChange
NumberInput provides two callback props for handling value changes:
-
onChange: Receives a simplified value (number | stringin default mode,bigint | stringin BigInt mode). This is the recommended callback for most use cases. The value is a number/bigint when possible, and a string in edge cases (empty input, very large numbers, trailing decimals, intermediate BigInt input states). -
onValueChange: Receives the full payload fromreact-number-format, which includes:floatValue: The numeric value (orundefined)formattedValue: The formatted string value (with prefix/suffix/separators)value: The raw unformatted string value- Additional metadata about the change source
Use onValueChange when you need access to the formatted value or metadata about the change (e.g., whether it came from user typing, increment/decrement buttons, or programmatic changes). For simple form handling, onChange is sufficient.
import { NumberInput } from '@mantine/core';
function Demo() {
return (
<NumberInput
prefix="$"
thousandSeparator=","
// onChange receives: 1234
onChange={(value) => console.log('Simple value:', value)}
// onValueChange receives: { floatValue: 1234, formattedValue: '$1,234', value: '1234' }
onValueChange={(payload) => console.log('Full payload:', payload)}
/>
);
}
min and max
Set the min and max props to limit the input value:
Clamp behavior
By default, the value is clamped when the input is blurred. If you set clampBehavior="strict",
it will not be possible to enter a value outside of the min/max range. Note that this option
may cause issues if you have tight min and max, for example min={10} and max={20}.
If you need to disable value clamping entirely, set clampBehavior="none".
Boundary callbacks
Use onMinReached and onMaxReached to call a function when the value hits the min or max boundary.
These callbacks are triggered when the user attempts to increment beyond max or decrement below min
using the controls or keyboard arrows.
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
Set selectAllOnFocus to automatically select the entire input value when the field receives focus.
This is useful when you expect users to replace the value rather than edit it:
import { NumberInput } from '@mantine/core';
function Demo() {
return <NumberInput selectAllOnFocus defaultValue={100} />;
}
Prefix and suffix
Set the prefix and suffix props to add a given string to the start or end of the input value:
Negative numbers
By default, negative numbers are allowed. Set allowNegative={false} to allow only positive numbers.
Decimal numbers
By default, decimal numbers are allowed. Set allowDecimal={false} to allow only integers.
Decimal scale
The decimalScale controls how many decimal places are allowed:
Fixed decimal scale
Set fixedDecimalScale to always display a fixed number of decimal places:
Decimal separator
Set decimalSeparator to change the decimal separator character:
Thousand separator
Set the thousandSeparator prop to separate thousands with a character. You can control
the grouping logic with thousandsGroupStyle, which accepts: thousand, lakh, wan, none values.
Trim leading zeros on blur
By default, leading zeros are removed when the input loses focus (e.g., 00100 becomes 100).
You can disable this behavior by setting trimLeadingZeroesOnBlur={false}:
Increment/decrement controls
By default, the right section is occupied by increment and decrement buttons.
To hide them, set the hideControls prop. You can also use the rightSection prop to render anything
in the right section to replace the default controls.
Increment/decrement on hold
Set the stepHoldDelay and stepHoldInterval props to define behavior when increment/decrement controls are clicked and held:
Custom increment and decrement controls
You can get a ref with increment and decrement functions to create custom controls:
Error state
Success state
Disabled state
源码参数参考
以下实现签名和类型从本地固定版本源码生成。保留可选属性、原生继承类型、联合分支和 never 限制;不会将其他设计系统的 API 混入。forwardRef 实现中的 ref 属于 React 引用,不是新增业务属性。此区域是原始类型参考,不是上游说明的全文翻译。
NumberInput(_props: NumberInputProps<T>)
export interface NumberInputHandlers {
increment: () => void;
decrement: () => void;
}
export type NumberInputNumericType = number | bigint;
export type NumberInputValue<T extends NumberInputNumericType = number> = T | string;
type NumberInputNumericValue<T extends NumberInputNumericType = number> = T;
export type NumberInputStylesNames = 'controls' | 'control' | __InputStylesNames;
export type NumberInputCssVariables = {
controls: '--ni-chevron-size';
};
export interface NumberInputProps<T extends NumberInputNumericType = number>
extends
BoxProps,
Omit<__BaseInputProps, 'pointer'>,
StylesApiProps<NumberInputFactory>,
ElementProps<
'input',
'size' | 'type' | 'onChange' | 'value' | 'defaultValue' | 'min' | 'max' | 'step'
> {
/** Controlled component value */
value?: NumberInputValue<T>;
/** Uncontrolled component default value */
defaultValue?: NumberInputValue<T>;
/** Called when value changes */
onChange?: (value: NumberInputValue<T>) => void;
/** Called when value changes with `react-number-format` payload */
onValueChange?: OnValueChange;
/** Determines whether leading zeros are allowed during input. If `false`, leading zeros are removed as you type (e.g., typing `007` results in `7`). Works in conjunction with `trimLeadingZeroesOnBlur`. @default true */
allowLeadingZeros?: boolean;
/** Determines whether negative numbers are allowed. If `false`, the input will not accept negative values, and the decrement button will stop at `0` (when `min` is not set). @default true */
allowNegative?: boolean;
/** Characters which when pressed result in a decimal separator. These characters will be replaced by the `decimalSeparator` in the input value. @default ['.', ','] */
allowedDecimalSeparators?: string[];
/** Limits the number of digits that can be entered after the decimal point @default Infinity */
decimalScale?: number;
/** Character used as a decimal separator. Generally used with `allowedDecimalSeparators` prop. @default '.' */
decimalSeparator?: string;
/** If `true`, automatically pads the decimal part with zeros to match `decimalScale` (e.g., with `decimalScale={2}`, typing `5.1` displays as `5.10`). Requires `decimalScale` to be set. @default false */
fixedDecimalScale?: boolean;
/** Prefix added before the input value */
prefix?: string;
/** Suffix added after the input value */
suffix?: string;
/** Defines the thousand grouping style. 'thousand' (1,000), 'lakh' (1,00,000), 'wan' (1,0000), 'none'. */
thousandsGroupStyle?: 'thousand' | 'lakh' | 'wan' | 'none';
/** A function to validate the input value. If this function returns `false`, the `onChange` will not be called and the input value will not change. */
isAllowed?: (values: NumberFormatValues) => boolean;
/** Advanced: Set to `true` if you're passing numeric strings (e.g., `"12345"`) and using formatting props like `prefix` or `suffix`. In most cases, you don't need this prop. See [react-number-format docs](https://www.npmjs.com/package/react-number-format) for details. @default false */
valueIsNumericString?: boolean;
/** Controls input `type` attribute @default 'text' */
type?: 'text' | 'tel' | 'password';
/** A character used to separate thousands */
thousandSeparator?: string | boolean;
/** Minimum possible value */
min?: NumberInputNumericValue<T>;
/** Maximum possible value */
max?: NumberInputNumericValue<T>;
/** Number by which value will be incremented/decremented with up/down controls and keyboard arrows @default 1 */
step?: NumberInputNumericValue<T>;
/** If set, the up/down controls are hidden @default false */
hideControls?: boolean;
/** Controls how values are clamped to the `min`/`max` range:
* - `'blur'` (default): User can type any value, but it's clamped when the input loses focus
* - `'strict'`: User cannot type values outside the range
* - `'none'`: No clamping; `min`/`max` only apply to increment/decrement controls and arrow keys
*/
clampBehavior?: 'strict' | 'blur' | 'none';
/** If set, decimal values are allowed @default true */
allowDecimal?: boolean;
/** Increment/decrement handlers */
handlersRef?: React.Ref<NumberInputHandlers | undefined>;
/** Value used when incrementing/decrementing an empty input. If `min` is set and `startValue < min`, `min` is used instead. @default 0 */
startValue?: NumberInputNumericValue<T>;
/** Interval in milliseconds between value steps when increment/decrement button is held down. Can be a number or a function `(stepCount) => number` for dynamic intervals. Requires `stepHoldDelay` to be set. @default undefined */
stepHoldInterval?: number | ((stepCount: number) => number);
/** Initial delay in milliseconds before stepping the value. */
stepHoldDelay?: number;
/** If set, up/down keyboard events increment/decrement value @default true */
withKeyboardEvents?: boolean;
/** If set, leading zeros are removed on blur. For example, `00100` -> `100` @default true */
trimLeadingZeroesOnBlur?: boolean;
/** If set, all text is selected when the input receives focus @default false */
selectAllOnFocus?: boolean;
/** Called when the increment button or arrow up key is pressed and the value has reached the maximum */
onMinReached?: () => void;
/** Called when the decrement button or arrow down key is pressed and the value has reached the minimum */
onMaxReached?: () => void;
}
export type NumberInputFactory = Factory<{
props: NumberInputProps;
ref: HTMLInputElement;
stylesNames: NumberInputStylesNames;
vars: NumberInputCssVariables;
variant: InputVariant;
signature: <T extends NumberInputNumericType = number>(
props: NumberInputProps<T>
) => React.JSX.Element;
}>;
export type Mod = Record<string, any> | string;
export type BoxMod = Mod | Mod[] | BoxMod[];
export interface BoxProps extends MantineStyleProps {
/** Class added to the root element, if applicable */
className?: string | undefined;
/** Inline style added to root component element, can subscribe to theme defined on MantineProvider */
style?: MantineStyleProp;
/** CSS variables defined on root component element */
__vars?: CssVarsProp;
/** `size` property passed down the HTML element */
__size?: string;
/** Breakpoint above which the component is hidden with `display: none` */
hiddenFrom?: MantineBreakpoint;
/** Breakpoint below which the component is hidden with `display: none` */
visibleFrom?: MantineBreakpoint;
/** Determines whether component should be hidden in light color scheme with `display: none` */
lightHidden?: boolean;
/** Determines whether component should be hidden in dark color scheme with `display: none` */
darkHidden?: boolean;
/** Element modifiers transformed into `data-` attributes, for example, `{ 'data-size': 'xl' }`, falsy values are removed */
mod?: BoxMod;
}
export type ElementProps<
ElementType extends React.ElementType,
PropsToOmit extends string = never,
> = Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>;
export interface CSSProperties extends React.CSSProperties {
[key: string]: any;
}
type MantineStyle = CSSProperties | ((theme: MantineTheme) => CSSProperties);
export type MantineStyleProp = MantineStyle | MantineStyle[] | MantineStyleProp[] | undefined;
export type CssVariable = `--${string}`;
export type CssVariables<Variable extends string = CssVariable> = Partial<Record<Variable, string>>;
export type CssVars<Variable extends string = CssVariable> =
| CssVariables<Variable>
| ((theme: MantineTheme) => CssVariables<Variable>)
| CssVars<Variable>[];
export type CssVarsProp<Variable extends string = CssVariable> =
| CssVars<Variable>
| CssVars<Variable>[];
type WrapperProps = React.ComponentProps<'div'> & DataAttributes;
export interface __BaseInputProps extends __InputWrapperProps, __InputProps {
/** Props passed down to the root element */
wrapperProps?: WrapperProps;
}
export type __InputStylesNames = InputStylesNames | InputWrapperStylesNames;
export type InputStylesNames = 'input' | 'wrapper' | 'section' | 'bottomSection';
export type InputVariant = 'default' | 'filled' | 'unstyled';
export interface __InputProps {
/** Content section displayed on the left side of the input */
leftSection?: React.ReactNode;
/** Left section width, used to set `width` of the section and input `padding-left`, by default equals to the input height */
leftSectionWidth?: React.CSSProperties['width'];
/** Props passed down to the `leftSection` element */
leftSectionProps?: React.ComponentProps<'div'>;
/** Sets `pointer-events` styles on the `leftSection` element. Use `'all'` when section contains interactive elements (buttons, links). @default 'none' */
leftSectionPointerEvents?: React.CSSProperties['pointerEvents'];
/** Content section displayed on the right side of the input */
rightSection?: React.ReactNode;
/** Right section width, used to set `width` of the section and input `padding-right`, by default equals to the input height */
rightSectionWidth?: React.CSSProperties['width'];
/** Props passed down to the `rightSection` element */
rightSectionProps?: React.ComponentProps<'div'>;
/** Sets `pointer-events` styles on the `rightSection` element. Use `'all'` when section contains interactive elements (buttons, links). @default 'none' */
rightSectionPointerEvents?: React.CSSProperties['pointerEvents'];
/** Sets `required` attribute on the `input` element */
required?: boolean;
/** Key of `theme.radius` or any valid CSS value to set `border-radius`, numbers are converted to rem @default theme.defaultRadius */
radius?: MantineRadius;
/** Sets `disabled` attribute on the `input` element */
disabled?: boolean;
/** Controls input `height`, horizontal `padding`, and `font-size` @default 'sm' */
size?: MantineSize | (string & {});
/** Determines whether the input should have `cursor: pointer` style. Use when input acts as a button-like trigger (e.g., `component="button"` for Select/DatePicker). @default false */
pointer?: boolean;
/** Determines whether the input should have red border and red text color when the `error` prop is set @default true */
withErrorStyles?: boolean;
/** Determines whether the input should have green border when the `success` prop is set @default true */
withSuccessStyles?: boolean;
/** HTML `size` attribute for the input element (number of visible characters) */
inputSize?: string;
/** Section to be displayed when the input is `__clearable` and `rightSection` is not defined */
__clearSection?: React.ReactNode;
/** Determines whether the `__clearSection` should be displayed if it is passed to the component, has no effect if `rightSection` is defined */
__clearable?: boolean;
/** Determines how the clear button and rightSection are rendered @default 'both' */
__clearSectionMode?: ClearSectionMode;
/** Right section displayed when both `__clearSection` and `rightSection` are not defined */
__defaultRightSection?: React.ReactNode;
/** Displays loading indicator in the left or right section @default false */
loading?: boolean;
/** Position of the loading indicator @default 'right' */
loadingPosition?: 'left' | 'right';
/** Content section displayed at the bottom of the input, inside the border */
__bottomSection?: React.ReactNode;
/** Props passed down to the `__bottomSection` element */
__bottomSectionProps?: React.ComponentProps<'div'>;
}
export type InputWrapperStylesNames =
| 'root'
| InputLabelStylesNames
| InputDescriptionStylesNames
| InputErrorStylesNames
| InputSuccessStylesNames;
export interface __InputWrapperProps {
/** Contents of `Input.Label` component. If not set, label is not displayed. */
label?: React.ReactNode;
/** Contents of `Input.Description` component. If not set, description is not displayed. */
description?: React.ReactNode;
/** Contents of `Input.Error` component. If not set, error is not displayed. */
error?: React.ReactNode;
/** Contents of `Input.Success` component. If not set, success is not displayed. */
success?: React.ReactNode;
/** Adds required attribute to the input and a red asterisk on the right side of label @default false */
required?: boolean;
/** If set, the required asterisk is displayed next to the label. Overrides `required` prop. Does not add required attribute to the input. @default false */
withAsterisk?: boolean;
/** Props passed down to the `Input.Label` component */
labelProps?: InputLabelProps & DataAttributes & PlaceholderPolymorphicProps;
/** Props passed down to the `Input.Description` component */
descriptionProps?: InputDescriptionProps & DataAttributes & PlaceholderPolymorphicProps;
/** Props passed down to the `Input.Error` component */
errorProps?: InputErrorProps & DataAttributes & PlaceholderPolymorphicProps;
/** Props passed down to the `Input.Success` component */
successProps?: InputSuccessProps & DataAttributes & PlaceholderPolymorphicProps;
/** Render function to wrap the input element. Useful for adding tooltips, popovers, or other wrappers around the input. @default React.Fragment */
inputContainer?: (children: React.ReactNode) => React.ReactNode;
/** Controls order and visibility of wrapper elements. Only elements included in this array will be rendered. @default ['label', 'description', 'input', 'error'] */
inputWrapperOrder?: ('label' | 'input' | 'description' | 'error')[];
}
原生与外部类型
以下外部类型仍由 React 或资源自身依赖定义,没有手工展开为虚构属性。实际可用类型以安装版本的 TypeScript 检查为准。
Factory<{
props: NumberInputProps;
ref: HTMLInputElement;
stylesNames: NumberInputStylesNames;
vars: NumberInputCssVariables;
variant: InputVariant;
signature: <T extends NumberInputNumericType = number>(
props: NumberInputProps<T>
) => React.JSX.Element;
}>
MantineStyleProps
React.CSSProperties
MantineTheme
Partial<Record<Variable, string>>
Record<Variable, string>
Variable
MantineBreakpoint
Record<string, any>
Omit<__BaseInputProps, 'pointer'>
React.ReactNode
InputLabelProps
DataAttributes
PlaceholderPolymorphicProps
InputDescriptionProps
InputErrorProps
InputSuccessProps
React.ComponentProps<'div'>
MantineRadius
MantineSize
ClearSectionMode
StylesApiProps<NumberInputFactory>
React.ElementType
Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>
React.ComponentProps<ElementType>
ElementType
PropsToOmit
T
OnValueChange
NumberFormatValues
React.Ref<NumberInputHandlers | undefined>
HTMLInputElement
InputLabelStylesNames
InputDescriptionStylesNames
InputErrorStylesNames
InputSuccessStylesNames
React.JSX.Element