Bug report
Current behavior
NumberField.Root does not consistently respect format.roundingMode.
The initial/controlled display formatting appears to use Intl.NumberFormat, but when the value is committed or validated, Base UI rounds the numeric value with Number.prototype.toFixed(). As a result, roundingMode is ignored for the committed value.
For example, with:
format={{ maximumFractionDigits: 2, roundingMode: 'floor' }}
typing 1.239 and blurring the input commits 1.24, even though Intl.NumberFormat with roundingMode: 'floor' formats the same value as 1.23.
Expected behavior
NumberField should respect format.roundingMode whenever it rounds a value according to the provided format options.
With maximumFractionDigits: 2 and roundingMode: 'floor', committing 1.239 should produce 1.23, not 1.24.
Reproducible example
The issue can be reproduced with the following test added to packages/react/src/number-field/input/NumberFieldInput.test.tsx:
it('should respect roundingMode when rounding to explicit maximumFractionDigits on blur', async () => {
const onValueChange = vi.fn();
function Controlled() {
const [value, setValue] = React.useState<number | null>(null);
return (
<NumberField.Root
value={value}
onValueChange={(nextValue) => {
onValueChange(nextValue);
setValue(nextValue);
}}
format={{
maximumFractionDigits: 2,
roundingMode: 'floor',
}}
locale="en-US"
>
<NumberField.Input />
</NumberField.Root>
);
}
const { user } = await render(<Controlled />);
const input = screen.getByRole('textbox');
await act(async () => {
input.focus();
});
await user.keyboard('1.239');
fireEvent.blur(input);
expect(onValueChange.mock.lastCall?.[0]).toBe(1.23);
expect(input).toHaveValue('1.23');
});
Currently this fails because the committed value is 1.24.
Base UI version
v1.4.1
Which browser are you using?
Google Chrome 147.0.7727.139
options.roundingMode is supported by all major browsers.
https://caniuse.com/mdn-javascript_builtins_intl_numberformat_numberformat_options_parameter_options_roundingmode_parameter
Which OS are you using?
macOS
Which assistive tech are you using (if applicable)?
N/A
Additional context
This seems to come from internal rounding logic using toFixed() instead of Intl.NumberFormat semantics.
|
const maxFrac = formatOptions?.maximumFractionDigits; |
|
const committed = |
|
hasExplicitPrecision && typeof maxFrac === 'number' |
|
? Number(parsedValue.toFixed(maxFrac)) |
|
: parsedValue; |
|
|
Bug report
Current behavior
NumberField.Rootdoes not consistently respectformat.roundingMode.The initial/controlled display formatting appears to use
Intl.NumberFormat, but when the value is committed or validated, Base UI rounds the numeric value withNumber.prototype.toFixed(). As a result,roundingModeis ignored for the committed value.For example, with:
typing
1.239and blurring the input commits1.24, even thoughIntl.NumberFormatwithroundingMode: 'floor'formats the same value as1.23.Expected behavior
NumberFieldshould respectformat.roundingModewhenever it rounds a value according to the providedformatoptions.With
maximumFractionDigits: 2androundingMode: 'floor', committing1.239should produce1.23, not1.24.Reproducible example
The issue can be reproduced with the following test added to
packages/react/src/number-field/input/NumberFieldInput.test.tsx:Currently this fails because the committed value is
1.24.Base UI version
v1.4.1
Which browser are you using?
Google Chrome 147.0.7727.139
options.roundingModeis supported by all major browsers.https://caniuse.com/mdn-javascript_builtins_intl_numberformat_numberformat_options_parameter_options_roundingmode_parameter
Which OS are you using?
macOS
Which assistive tech are you using (if applicable)?
N/A
Additional context
This seems to come from internal rounding logic using
toFixed()instead ofIntl.NumberFormatsemantics.base-ui/packages/react/src/number-field/input/NumberFieldInput.tsx
Lines 189 to 194 in ac7ebaf