Skip to main content
Migrate to Polaris

Version 2025-07 is the last API version to support React-based UI components. Later versions use web components, native UI elements with built-in accessibility, better performance, and consistent styling with Shopify's design system. Check out the migration guide to upgrade your extension.

ChoiceList

The ChoiceList component groups a set of related options as either radio buttons (single selection) or checkboxes (multiple selection). Use it when merchants need to choose from a visible set of options.

For dropdown selection that takes less vertical space, use Select.

Support
Targets (46)

Supported targets


Props for the ChoiceList component, which renders a group of selectable choices as either radio buttons (single selection) or checkboxes (multiple selection).

Anchor to choices
choices
[]

The list of choices to render. Each choice is an object with properties like label, id, disabled, readOnly, error, and checked. Provide at least two choices for meaningful selection.

Anchor to defaultValue
defaultValue
string | string[]

The initial value of the field when it isn't controlled by state. Use this instead of value when you don't need to manage the field's state yourself. The component tracks its own value internally and reports changes through onChange.

Anchor to disabled
disabled
boolean
Default: false

Whether the field is disabled. When true, the field can't be edited by the user, won't receive focus, and won't be submitted with the form. Use this for fields that aren't relevant in the current context.

Anchor to error
error
string

An error message to display below the field. When set, the field receives a specific stylistic treatment (typically a red border) to communicate problems that have to be resolved immediately. The string value is displayed as the error message.

Pass undefined or omit this prop to clear the error state.

Anchor to multiple
multiple
boolean
Default: false

Whether the merchant can select more than one choice. When true, then each choice is rendered as a checkbox. When false, then choices are rendered as radio buttons and only one can be selected at a time.

string

An identifier for the field that is unique within the nearest containing Form component.

Anchor to onChange
onChange
(value: string | string[]) => void

A callback that fires when the user finishes editing the field, typically on blur. Only fires if the value changed. Update your state in this callback and pass the new value back through the value prop.

This doesn't fire on every keystroke. Use onInput for real-time responses like clearing validation errors as the user types. Don't use onInput to control value because that can cause issues on lower-powered devices due to asynchronous rendering.

Anchor to readOnly
readOnly
boolean
Default: false

Whether the field is read-only. Unlike disabled, a read-only field can still receive focus and its value is included when the form is submitted. Use this when the value should be visible and selectable but not editable, such as a computed total.

Anchor to value
value
T

The current value for the field. If omitted, then the field will be empty. You should update this value in response to the onChange callback.


Anchor to Choose a shipping methodChoose a shipping method

Pick a shipping speed from standard, express, and overnight radio options. This example renders a ChoiceList with three tiers as radio buttons, and a Button that saves the selected method.

Choose a shipping method

Pick a shipping speed from standard, express, and overnight radio options. This example renders a `ChoiceList` with three tiers as radio buttons, and a [Button](/docs/api/admin-extensions/2025-07/ui-components/actions/button) that saves the selected method.

Choose a shipping method

import {useState} from 'react';
import {reactExtension, useApi, ChoiceList, Button, BlockStack} from '@shopify/ui-extensions-react/admin';

function App() {
const {data, close} = useApi('admin.product-details.action.render');
const productId = data.selected[0]?.id;
const [shippingMethod, setShippingMethod] = useState('standard');

return (
<BlockStack>
<ChoiceList
name="shippingMethod"
value={shippingMethod}
choices={[
{label: 'Standard shipping (5-7 business days)', id: 'standard'},
{label: 'Express shipping (2-3 business days)', id: 'express'},
{label: 'Overnight delivery', id: 'overnight'},
]}
onChange={setShippingMethod}
/>
<Button
variant="primary"
onPress={async () => {
await fetch('/api/products/shipping-method', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, shippingMethod}),
});
close();
}}
>
Save shipping method
</Button>
</BlockStack>
);
}

export default reactExtension(
'admin.product-details.action.render',
() => <App />,
);
import {extension, ChoiceList, Button, BlockStack} from '@shopify/ui-extensions/admin';

export default extension(
'admin.product-details.action.render',
(root, api) => {
const {data, close} = api;
const productId = data.selected[0]?.id;
let shippingMethod = 'standard';

const stack = root.createComponent(BlockStack);

const choiceList = root.createComponent(ChoiceList, {
name: 'shippingMethod',
value: shippingMethod,
choices: [
{label: 'Standard shipping (5-7 business days)', id: 'standard'},
{label: 'Express shipping (2-3 business days)', id: 'express'},
{label: 'Overnight delivery', id: 'overnight'},
],
onChange: (value) => {
shippingMethod = value;
},
});

const saveButton = root.createComponent(
Button,
{
variant: 'primary',
onPress: async () => {
await fetch('/api/products/shipping-method', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, shippingMethod}),
});
close();
},
},
'Save shipping method',
);

stack.appendChild(choiceList);
stack.appendChild(saveButton);
root.appendChild(stack);
},
);

Anchor to Select multiple product tagsSelect multiple product tags

Enable multi-select with the multiple prop to let merchants pick several options at once. This example renders checkboxes for product tags like "Seasonal" and "Best seller", collecting an array of selected values to save as product metadata.

Select multiple product tags

import {useState} from 'react';
import {reactExtension, useApi, ChoiceList, Button, BlockStack, Text} from '@shopify/ui-extensions-react/admin';

