QuantityPicker
Minus and plus around a number, with an optional slider and quick-add chips. It is the portal's "how many users" control, and the number can be typed as well as stepped.

Use this when / not when
- Use for a count the user adjusts up and down — seats, licences, copies — especially where a slider or preset increments help.
- Not for an arbitrary number. The control is built around a minimum, a maximum and a step;
TextInputis simpler for a free number. - Not for a rough value on a continuous range —
Slideralone is that. - Not for a value that has no bounds.
minValue,maxValueandstepare all required.
Import
import QuantityPicker from "@onlyoffice/apps-ui-kit/components/quantity-picker";
It is a default export, so the name is yours to choose. The root barrel carries it by name
as well — components/index.ts re-exports it as export { default as QuantityPicker } — but prefer the
subpath: the barrel does not build without four optional peers, see
Which import form.
Needs ThemeProvider from @onlyoffice/apps-ui-kit/providers/theme.
Stories
Default
The basic picker: press minus or plus, or type a number between the bounds. Change any other prop live in the Controls panel below.

With Slider
A slider under the controls, for covering a wide range quickly: dragging it and pressing the controls move the same number (showSlider).

With Presets
Quick-add tabs for the amounts people pick most: +10, +50 and +100 each add their amount to the number shown, and stop at the maximum (items).

Disabled
An amount the reader may see but not change: Unlimited stands in place of the number as static text that cannot be typed into, and the controls cannot be pressed (isDisabled, disableValue).

With Plus Sign
For a range whose top is open-ended: the number reads 100+, one step past the maximum, and the slider's far end carries the same label; press minus to come back to 100 (showPlusSign).

With Zero Allowed
For an amount that is either none or at least a minimum: pressing minus at 5 drops straight to 0, and plus from 0 jumps back to 5 (enableZero). The line under the number is free text (underControlsTitle).

Minus Locked With Tooltip
When the amount may only grow: pressing minus changes nothing, but the control stays reachable with Tab, and hovering it opens a tooltip that says why (minusDisabled, minusTooltipId). The tooltip is the host's own.

Without Controls
The number alone, without the minus and plus controls, for when a slider or typing is the way to change it (withoutControls).

Right To Left
The picker in a right-to-left layout: plus moves to the left of the number and minus to the right, the slider fills from the right, and the minimum sits at its right end. The wrapper carries dir="rtl"; the direction also comes from the theme's interfaceDirection (the Direction toolbar).

Minimal example
The control is fully controlled: hold the number and set it from onChange.
import { useState } from "react";
import QuantityPicker from "@onlyoffice/apps-ui-kit/components/quantity-picker";
export function Seats() {
const [seats, setSeats] = useState(5);
return (
<QuantityPicker
title="Users"
value={seats}
minValue={1}
maxValue={100}
step={1}
onChange={setSeats}
/>
);
}
Props
Recipes
Disabled / read-only
isDisabled replaces the field with plain text, so nothing can be typed or focused.
disableValue is what that text says when the number itself is not the right thing to show.
import QuantityPicker from "@onlyoffice/apps-ui-kit/components/quantity-picker";
export function FixedSeats({ seats }: { seats: number }) {
return (
<QuantityPicker
title="Users"
value={seats}
minValue={1}
maxValue={100}
step={1}
isDisabled
disableValue="Set by your plan"
onChange={() => {}}
/>
);
}
A slider and quick-add chips
items are increments, not values: a chip labelled +10 adds ten to whatever is there.
import { useState } from "react";
import QuantityPicker from "@onlyoffice/apps-ui-kit/components/quantity-picker";
export function SeatsWithPresets() {
const [seats, setSeats] = useState(10);
return (
<QuantityPicker
title="Users"
subtitle="Billed monthly"
value={seats}
minValue={1}
maxValue={250}
step={1}
showSlider
showPlusSign
items={[10, 50, { name: "100", value: 100 }]}
underControlsTitle={`${seats} users`}
onChange={setSeats}
/>
);
}
Behaviour the types don't state
- Typing is held as a draft and committed on blur or Enter. Until then
onChangefires for each valid intermediate number, but an empty field is not reported — it settles to 0 orminValuewhen you leave it. - Out-of-range typing is treated unevenly. A number above
maxValueis replaced at once by the cap (maxValue, ormaxValue + 1withshowPlusSign) and reported straight away. A number belowminValueis not reported while you type; on blur or Enter it is raised tominValue— or, withenableZero, kept exactly as typed and flagged. - Non-digits are stripped as you type, so a minus sign or a decimal point cannot be entered at all; the control is whole, non-negative numbers only.
showPlusSignchanges what the maximum means. AbovemaxValuethe display becomes<maxValue>+, typing a larger number reportsmaxValue + 1, and the slider's own maximum ismaxValue + 1so the handle can reach that overflow step.- The slider writes its bounds at its ends:
minValueat the start andmaxValueat the end, followed by+withshowPlusSign. itemsadd, they do not set. A chip's number is added to the current value, and the chips never look selected — they are buttons, not a choice.- The minus button stops at
minValue, or at 0 withenableZero. WithenableZeroa value between 0 andminValuealso turns the line under the controls into its warning colour, and plus from anywhere belowminValuejumps straight tominValuerather than addingstep. underControlsTitlealways renders, even when empty, so the control reserves that line's height whether or not you use it.isDisabledremoves the field from the DOM, replacing it with text — so refs, focus and test queries against the input stop working while it is disabled.minusDisableddisables only the minus button;minusTooltipIdputsdata-tooltip-idon it for aTooltipyou render yourself.isZeroAllowedis the former name ofenableZeroand still works as an alias:enableZero ?? isZeroAllowed ?? false, soenableZerowins when both are set. It is marked deprecated; writeenableZeroin new code.
CSS variables
The control is styled from the shared theme tokens rather than custom properties of its own.
The slider inside follows Slider.
Accessibility
- The plus and minus controls are real
<button type="button">s, so they are in the tab order and answer Enter and Space. Tab reaches minus, the number and plus in that order. Their icons arearia-hidden, so they are unnamed until you passdecreaseLabelandincreaseLabel— noaria-labelis rendered without them, and a screen reader then announces two nameless buttons. minusDisabledleaves the minus control focusable and marks itaria-disabled, so a tooltip attached throughminusTooltipIdcan still be read; onlyisDisabledsets the realdisabled.- The number is a
TextInputgiventabIndex={0}by the component, against the kit's own default of-1. It has no label; name it through your own markup, and note it disappears entirely whenisDisabled. - The quick-add chips are
TabItems, which present as tabs rather than as buttons that change a number. - The slider is a real range input and is the only part fully usable from the keyboard — see
Slider, which has no visible focus ring of its own. - Nothing announces the minimum, the maximum or that a value was clamped.
Test ids
| Element | data-testid |
|---|---|
| The field | quantity_picker_input |
| The minus button | quantity_picker_minus_icon |
| The plus button | quantity_picker_plus_icon |
| The slider | quantity_picker_slider |
| A quick-add chip | add_<value>_tab_item |
None of them can be overridden by a prop.