Skip to main content

Migrate Stepper to the Polaris number field component

The Polaris number field component renders a numeric input. It replaces the previous Stepper component and is available as <s-number-field> in API versions 2025-10 and newer. The previous Stepper component always showed increment and decrement buttons. On the Polaris number field, controls defaults to 'auto', which lets the surface decide whether to show the buttons. To guarantee the previous behavior, set controls="stepper" explicitly.

Showing stepper buttons

import '@shopify/ui-extensions/preact';
import {render} from 'preact';

export default function extension() {
render(<Extension />, document.body);
}

function Extension() {
return (
<s-number-field
label="Quantity"
controls="stepper"
defaultValue="1"
min={1}
max={10}
/>
);
}
import {
reactExtension,
Stepper,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
'purchase.checkout.block.render',
() => <Extension />,
);

function Extension() {
return (
<Stepper label="Quantity" value={1} min={1} max={10} />
);
}

The following properties are different in the Polaris number field component.

The previous Stepper onChange prop was called with the new numeric value, and an empty field was represented as undefined. On the Polaris number field, the handler receives an Event instead. Read the value from event.currentTarget.value, which is now a string. Don't cast it with Number(...) directly — Number('') evaluates to 0, so a cleared field would be reinterpreted as zero. Check for an empty string first, then cast:

Migrating onChange

import '@shopify/ui-extensions/preact';
import {render} from 'preact';

export default function extension() {
render(<Extension />, document.body);
}

function Extension() {
return (
<s-number-field
label="Quantity"
controls="stepper"
onChange={(event) => {
const raw = event.currentTarget.value;
const value = raw === '' ? undefined : Number(raw);
console.log('Value:', value);
}}
/>
);
}
import {
reactExtension,
Stepper,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
'purchase.checkout.block.render',
() => <Extension />,
);

function Extension() {
return (
<Stepper
label="Quantity"
onChange={(value) => console.log('Value:', value)}
/>
);
}

The previous Stepper onInput prop was called with the new numeric value, and an empty field was represented as undefined. On the Polaris number field, the handler receives an Event. Read the value from event.currentTarget.value, which is now a string. Check for an empty string before casting with Number(...) — Number('') is 0, which would reinterpret a cleared field as zero.

The previous Stepper onBlur and onFocus props were called with no arguments. On the Polaris number field, both handlers now receive an Event.

The previous Stepper value prop was a number. On the Polaris number field, value is a string. Use defaultValue (uncontrolled) or value (controlled) to set the initial value.

Migrating value

import '@shopify/ui-extensions/preact';
import {render} from 'preact';

export default function extension() {
render(<Extension />, document.body);
}

function Extension() {
return (
<s-number-field
label="Quantity"
controls="stepper"
defaultValue="1"
min={1}
max={10}
/>
);
}
import {
reactExtension,
Stepper,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension(
'purchase.checkout.block.render',
() => <Extension />,
);

function Extension() {
return (
<Stepper label="Quantity" value={1} min={1} max={10} />
);
}

The previous Stepper readonly prop is now readOnly (capital O).

Anchor to min and max defaultsmin and max defaults

The previous Stepper min defaulted to 0 and max had no default. On the Polaris number field, min defaults to -Infinity and max defaults to Infinity. If your code relied on the previous min={0} default, set min={0} explicitly.

If you use the icon property, then update icon names to their Polaris web component equivalents. The number field uses the same icon names as the Polaris icon component.

Note

This table lists only icon values that need more than a camelCase-to-kebab-case rename. If an icon isn't listed here, then convert its previous camelCase name to kebab-case. For example, arrowLeft becomes arrow-left.

Previous iconNew icon
'checkmark''check'
'close''x'
'critical''alert-circle'
'error''x-circle'
'errorFill''x-circle-filled'
'gift''gift-card'
'giftFill''gift-card'
'hamburger''menu'
'hollowCircle''circle'
'horizontalDots''menu-horizontal'
'infoFill''info-filled'
'list''list-bulleted'
'magnify''search'
'marker''location'
'orderBox''order'
'pen''edit'
'question''question-circle'
'questionFill''question-circle-filled'
'starFill''star-filled'
'success''check-circle'
'verticalDots''menu-vertical'
'warning''alert-triangle'
'warningFill''alert-triangle-filled'

The Polaris number field component introduces the following new properties:

New propTypeDescription
controls'auto' | 'stepper' | 'none'Sets the type of controls displayed in the field. 'stepper' shows increment and decrement buttons, 'none' hides them, and 'auto' (the default) lets the surface decide.
defaultValuestringSets the initial value for uncontrolled usage.
suffixstringText content to render after the value.
autocompletestringA hint to the browser about the autofill value for the field.
inputMode'decimal' | 'numeric'Hints at the type of virtual keyboard to display.
labelAccessibilityVisibility'visible' | 'exclusive'Controls whether the label is visible or only available to assistive technology.

The Polaris number field exposes an accessory slot for supplementary interactive content (a button or clickable with text content). The previous Stepper had no equivalent — the slot is a new capability on <s-number-field>. Use it for actions adjacent to the input, such as a "Use max" button:

Adding an accessory to s-number-field

import '@shopify/ui-extensions/preact';
import {render} from 'preact';

export default function extension() {
render(<Extension />, document.body);
}

function Extension() {
return (
<s-number-field label="Quantity" defaultValue="1" min={0} max={10}>
<s-button slot="accessory">Use max</s-button>
</s-number-field>
);
}

For static unit indicators like kg or %, use the prefix and suffix props instead:

<s-number-field label="Weight" defaultValue="1" min={0} suffix="kg" />

Anchor to accessibilityDescriptionaccessibilityDescription

The previous Stepper accessibilityDescription prop has been removed. Use label or the field's built-in error and validation messaging to communicate additional context to screen readers.


Was this page helpful?