function App() {
const {data, close} = useApi('admin.product-details.action.render');
const productId = data.selected[0]?.id;
const [tags, setTags] = useState([]);

return (
<BlockStack>
<Text fontWeight="bold">Apply product tags</Text>
<ChoiceList
name="productTags"
multiple
value={tags}
choices={[
{label: 'Seasonal', id: 'seasonal'},
{label: 'Clearance', id: 'clearance'},
{label: 'New arrival', id: 'new-arrival'},
{label: 'Best seller', id: 'best-seller'},
{label: 'Limited edition', id: 'limited-edition'},
]}
onChange={setTags}
/>
<Button
variant="primary"
onPress={async () => {
await fetch('/api/products/tags', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, tags}),
});
close();
}}
>
Apply tags
</Button>
</BlockStack>
);
}

export default reactExtension(
'admin.product-details.action.render',
() => <App />,
);
import {extension, ChoiceList, Button, BlockStack, Text} from '@shopify/ui-extensions/admin';

export default extension(
'admin.product-details.action.render',
(root, api) => {
const {data, close} = api;
const productId = data.selected[0]?.id;
let tags = [];

const stack = root.createComponent(BlockStack);

const heading = root.createComponent(
Text,
{fontWeight: 'bold'},
'Apply product tags',
);

const choiceList = root.createComponent(ChoiceList, {
name: 'productTags',
multiple: true,
value: tags,
choices: [
{label: 'Seasonal', id: 'seasonal'},
{label: 'Clearance', id: 'clearance'},
{label: 'New arrival', id: 'new-arrival'},
{label: 'Best seller', id: 'best-seller'},
{label: 'Limited edition', id: 'limited-edition'},
],
onChange: (value) => {
tags = value;
},
});

const saveButton = root.createComponent(
Button,
{
variant: 'primary',
onPress: async () => {
await fetch('/api/products/tags', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, tags}),
});
close();
},
},
'Apply tags',
);

stack.appendChild(heading);
stack.appendChild(choiceList);
stack.appendChild(saveButton);
root.appendChild(stack);
},
);

Anchor to Validate required selectionValidate required selection

Validate that a selection has been made before submission using the error prop. This example shows an inline error when merchants attempt to save without selecting a compliance region, so a region is always chosen before the data reaches your backend.

Validate required selection

import {useState} from 'react';
import {reactExtension, useApi, ChoiceList, Button, BlockStack} from '@shopify/ui-extensions-react/admin';

function App() {
const {data, close} = useApi('admin.product-details.action.render');
const productId = data.selected[0]?.id;
const [region, setRegion] = useState('');
const [error, setError] = useState(undefined);

return (
<BlockStack>
<ChoiceList
name="complianceRegion"
value={region}
error={error}
choices={[
{label: 'North America (FDA, FTC)', id: 'na'},
{label: 'European Union (CE, REACH)', id: 'eu'},
{label: 'Asia-Pacific (JIS, CCC)', id: 'apac'},
]}
onChange={(value) => {
setRegion(value);
setError(undefined);
}}
/>
<Button
variant="primary"
onPress={async () => {
if (!region) {
setError('Select a compliance region before saving');
return;
}
await fetch('/api/products/compliance', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, complianceRegion: region}),
});
close();
}}
>
Set compliance region
</Button>
</BlockStack>
);
}

export default reactExtension(
'admin.product-details.action.render',
() => <App />,
);
import {extension, ChoiceList, Button, BlockStack} from '@shopify/ui-extensions/admin';

export default extension(
'admin.product-details.action.render',
(root, api) => {
const {data, close} = api;
const productId = data.selected[0]?.id;
let complianceRegion = '';

const stack = root.createComponent(BlockStack);

const choiceList = root.createComponent(ChoiceList, {
name: 'complianceRegion',
value: complianceRegion,
choices: [
{label: 'North America (FDA, FTC)', id: 'na'},
{label: 'European Union (CE, REACH)', id: 'eu'},
{label: 'Asia-Pacific (JIS, CCC)', id: 'apac'},
],
onChange: (value) => {
complianceRegion = value;
choiceList.updateProps({error: undefined});
},
});

const saveButton = root.createComponent(
Button,
{
variant: 'primary',
onPress: async () => {
if (!complianceRegion) {
choiceList.updateProps({error: 'Select a compliance region before saving'});
return;
}
await fetch('/api/products/compliance', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId, complianceRegion}),
});
close();
},
},
'Set compliance region',
);

stack.appendChild(choiceList);
stack.appendChild(saveButton);
root.appendChild(stack);
},
);

  • Keep the list short and scannable: Display 2-6 options. For longer lists, consider using a Select dropdown instead, which takes up less vertical space.
  • Write clear, parallel labels: Each choice label should be written in a consistent format. For example, all labels should start with a verb or all should be noun phrases.
  • Provide a default selection for radio buttons: When using single selection, pre-select the most common or recommended option so merchants don't have to make a choice when the default works.

  • ChoiceList doesn't support nested or hierarchical choices. All options are presented at the same level.
  • Individual choices within the list can be disabled or set to read-only, but the visual distinction between disabled and read-only choices is subtle.
  • The choices prop requires each choice to have a label and optionally supports disabled, id, readOnly, error, and checked. Rich content like descriptions or images inside choices isn't supported.
  • When multiple is true, the onChange callback returns an array of selected values. When false, it returns a single string value. Your handler must account for both shapes.

Was this page helpful?