,
FieldDecorationProps {
/**
- * Sets the virtual keyboard.
+ * The type of virtual keyboard displayed on touch devices.
+ *
+ * - `decimal`: Shows a numeric keyboard with a decimal separator,
+ * suitable for values that may include fractional digits (for example, prices).
+ * - `numeric`: Shows a numeric keyboard without a decimal separator,
+ * suitable for whole numbers (for example, quantities).
*
* @defaultValue 'decimal'
*/
inputMode?: 'decimal' | 'numeric';
}
+/**
+ * Autocomplete field values valid for a number input. Includes one-time
+ * codes and credit-card-related number fields such as card number and
+ * security code.
+ */
export type NumberAutocompleteField = Extract<
AnyAutocompleteField,
| 'one-time-code'
@@ -29,6 +44,10 @@ export type NumberAutocompleteField = Extract<
| `${AutocompleteFieldCreditCardAlias}-${AutocompleteFieldSecurityCodeAlias}`
>;
+/**
+ * A text input for numeric values, with configurable decimal / integer
+ * keyboard modes and number constraint validation.
+ */
export const NumberField = createRemoteComponent<
'NumberField',
NumberFieldProps
diff --git a/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/basic-numberfield.example.ts b/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/basic-numberfield.example.ts
index 1cbceaacf8..fc2e9c130d 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/basic-numberfield.example.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/basic-numberfield.example.ts
@@ -1,9 +1,42 @@
-import {extend, NumberField} from '@shopify/ui-extensions/admin';
+import {extension, NumberField, Button, BlockStack} from '@shopify/ui-extensions/admin';
-extend('Playground', (root) => {
- const discountField = root.createComponent(NumberField, {
- label: 'Enter the discount amount',
- });
+export default extension(
+ 'admin.product-details.action.render',
+ (root, api) => {
+ const {data, close} = api;
+ const productId = data.selected[0]?.id;
+ let quantity = 0;
- root.appendChild(discountField);
-});
+ const stack = root.createComponent(BlockStack);
+
+ const field = root.createComponent(NumberField, {
+ label: 'Restock quantity',
+ name: 'quantity',
+ min: 1,
+ max: 10000,
+ onChange: (value) => {
+ quantity = value;
+ },
+ });
+
+ const saveButton = root.createComponent(
+ Button,
+ {
+ variant: 'primary',
+ onPress: async () => {
+ await fetch('/api/inventory/restock', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({productId, quantity}),
+ });
+ close();
+ },
+ },
+ 'Submit restock',
+ );
+
+ stack.appendChild(field);
+ stack.appendChild(saveButton);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/numberfield-constraints.example.ts b/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/numberfield-constraints.example.ts
new file mode 100644
index 0000000000..a0fcd753db
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/numberfield-constraints.example.ts
@@ -0,0 +1,46 @@
+import {extension, NumberField, BlockStack, Text} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.block.render',
+ (root) => {
+
+ const stack = root.createComponent(BlockStack);
+
+ const heading = root.createComponent(
+ Text,
+ {fontWeight: 'bold'},
+ 'Warehouse slot configuration',
+ );
+
+ const slotField = root.createComponent(NumberField, {
+ label: 'Storage slot number',
+ name: 'slotNumber',
+ min: 1,
+ max: 500,
+ step: 1,
+ error: undefined,
+ onChange: (value) => {
+ if (value > 500) {
+ slotField.updateProps({
+ error: 'Slot number cannot exceed 500',
+ });
+ } else {
+ slotField.updateProps({error: undefined});
+ }
+ },
+ });
+
+ const palletField = root.createComponent(NumberField, {
+ label: 'Units per pallet',
+ name: 'unitsPerPallet',
+ min: 1,
+ max: 200,
+ step: 1,
+ });
+
+ stack.appendChild(heading);
+ stack.appendChild(slotField);
+ stack.appendChild(palletField);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/numberfield-decimal.example.ts b/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/numberfield-decimal.example.ts
new file mode 100644
index 0000000000..e3b02fa434
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/NumberField/examples/numberfield-decimal.example.ts
@@ -0,0 +1,39 @@
+import {extension, NumberField, BlockStack, Text} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.action.render',
+ (root) => {
+
+ const stack = root.createComponent(BlockStack);
+
+ const heading = root.createComponent(
+ Text,
+ {fontWeight: 'bold'},
+ 'Cost pricing',
+ );
+
+ const costField = root.createComponent(NumberField, {
+ label: 'Cost per item',
+ name: 'costPerItem',
+ inputMode: 'decimal',
+ min: 0,
+ step: 0.01,
+ suffix: 'USD',
+ });
+
+ const marginField = root.createComponent(NumberField, {
+ label: 'Profit margin',
+ name: 'margin',
+ inputMode: 'decimal',
+ min: 0,
+ max: 100,
+ step: 0.5,
+ suffix: '%',
+ });
+
+ stack.appendChild(heading);
+ stack.appendChild(costField);
+ stack.appendChild(marginField);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Paragraph/Paragraph.doc.ts b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/Paragraph.doc.ts
index 55c96e9edc..2d4808305f 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Paragraph/Paragraph.doc.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/Paragraph.doc.ts
@@ -3,24 +3,27 @@ import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
const data: ReferenceEntityTemplateSchema = {
name: 'Paragraph',
description:
- 'Use this to display a block of text similar to the `` tag in HTML.',
+ 'The Paragraph component renders a block of text with appropriate spacing between sibling paragraphs. Use it for body copy, descriptions, and any multi-sentence content. It supports font size, weight, style, and text overflow control through its typography props.\n\nFor inline text within a sentence, use [Text](/docs/api/admin-extensions/{API_VERSION}/ui-components/typography-and-content/text). For titles, use [Heading](/docs/api/admin-extensions/{API_VERSION}/ui-components/typography-and-content/heading).',
requires: '',
thumbnail: 'paragraph-thumbnail.png',
isVisualComponent: true,
type: '',
definitions: [
{
- title: 'ParagraphProps',
- description: '',
+ title: 'Properties',
+ description:
+ 'Configure the following properties on the Paragraph component.',
type: 'ParagraphProps',
},
],
- category: 'Components',
- subCategory: 'Titles and text',
+ category: 'UI components',
+ subCategory: 'Typography and content',
defaultExample: {
image: 'paragraph-default.png',
+ description:
+ 'Show a sync status message with bolded key figures like the completion time and field count. This example nests [Text](/docs/api/admin-extensions/{API_VERSION}/components/typography-and-content/text) with `fontWeight` props inside a `Paragraph` to emphasize specific details within a sentence.',
codeblock: {
- title: 'Simple Paragraph example',
+ title: 'Display product sync summary',
tabs: [
{
title: 'React',
@@ -28,21 +31,75 @@ const data: ReferenceEntityTemplateSchema = {
language: 'tsx',
},
{
- title: 'JS',
+ title: 'TS',
code: './examples/basic-paragraph.example.ts',
- language: 'js',
+ language: 'ts',
},
],
},
},
-
- related: [
+ examples: {
+ description: '',
+ examples: [
+ {
+ description:
+ 'Present fulfillment instructions that include an inline [Link](/docs/api/admin-extensions/{API_VERSION}/components/actions/link) to external documentation. This example shows how to nest interactive elements inside a paragraph to provide contextual help alongside instructional text.',
+ codeblock: {
+ title: 'Embed inline links in help text',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/Paragraph/examples/paragraph-with-links.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/paragraph-with-links.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ {
+ description:
+ 'Show or hide guidance based on product status retrieved from the [GraphQL Admin API](/docs/api/admin-graphql/). This example queries the product status and conditionally renders a paragraph with publishing instructions when the product is in draft, helping merchants understand what steps remain.',
+ codeblock: {
+ title: 'Show conditional help text',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/Paragraph/examples/paragraph-conditional.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/paragraph-conditional.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ subSections: [
+ {
+ type: 'Generic',
+ title: 'Best practices',
+ anchorLink: 'best-practices',
+ sectionContent: `- **Keep paragraphs focused:** Each paragraph should convey a single idea or piece of information. Short, focused paragraphs are easier for merchants to scan.
+- **Nest Text inside Paragraph for inline formatting:** To add emphasis, bold, or other inline styling within a paragraph, nest [Text](/docs/api/admin-extensions/{API_VERSION}/ui-components/typography-and-content/text) components inside the Paragraph. This preserves the block-level layout while giving you typographic control.
+- **Pair with Heading for structure:** Place a [Heading](/docs/api/admin-extensions/{API_VERSION}/ui-components/typography-and-content/heading) before a group of paragraphs to give the section a clear title that helps merchants navigate the content.`,
+ },
{
- type: 'component',
- name: 'Heading',
- url: '/docs/api/admin-extensions/components/titles-and-text/heading',
+ type: 'Generic',
+ title: 'Limitations',
+ anchorLink: 'limitations',
+ sectionContent: `- Paragraph doesn't support color or tone props. Text inside a Paragraph always renders in the default body text color.
+- The \`textOverflow\` prop takes effect only when the parent container constrains width. Without a width constraint, text will wrap normally rather than truncate.
+- Paragraph doesn't support alignment (center, right). Text always renders with the default start alignment for the current locale direction.`,
},
],
+ related: [],
};
export default data;
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Paragraph/Paragraph.ts b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/Paragraph.ts
index 1acc8cdeb6..faefa7bd72 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Paragraph/Paragraph.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/Paragraph.ts
@@ -1,10 +1,20 @@
import {createRemoteComponent} from '@remote-ui/core';
import type {BaseTypographyProps, GlobalProps} from '../shared';
+/**
+ * Props for the Paragraph component, which renders a block of text
+ * as a distinct paragraph. Use Paragraph to structure your content
+ * into readable sections with appropriate spacing.
+ */
export interface ParagraphProps extends BaseTypographyProps, GlobalProps {
+ /**
+ * The content to render inside the paragraph. You can pass plain text
+ * or other inline components such as Text or Link.
+ */
children?: any;
}
+/** A Paragraph component that renders a block of text with appropriate spacing. */
export const Paragraph = createRemoteComponent<'Paragraph', ParagraphProps>(
'Paragraph',
);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/basic-paragraph.example.ts b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/basic-paragraph.example.ts
index 757de5782b..547b2a6c93 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/basic-paragraph.example.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/basic-paragraph.example.ts
@@ -1,10 +1,46 @@
-import {extend, Paragraph, BlockStack} from '@shopify/ui-extensions/admin';
+import {extension, Paragraph, Text, BlockStack} from '@shopify/ui-extensions/admin';
-extend('Playground', (root) => {
- const paragraph = root.createComponent(BlockStack, {inlineAlignment: 'center', gap: true}, [
- root.createComponent(Paragraph, {fontWeight: 'bold'}, 'Name:'),
- root.createComponent(Paragraph, {}, 'Jane Doe'),
- ]);
+export default extension(
+ 'admin.product-details.block.render',
+ (root) => {
- root.appendChild(paragraph);
-});
+ const stack = root.createComponent(BlockStack);
+
+ const paragraph = root.createComponent(Paragraph);
+
+ const intro = root.createComponent(
+ Text,
+ {},
+ 'Last sync completed at ',
+ );
+ const time = root.createComponent(
+ Text,
+ {fontWeight: 'bold'},
+ '2:45 PM EST',
+ );
+ const detail = root.createComponent(
+ Text,
+ {},
+ '. All product tags and metafields were successfully pushed to the warehouse management system. ',
+ );
+ const count = root.createComponent(
+ Text,
+ {fontWeight: 'bold'},
+ '24 fields',
+ );
+ const suffix = root.createComponent(
+ Text,
+ {},
+ ' updated across 3 variants.',
+ );
+
+ paragraph.appendChild(intro);
+ paragraph.appendChild(time);
+ paragraph.appendChild(detail);
+ paragraph.appendChild(count);
+ paragraph.appendChild(suffix);
+
+ stack.appendChild(paragraph);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/paragraph-conditional.example.ts b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/paragraph-conditional.example.ts
new file mode 100644
index 0000000000..a480820d62
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/paragraph-conditional.example.ts
@@ -0,0 +1,38 @@
+import {extension, Paragraph, Text, BlockStack} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.block.render',
+ async (root, api) => {
+ const {data, query} = api;
+ const productId = data.selected[0]?.id;
+
+ const stack = root.createComponent(BlockStack);
+
+ const result = await query(
+ `query Product($id: ID!) {
+ product(id: $id) {
+ status
+ title
+ }
+ }`,
+ {variables: {id: productId}},
+ );
+
+ const product = result?.data?.product;
+
+ if (product?.status === 'DRAFT') {
+ const helpText = root.createComponent(Paragraph);
+
+ const message = root.createComponent(
+ Text,
+ {},
+ `"${product.title}" is currently in draft status. Complete the product description, add at least one image, and set pricing before publishing to your storefront.`,
+ );
+
+ helpText.appendChild(message);
+ stack.appendChild(helpText);
+ }
+
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/paragraph-with-links.example.ts b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/paragraph-with-links.example.ts
new file mode 100644
index 0000000000..357307e896
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/Paragraph/examples/paragraph-with-links.example.ts
@@ -0,0 +1,39 @@
+import {extension, Paragraph, Text, Link, BlockStack} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.block.render',
+ (root) => {
+
+ const stack = root.createComponent(BlockStack);
+
+ const paragraph = root.createComponent(Paragraph);
+
+ const intro = root.createComponent(
+ Text,
+ {},
+ 'This product requires special handling during fulfillment. Review the ',
+ );
+
+ const link = root.createComponent(
+ Link,
+ {
+ href: 'https://help.shopify.com/manual/fulfillment',
+ target: '_blank',
+ },
+ 'fulfillment guidelines',
+ );
+
+ const suffix = root.createComponent(
+ Text,
+ {},
+ ' for packaging requirements and carrier restrictions before processing orders.',
+ );
+
+ paragraph.appendChild(intro);
+ paragraph.appendChild(link);
+ paragraph.appendChild(suffix);
+
+ stack.appendChild(paragraph);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/PasswordField/PasswordField.doc.ts b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/PasswordField.doc.ts
index d7efaa2633..e735a47166 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/PasswordField/PasswordField.doc.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/PasswordField.doc.ts
@@ -3,24 +3,27 @@ import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
const data: ReferenceEntityTemplateSchema = {
name: 'PasswordField',
description:
- 'This component is for secure input, it obfuscates any text that users enter.',
+ 'The PasswordField component provides a text input that masks the entered characters for secure data entry. Use it for passwords, API keys, secret tokens, or any other sensitive information.\n\nFor non-sensitive text input, use [TextField](/docs/api/admin-extensions/{API_VERSION}/ui-components/forms/textfield).',
requires: '',
thumbnail: 'passwordfield-thumbnail.png',
isVisualComponent: true,
type: '',
definitions: [
{
- title: 'PasswordFieldProps',
- description: '',
+ title: 'Properties',
+ description:
+ 'Configure the following properties on the PasswordField component.',
type: 'PasswordFieldProps',
},
],
- category: 'Components',
+ category: 'UI components',
subCategory: 'Forms',
defaultExample: {
image: 'passwordfield-default.png',
+ description:
+ 'Store a warehouse API key without exposing it on screen. This example uses `PasswordField` to mask the input, with a [Button](/docs/api/admin-extensions/{API_VERSION}/components/actions/button) that saves the credentials.',
codeblock: {
- title: 'Simple PasswordField example',
+ title: 'Enter API credentials securely',
tabs: [
{
title: 'React',
@@ -28,26 +31,74 @@ const data: ReferenceEntityTemplateSchema = {
language: 'tsx',
},
{
- title: 'JS',
+ title: 'TS',
code: './examples/basic-passwordfield.example.ts',
- language: 'js',
+ language: 'ts',
},
],
},
},
-
- related: [
+ examples: {
+ description: '',
+ examples: [
+ {
+ description:
+ 'Enforce minimum length requirements on sensitive inputs using the `error` prop. This example validates that a webhook secret is at least 16 characters, displaying an inline error until the requirement is met to prevent merchants from saving weak secrets.',
+ codeblock: {
+ title: 'Validate secret length requirements',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/PasswordField/examples/passwordfield-validation.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/passwordfield-validation.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ {
+ description:
+ 'Combine `PasswordField` with [TextField](/docs/api/admin-extensions/{API_VERSION}/components/forms/textfield) and [EmailField](/docs/api/admin-extensions/{API_VERSION}/components/forms/emailfield) to build a complete service connection form. This example collects an API endpoint, account email, and token in a single modal to connect a fulfillment provider.',
+ codeblock: {
+ title: 'Build a service connection form',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/PasswordField/examples/passwordfield-integration.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/passwordfield-integration.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ subSections: [
{
- type: 'component',
- name: 'EmailField',
- url: '/docs/api/admin-extensions/components/forms/emailfield',
+ type: 'Generic',
+ title: 'Best practices',
+ anchorLink: 'best-practices',
+ sectionContent: `- **Write a clear label:** The required \`label\` prop tells merchants what credential to enter. Use specific labels like "Password", "Current password", or "API secret key".
+- **Pair with a confirmation field when creating passwords:** When merchants set a new password, provide a second PasswordField for confirmation and validate that both values match.`,
},
{
- type: 'component',
- name: 'TextField',
- url: '/docs/api/admin-extensions/components/forms/textfield',
+ type: 'Generic',
+ title: 'Limitations',
+ anchorLink: 'limitations',
+ sectionContent: `- PasswordField doesn't include a built-in "show/hide password" toggle. The entered text is always masked and can't be revealed.
+- The component doesn't enforce password strength rules. You must validate the input yourself and use the \`error\` prop to communicate requirements.
+- PasswordField doesn't support the \`suffix\` prop available on [TextField](/docs/api/admin-extensions/{API_VERSION}/ui-components/forms/textfield). There is no built-in way to add a decoration or strength indicator inside the field.`,
},
],
+ related: [],
};
export default data;
diff --git a/packages/ui-extensions/src/surfaces/admin/components/PasswordField/PasswordField.ts b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/PasswordField.ts
index d7aa8d59a7..496ea7af12 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/PasswordField/PasswordField.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/PasswordField.ts
@@ -6,16 +6,31 @@ import {
MinMaxLengthProps,
} from '../shared';
+/**
+ * Props for the PasswordField component, a text input that masks its
+ * content for secure entry of sensitive values like passwords or PINs.
+ * It extends standard input props with min/max length constraints and
+ * autocomplete support for password managers.
+ */
export interface PasswordFieldProps
extends InputProps,
MinMaxLengthProps,
AutocompleteProps {}
+/**
+ * Autocomplete field types relevant to password inputs.
+ *
+ * - `new-password`: hints that the user is creating a new password, allowing
+ * password managers to suggest a strong generated password.
+ * - `current-password`: hints that the user is entering an existing password,
+ * allowing password managers to autofill a saved credential.
+ */
export type PasswordAutocompleteField = Extract<
AnyAutocompleteField,
'new-password' | 'current-password'
>;
+/** A PasswordField component for secure, masked text entry. */
export const PasswordField = createRemoteComponent<
'PasswordField',
PasswordFieldProps
diff --git a/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/basic-passwordfield.example.ts b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/basic-passwordfield.example.ts
index 787e4e478a..08008ae1a2 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/basic-passwordfield.example.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/basic-passwordfield.example.ts
@@ -1,14 +1,46 @@
-import {extend, TextField, PasswordField, BlockStack} from '@shopify/ui-extensions/admin';
-
-extend('Playground', (root) => {
- const blockStack = root.createComponent(BlockStack, {}, [
- root.createComponent(TextField, {
- label: 'Username',
- }),
- root.createComponent(PasswordField, {
- label: 'Password',
- }),
- ]);
-
- root.appendChild(blockStack);
-});
+import {extension, PasswordField, Button, BlockStack, Text} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.action.render',
+ (root, api) => {
+ const {data, close} = api;
+ let apiKey = '';
+
+ const stack = root.createComponent(BlockStack);
+
+ const heading = root.createComponent(
+ Text,
+ {fontWeight: 'bold'},
+ 'Connect warehouse API',
+ );
+
+ const field = root.createComponent(PasswordField, {
+ label: 'API key',
+ name: 'apiKey',
+ onChange: (value) => {
+ apiKey = value;
+ },
+ });
+
+ const saveButton = root.createComponent(
+ Button,
+ {
+ variant: 'primary',
+ onPress: async () => {
+ await fetch('/api/integrations/warehouse', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({apiKey}),
+ });
+ close();
+ },
+ },
+ 'Save credentials',
+ );
+
+ stack.appendChild(heading);
+ stack.appendChild(field);
+ stack.appendChild(saveButton);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/passwordfield-integration.example.ts b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/passwordfield-integration.example.ts
new file mode 100644
index 0000000000..f397d06356
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/passwordfield-integration.example.ts
@@ -0,0 +1,50 @@
+import {extension, PasswordField, TextField, EmailField, Button, BlockStack, Heading} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.action.render',
+ (root, api) => {
+ const {close} = api;
+
+ const stack = root.createComponent(BlockStack);
+
+ const heading = root.createComponent(
+ Heading,
+ {},
+ 'Connect fulfillment service',
+ );
+
+ const endpointField = root.createComponent(TextField, {
+ label: 'API endpoint',
+ name: 'endpoint',
+ });
+
+ const emailField = root.createComponent(EmailField, {
+ label: 'Account email',
+ name: 'accountEmail',
+ });
+
+ const tokenField = root.createComponent(PasswordField, {
+ label: 'API token',
+ name: 'apiToken',
+ });
+
+ const connectButton = root.createComponent(
+ Button,
+ {
+ variant: 'primary',
+ onPress: async () => {
+ await fetch('/api/fulfillment/connect', {method: 'POST'});
+ close();
+ },
+ },
+ 'Connect service',
+ );
+
+ stack.appendChild(heading);
+ stack.appendChild(endpointField);
+ stack.appendChild(emailField);
+ stack.appendChild(tokenField);
+ stack.appendChild(connectButton);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/passwordfield-validation.example.ts b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/passwordfield-validation.example.ts
new file mode 100644
index 0000000000..bdb27af35b
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/PasswordField/examples/passwordfield-validation.example.ts
@@ -0,0 +1,47 @@
+import {extension, PasswordField, Button, BlockStack} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.action.render',
+ (root, api) => {
+ const {close} = api;
+ let secret = '';
+
+ const stack = root.createComponent(BlockStack);
+
+ const field = root.createComponent(PasswordField, {
+ label: 'Webhook secret',
+ name: 'webhookSecret',
+ required: true,
+ onChange: (value) => {
+ secret = value;
+ if (value.length > 0 && value.length < 16) {
+ field.updateProps({error: 'Secret must be at least 16 characters'});
+ } else {
+ field.updateProps({error: undefined});
+ }
+ },
+ });
+
+ const saveButton = root.createComponent(
+ Button,
+ {
+ variant: 'primary',
+ onPress: async () => {
+ if (secret.length >= 16) {
+ await fetch('/api/webhooks/secret', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({secret}),
+ });
+ close();
+ }
+ },
+ },
+ 'Save webhook secret',
+ );
+
+ stack.appendChild(field);
+ stack.appendChild(saveButton);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Pressable/Pressable.doc.ts b/packages/ui-extensions/src/surfaces/admin/components/Pressable/Pressable.doc.ts
index 3669cbd9b5..37ef4bd3fa 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Pressable/Pressable.doc.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Pressable/Pressable.doc.ts
@@ -3,24 +3,27 @@ import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
const data: ReferenceEntityTemplateSchema = {
name: 'Pressable',
description:
- 'Use this component when you need to capture click or press events on its child elements without adding any additional visual styling. It subtly enhances user interaction by changing the cursor when hovering over the child elements, providing a clear indication of interactivity.',
+ 'The Pressable component creates a clickable area around its children without adding any visible button or link styling. It combines the layout capabilities of [Box](/docs/api/admin-extensions/{API_VERSION}/ui-components/layout-and-structure/box) with the navigation and click handling of [Link](/docs/api/admin-extensions/{API_VERSION}/ui-components/actions/link), making it useful for building custom interactive elements like clickable cards or list items.\n\nFor standard actions with button styling, use [Button](/docs/api/admin-extensions/{API_VERSION}/ui-components/actions/button).',
requires: '',
thumbnail: 'pressable-thumbnail.png',
isVisualComponent: true,
type: '',
definitions: [
{
- title: 'PressableProps',
- description: '',
+ title: 'Properties',
+ description:
+ 'Configure the following properties on the Pressable component.',
type: 'PressableProps',
},
],
- category: 'Components',
+ category: 'UI components',
subCategory: 'Actions',
defaultExample: {
image: 'pressable-default.png',
+ description:
+ 'Trigger an inventory sync or a data export from icon-labeled action rows. This example uses `Pressable` with `onPress` callbacks and [Icon](/docs/api/admin-extensions/{API_VERSION}/components/media-and-visuals/icon) labels to create clickable rows that call backend APIs without visible button styling.',
codeblock: {
- title: 'Simple pressable example',
+ title: 'Build custom action rows',
tabs: [
{
title: 'React',
@@ -28,21 +31,75 @@ const data: ReferenceEntityTemplateSchema = {
language: 'tsx',
},
{
- title: 'JS',
+ title: 'TS',
code: './examples/basic-pressable.example.ts',
- language: 'js',
+ language: 'ts',
},
],
},
},
-
- related: [
+ examples: {
+ description: '',
+ examples: [
+ {
+ description:
+ "Use `href` and `target` props to make a `Pressable` behave as a link, wrapping rich content like [Image](/docs/api/admin-extensions/{API_VERSION}/components/media-and-visuals/image) and text. This example creates a clickable card that opens the product's storefront page in a new tab.",
+ codeblock: {
+ title: 'Create a clickable product card',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/Pressable/examples/pressable-link.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/pressable-link.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ {
+ description:
+ 'Label custom tappable areas for screen readers using `accessibilityLabel`. This example renders warehouse location rows with [Badge](/docs/api/admin-extensions/{API_VERSION}/components/feedback-and-status-indicators/badge) indicators, where each row announces its stock level and location name to assistive technology.',
+ codeblock: {
+ title: 'Add accessible interactive regions',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/Pressable/examples/pressable-accessible.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/pressable-accessible.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ subSections: [
+ {
+ type: 'Generic',
+ title: 'Best practices',
+ anchorLink: 'best-practices',
+ sectionContent: `- **Use for custom interactive layouts:** Pressable is ideal when you need to make a group of elements clickable as a single unit, such as a card that navigates to a detail page. For standard actions, prefer [Button](/docs/api/admin-extensions/{API_VERSION}/ui-components/actions/button) or [Link](/docs/api/admin-extensions/{API_VERSION}/ui-components/actions/link).
+- **Pair with visual hover cues:** Pressable changes the cursor on hover, but consider combining it with other visual feedback so the interactive area is obvious to all users.
+- **Avoid nesting interactive elements:** Don't place [Button](/docs/api/admin-extensions/{API_VERSION}/ui-components/actions/button) or [Link](/docs/api/admin-extensions/{API_VERSION}/ui-components/actions/link) components inside a Pressable. Nesting interactive elements creates confusing behavior for keyboard and screen reader users and can lead to unexpected click handling.`,
+ },
{
- type: 'component',
- name: 'Button',
- url: '/docs/api/admin-extensions/components/actions/button',
+ type: 'Generic',
+ title: 'Limitations',
+ anchorLink: 'limitations',
+ sectionContent: `- Pressable doesn't render any visual styling beyond a cursor change on hover. All visual feedback, such as background color changes or borders, must be handled by the child components.
+- Pressable doesn't support form submission or reset behavior. Use a [Button](/docs/api/admin-extensions/{API_VERSION}/ui-components/actions/button) to submit or reset forms.
+- When using \`href\` for navigation, the \`onClick\` callback fires before navigation. You can't conditionally prevent navigation from within the callback.`,
},
],
+ related: [],
};
export default data;
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Pressable/Pressable.ts b/packages/ui-extensions/src/surfaces/admin/components/Pressable/Pressable.ts
index 9d356131df..a10c5cf35b 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Pressable/Pressable.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Pressable/Pressable.ts
@@ -2,8 +2,15 @@ import {createRemoteComponent} from '@remote-ui/core';
import type {LinkProps} from '../Link/Link';
import type {BoxProps} from '../Box/Box';
+/**
+ * Props for the Pressable component, which combines the layout capabilities
+ * of Box with the interactive behavior of Link. Use Pressable when you
+ * need a custom interactive area that can navigate to a URL or respond to
+ * press events while supporting flexible layout and styling options.
+ */
export interface PressableProps extends BoxProps, LinkProps {}
+/** A Pressable component that combines Box layout with Link interactivity. */
export const Pressable = createRemoteComponent<'Pressable', PressableProps>(
'Pressable',
);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/basic-pressable.example.ts b/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/basic-pressable.example.ts
index 8b6f67c8d5..dd59ce0722 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/basic-pressable.example.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/basic-pressable.example.ts
@@ -1,25 +1,52 @@
-import {
- extension,
- Icon,
- InlineStack,
- Pressable,
- Text,
-} from '@shopify/ui-extensions/admin';
+import {extension, Pressable, Text, Icon, InlineStack, BlockStack} from '@shopify/ui-extensions/admin';
-extension('Playground', (root) => {
- const pressable = root.createComponent(
- Pressable,
- {
+export default extension(
+ 'admin.product-details.block.render',
+ (root, api) => {
+ const {data} = api;
+ const productId = data.selected[0]?.id;
+
+ const stack = root.createComponent(BlockStack, {gap: true});
+
+ const heading = root.createComponent(Text, {fontWeight: 'bold'}, 'Quick actions');
+
+ const syncAction = root.createComponent(Pressable, {
+ padding: 'base',
+ onPress: async () => {
+ await fetch('/api/products/sync', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({productId}),
+ });
+ },
+ });
+ const syncRow = root.createComponent(InlineStack, {gap: true, blockAlignment: 'center'});
+ const syncIcon = root.createComponent(Icon, {name: 'RefreshMajor', accessibilityLabel: ''});
+ const syncText = root.createComponent(Text, {}, 'Sync inventory now');
+ syncRow.appendChild(syncIcon);
+ syncRow.appendChild(syncText);
+ syncAction.appendChild(syncRow);
+
+ const exportAction = root.createComponent(Pressable, {
padding: 'base',
- onPress: () => console.log('onPress event'),
- },
- [
- root.createComponent(InlineStack, {}, [
- root.createComponent(Text, {}, 'Go to App Dashboard'),
- root.createComponent(Icon, {name: 'AppsMajor'}),
- ]),
- ],
- );
+ onPress: async () => {
+ await fetch('/api/products/export', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({productId}),
+ });
+ },
+ });
+ const exportRow = root.createComponent(InlineStack, {gap: true, blockAlignment: 'center'});
+ const exportIcon = root.createComponent(Icon, {name: 'ExportMinor', accessibilityLabel: ''});
+ const exportText = root.createComponent(Text, {}, 'Export product data');
+ exportRow.appendChild(exportIcon);
+ exportRow.appendChild(exportText);
+ exportAction.appendChild(exportRow);
- root.appendChild(pressable);
-});
+ stack.appendChild(heading);
+ stack.appendChild(syncAction);
+ stack.appendChild(exportAction);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/pressable-accessible.example.ts b/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/pressable-accessible.example.ts
new file mode 100644
index 0000000000..a227b842e5
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/pressable-accessible.example.ts
@@ -0,0 +1,40 @@
+import {extension, Pressable, Text, Badge, InlineStack, BlockStack} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.block.render',
+ (root) => {
+
+ const stack = root.createComponent(BlockStack, {gap: true});
+
+ const heading = root.createComponent(Text, {fontWeight: 'bold'}, 'Warehouse locations');
+
+ const location1 = root.createComponent(Pressable, {
+ padding: 'base',
+ accessibilityLabel: 'View New York warehouse details — 142 units in stock',
+ onPress: () => console.log('Card pressed'),
+ });
+ const row1 = root.createComponent(InlineStack, {gap: true, blockAlignment: 'center'});
+ const name1 = root.createComponent(Text, {}, 'New York');
+ const badge1 = root.createComponent(Badge, {tone: 'success'}, '142 units');
+ row1.appendChild(name1);
+ row1.appendChild(badge1);
+ location1.appendChild(row1);
+
+ const location2 = root.createComponent(Pressable, {
+ padding: 'base',
+ accessibilityLabel: 'View Los Angeles warehouse details — 3 units in stock, low stock',
+ onPress: () => console.log('Card pressed'),
+ });
+ const row2 = root.createComponent(InlineStack, {gap: true, blockAlignment: 'center'});
+ const name2 = root.createComponent(Text, {}, 'Los Angeles');
+ const badge2 = root.createComponent(Badge, {tone: 'warning'}, '3 units');
+ row2.appendChild(name2);
+ row2.appendChild(badge2);
+ location2.appendChild(row2);
+
+ stack.appendChild(heading);
+ stack.appendChild(location1);
+ stack.appendChild(location2);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/pressable-link.example.ts b/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/pressable-link.example.ts
new file mode 100644
index 0000000000..9cd4517e79
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/Pressable/examples/pressable-link.example.ts
@@ -0,0 +1,32 @@
+import {extension, Pressable, Text, Image, BlockStack} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.block.render',
+ (root, api) => {
+ const {data} = api;
+ const productId = data.selected[0]?.id;
+ const numericId = productId?.split('/').pop();
+
+ const stack = root.createComponent(BlockStack, {gap: true});
+
+ const card = root.createComponent(Pressable, {
+ href: `https://your-store.myshopify.com/products/${numericId}`,
+ target: '_blank',
+ padding: 'base',
+ });
+
+ const image = root.createComponent(Image, {
+ source: 'https://cdn.shopify.com/s/files/placeholder-images/product.png',
+ accessibilityLabel: 'Product preview',
+ });
+ const title = root.createComponent(Text, {fontWeight: 'bold'}, 'View on storefront');
+ const description = root.createComponent(Text, {}, 'Opens the live product page in a new tab');
+
+ card.appendChild(image);
+ card.appendChild(title);
+ card.appendChild(description);
+
+ stack.appendChild(card);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/ProgressIndicator.doc.ts b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/ProgressIndicator.doc.ts
index 84305047a6..bbed452fde 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/ProgressIndicator.doc.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/ProgressIndicator.doc.ts
@@ -3,24 +3,27 @@ import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
const data: ReferenceEntityTemplateSchema = {
name: 'ProgressIndicator',
description:
- 'Use this component to notify merchants that their action is being processed or loaded.',
+ 'The ProgressIndicator component displays an animated spinner to communicate that content is loading or an action is being processed. Use ProgressIndicator to provide visual feedback during asynchronous operations like data fetching, form submission, or background processing.\n\nProgressIndicator supports a range of sizes from compact inline indicators to larger full-section spinners, so it can be placed alongside other components or used as a standalone loading state.',
requires: '',
thumbnail: 'progressindicator-thumbnail.png',
isVisualComponent: true,
type: '',
definitions: [
{
- title: 'ProgressIndicatorProps',
- description: '',
+ title: 'Properties',
+ description:
+ 'Configure the following properties on the ProgressIndicator component.',
type: 'ProgressIndicatorProps',
},
],
- category: 'Components',
- subCategory: 'Media',
+ category: 'UI components',
+ subCategory: 'Feedback and status indicators',
defaultExample: {
image: 'progressindicator-default.png',
+ description:
+ 'Show a loading indicator while querying product data from the [GraphQL Admin API](/docs/api/admin-graphql/), then replace it with the results. This example uses `ProgressIndicator` with `size` and `accessibilityLabel` props during the query, then displays the product title and inventory count.',
codeblock: {
- title: 'Simple spinner example',
+ title: 'Show loading during API query',
tabs: [
{
title: 'React',
@@ -28,21 +31,74 @@ const data: ReferenceEntityTemplateSchema = {
language: 'tsx',
},
{
- title: 'JS',
+ title: 'TS',
code: './examples/basic-progressindicator.example.ts',
- language: 'js',
+ language: 'ts',
},
],
},
},
-
- related: [
+ examples: {
+ description: '',
+ examples: [
+ {
+ description:
+ 'Show a prominent loading indicator while a sync is in progress inside an [action modal](/docs/api/admin-extensions/{API_VERSION}/components/settings-and-templates/adminaction). This example pairs a `base`-sized indicator with a status message and a cancel [Button](/docs/api/admin-extensions/{API_VERSION}/components/actions/button), keeping merchants informed and in control.',
+ codeblock: {
+ title: 'Display sync progress in action modal',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/ProgressIndicator/examples/progressindicator-sizes.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/progressindicator-sizes.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ {
+ description:
+ 'Place a compact `small-300` spinner inline with status text using `tone="inherit"` to match the surrounding text color. This example renders the indicator alongside a description inside an [InlineStack](/docs/api/admin-extensions/{API_VERSION}/components/layout-and-structure/inlinestack), creating a subtle loading cue.',
+ codeblock: {
+ title: 'Add inline loading indicator',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/ProgressIndicator/examples/progressindicator-inline.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/progressindicator-inline.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ subSections: [
+ {
+ type: 'Generic',
+ title: 'Best practices',
+ anchorLink: 'best-practices',
+ sectionContent: `- **Match the size to the context:** Use smaller sizes for inline indicators next to buttons or fields, and larger sizes for full-section or full-page loading states.
+- **Show spinners only when needed:** Display the ProgressIndicator only while an operation is in progress. Remove it immediately when loading completes or an error occurs. Avoid leaving a spinner visible indefinitely.`,
+ },
{
- type: 'component',
- name: 'Button',
- url: '/docs/api/admin-extensions/components/actions/button',
+ type: 'Generic',
+ title: 'Limitations',
+ anchorLink: 'limitations',
+ sectionContent: `- ProgressIndicator supports only the \`spinner\` variant. There is no progress bar or determinate progress indicator available.
+- ProgressIndicator doesn't communicate progress percentage or estimated time remaining. For operations where progress can be measured, consider pairing the spinner with a text description of the current status.
+- The spinner animation can't be paused or customized. It always displays the same continuous rotation animation.`,
},
],
+ related: [],
};
export default data;
diff --git a/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/ProgressIndicator.ts b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/ProgressIndicator.ts
index a077bac6ba..2860899019 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/ProgressIndicator.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/ProgressIndicator.ts
@@ -1,31 +1,47 @@
import {createRemoteComponent} from '@remote-ui/core';
import {GlobalProps, SizeScale, AccessibilityLabelProps} from '../shared';
+/**
+ * Props for the ProgressIndicator component, which renders a visual cue
+ * (such as a spinner) to communicate that a process is underway. Use this
+ * component to reassure users that content is loading or an action is being
+ * processed.
+ */
export interface ProgressIndicatorProps
extends GlobalProps,
AccessibilityLabelProps {
/**
- * The size of the component.
+ * The size of the progress indicator. This prop is required
+ * and determines how large the indicator renders. Use smaller
+ * sizes for inline or compact placements, and larger sizes for
+ * prominent loading states.
*/
size: SizeScale;
/**
- * Set the color of the progress indicator.
+ * The color of the progress indicator.
*
- * - `inherit` will take the color value from its parent,
- * giving the progress indicator a monochrome appearance.
+ * - `inherit`: Takes the color value from its parent, giving
+ * the progress indicator a monochrome appearance. This is useful
+ * when you want the indicator to blend with surrounding text or icons.
+ * - `default`: Uses the standard progress indicator color.
*
* @defaultValue 'default'
*/
tone?: 'inherit' | 'default';
/**
- * Style of the progress indicator
- * @default 'spinner'
+ * The visual style of the progress indicator.
+ *
+ * - `spinner`: Renders a circular spinning animation to indicate
+ * an indeterminate loading state.
+ *
+ * @defaultValue 'spinner'
*/
variant?: 'spinner';
}
+/** A ProgressIndicator component that renders a loading spinner or similar visual cue. */
export const ProgressIndicator = createRemoteComponent<
'ProgressIndicator',
ProgressIndicatorProps
diff --git a/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/basic-progressindicator.example.ts b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/basic-progressindicator.example.ts
index 1c5b343f39..a98f97992d 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/basic-progressindicator.example.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/basic-progressindicator.example.ts
@@ -1,18 +1,43 @@
-import {
- extension,
- ProgressIndicator,
-} from '@shopify/ui-extensions/admin';
+import {extension, ProgressIndicator, Text, BlockStack} from '@shopify/ui-extensions/admin';
export default extension(
- 'Playground',
- (root) => {
- const progressIndicator = root.createComponent(
- ProgressIndicator,
- {
- size: 'small-200',
- },
+ 'admin.product-details.block.render',
+ async (root, api) => {
+ const {data, query} = api;
+ const productId = data.selected[0]?.id;
+
+ const stack = root.createComponent(BlockStack);
+
+ const loader = root.createComponent(ProgressIndicator, {
+ size: 'small-200',
+ accessibilityLabel: 'Loading product data',
+ });
+ stack.appendChild(loader);
+ root.appendChild(stack);
+
+ const result = await query(
+ `query Product($id: ID!) {
+ product(id: $id) { title totalInventory }
+ }`,
+ {variables: {id: productId}},
);
- root.appendChild(progressIndicator);
+ stack.removeChild(loader);
+
+ const product = result?.data?.product;
+ if (product) {
+ const title = root.createComponent(
+ Text,
+ {fontWeight: 'bold'},
+ product.title,
+ );
+ const inventory = root.createComponent(
+ Text,
+ {},
+ `Total inventory: ${product.totalInventory} units`,
+ );
+ stack.appendChild(title);
+ stack.appendChild(inventory);
+ }
},
);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/progressindicator-inline.example.ts b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/progressindicator-inline.example.ts
new file mode 100644
index 0000000000..0446cb0104
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/progressindicator-inline.example.ts
@@ -0,0 +1,36 @@
+import {extension, ProgressIndicator, Text, InlineStack, BlockStack} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.block.render',
+ (root) => {
+
+ const stack = root.createComponent(BlockStack);
+
+ const heading = root.createComponent(
+ Text,
+ {fontWeight: 'bold'},
+ 'Checking eligibility...',
+ );
+
+ const row = root.createComponent(InlineStack);
+
+ const indicator = root.createComponent(ProgressIndicator, {
+ size: 'small-300',
+ tone: 'inherit',
+ accessibilityLabel: 'Checking product eligibility',
+ });
+
+ const label = root.createComponent(
+ Text,
+ {},
+ 'Verifying product meets marketplace requirements',
+ );
+
+ row.appendChild(indicator);
+ row.appendChild(label);
+
+ stack.appendChild(heading);
+ stack.appendChild(row);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/progressindicator-sizes.example.ts b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/progressindicator-sizes.example.ts
new file mode 100644
index 0000000000..876611c9eb
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/ProgressIndicator/examples/progressindicator-sizes.example.ts
@@ -0,0 +1,37 @@
+import {extension, ProgressIndicator, Text, Button, BlockStack, InlineStack} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.action.render',
+ (root, api) => {
+ const {data, close} = api;
+
+ const stack = root.createComponent(BlockStack);
+
+ const message = root.createComponent(
+ Text,
+ {},
+ 'Syncing product data to your warehouse system...',
+ );
+
+ const spinnerRow = root.createComponent(InlineStack);
+ const spinner = root.createComponent(ProgressIndicator, {
+ size: 'base',
+ accessibilityLabel: 'Syncing in progress',
+ });
+ spinnerRow.appendChild(spinner);
+
+ const cancelButton = root.createComponent(
+ Button,
+ {
+ variant: 'tertiary',
+ onPress: () => close(),
+ },
+ 'Cancel',
+ );
+
+ stack.appendChild(message);
+ stack.appendChild(spinnerRow);
+ stack.appendChild(cancelButton);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Section/Section.doc.ts b/packages/ui-extensions/src/surfaces/admin/components/Section/Section.doc.ts
index 7f14d0a68a..64f0651179 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Section/Section.doc.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Section/Section.doc.ts
@@ -3,24 +3,27 @@ import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
const data: ReferenceEntityTemplateSchema = {
name: 'Section',
description:
- '`Section` is a structural component that allows thematic grouping of content. Its visual style is contextual and controlled by Shopify, so a `Section` may look different depending on the component it is nested inside.\n\n`Section` also automatically increases the heading level for its content to ensure a semantically correct heading structure in the document. To further increase the heading level inside the `Section`, consider nesting new `Section`s.',
+ 'The Section component creates a visual and semantic grouping for related content, with an optional heading and adjustable padding. Use Section to organize your extension into distinct content areas that merchants can scan and understand at a glance.\n\nSection automatically increments the heading level for any [Heading](/docs/api/admin-extensions/{API_VERSION}/ui-components/typography-and-content/heading) components nested inside it, ensuring a correct document outline. Nest Section components to create deeper heading hierarchies without manually managing levels.',
requires: '',
thumbnail: 'section-thumbnail.png',
isVisualComponent: true,
type: '',
definitions: [
{
- title: 'SectionProps',
- description: '',
+ title: 'Properties',
+ description:
+ 'Configure the following properties on the Section component.',
type: 'SectionProps',
},
],
- category: 'Components',
- subCategory: 'Structure',
+ category: 'UI components',
+ subCategory: 'Layout and structure',
defaultExample: {
image: 'section-default.png',
+ description:
+ 'Group warehouse location details under a labeled heading. This example uses `Section` with a `heading` prop to bundle a warehouse name, storage slot, and stock count into one content area.',
codeblock: {
- title: 'Section to an app page',
+ title: 'Group content with a heading',
tabs: [
{
title: 'React',
@@ -28,13 +31,73 @@ const data: ReferenceEntityTemplateSchema = {
language: 'tsx',
},
{
- title: 'JS',
+ title: 'TS',
code: './examples/basic-Section.example.ts',
- language: 'js',
+ language: 'ts',
},
],
},
},
+ examples: {
+ description: '',
+ examples: [
+ {
+ description:
+ 'Nest sections to create multi-level content grouping with automatic heading level adjustment. This example places a "Safety certifications" section inside a "Product compliance" parent section, with heading levels adapting to reflect the hierarchy.',
+ codeblock: {
+ title: 'Nest sections for content hierarchy',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/Section/examples/section-nested.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/section-nested.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ {
+ description:
+ 'Provide screen readers with a more descriptive section context than the visible heading alone using `accessibilityLabel`. This example labels a shipping configuration section with form fields, so assistive technology announces the full purpose of the form group.',
+ codeblock: {
+ title: 'Add accessible section descriptions',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/Section/examples/section-accessible.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/section-accessible.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ subSections: [
+ {
+ type: 'Generic',
+ title: 'Best practices',
+ anchorLink: 'best-practices',
+ sectionContent: `- **Use Section to group related content:** Wrap related fields, text, or actions in a Section with a descriptive heading to create clear visual and semantic groupings.
+- **Provide an accessibility label when there is no heading:** If the section doesn't have a visible heading, provide an accessibility label so screen reader users understand what the section contains.
+- **Nest Section components for deeper structure:** Each nested Section increments the heading level. This creates a natural document outline (h1 \u2192 h2 \u2192 h3) without manually managing heading levels.`,
+ },
+ {
+ type: 'Generic',
+ title: 'Limitations',
+ anchorLink: 'limitations',
+ sectionContent: `- Section's visual appearance is controlled by Shopify and can't be customized. It might render differently in different component contexts ([AdminBlock](/docs/api/admin-extensions/{API_VERSION}/ui-components/settings-and-templates/adminblock) versus [AdminAction](/docs/api/admin-extensions/{API_VERSION}/ui-components/settings-and-templates/adminaction)).
+- The heading level auto-increment stops at h6. Nesting sections beyond six levels deep will still render h6 headings.`,
+ },
+ ],
related: [],
};
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Section/Section.ts b/packages/ui-extensions/src/surfaces/admin/components/Section/Section.ts
index a650a703bb..ff742c1e6f 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Section/Section.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Section/Section.ts
@@ -1,35 +1,44 @@
import {createRemoteComponent} from '@remote-ui/core';
+/**
+ * Props for the Section component, which groups related content under an
+ * optional heading. Sections provide both visual and semantic structure,
+ * making it easier for users (and assistive technologies) to navigate
+ * through distinct areas of an extension.
+ */
export interface SectionProps {
/**
- * A label used to describe the section that will be announced by assistive technologies.
- *
- * When no `heading` property is provided or included as a children of the Section, you **must** provide an
- * `accessibilityLabel` to describe the Section. This is important as it allows assistive technologies to provide
- * the right context to users.
+ * A label that describes the section for assistive technologies such as
+ * screen readers. When no `heading` prop is provided, then you **must** provide
+ * an `accessibilityLabel` so that assistive technologies can announce
+ * meaningful context about the section to users.
*/
accessibilityLabel?: string;
/**
- * A title that describes the content of the section.
+ * A visible title displayed at the top of the section that describes
+ * its content. When provided, this heading also serves as the accessible
+ * label for the section unless `accessibilityLabel` is explicitly set.
*/
heading?: string;
/**
- * Adjust the padding of all edges.
- *
- * `base`: applies padding that is appropriate for the element. Note that it may result in no padding if Shopify
- * believes this is the right design decision in a particular context.
+ * The padding on all edges of the section.
*
- * `none`: removes all padding from the element. This can be useful when elements inside the Section need to span
- * to the edge of the Section. For example, a full-width image. In this case, rely on `Box` or any other layout
- * element to bring back the desired padding for the rest of the content.
+ * - `base`: Applies padding that's appropriate for the context.
+ * This might result in no padding if Shopify determines that's the
+ * right design decision for a particular placement.
+ * - `none`: Removes all padding from the section. This is useful when
+ * child elements need to span to the section's edges (for example, a
+ * full-width image). Use Box or another layout component to restore
+ * padding for the remaining content.
*
- * @default: "base"
+ * @defaultValue 'base'
*/
padding?: 'base' | 'none';
}
+/** A Section component that groups related content under an optional heading. */
export const Section = createRemoteComponent<'Section', SectionProps>(
'Section',
);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Section/examples/basic-Section.example.ts b/packages/ui-extensions/src/surfaces/admin/components/Section/examples/basic-Section.example.ts
index 199f00ef82..a06ecbe0fa 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Section/examples/basic-Section.example.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Section/examples/basic-Section.example.ts
@@ -1,19 +1,24 @@
-import {
- extend,
- Section,
-} from '@shopify/ui-extensions/admin';
+import {extension, Section, Text, BlockStack} from '@shopify/ui-extensions/admin';
-export default extend(
- 'Playground',
+export default extension(
+ 'admin.product-details.block.render',
(root) => {
- const section = root.createComponent(
- Section,
- {
- heading: 'Section heading',
- },
- 'Section content'
- );
- root.appendChild(section);
+ const stack = root.createComponent(BlockStack);
+
+ const section = root.createComponent(Section, {
+ heading: 'Inventory details',
+ });
+
+ const warehouse = root.createComponent(Text, {}, 'Warehouse: East Coast — New York');
+ const slot = root.createComponent(Text, {}, 'Storage slot: A-42');
+ const quantity = root.createComponent(Text, {}, 'Units in stock: 247');
+
+ section.appendChild(warehouse);
+ section.appendChild(slot);
+ section.appendChild(quantity);
+
+ stack.appendChild(section);
+ root.appendChild(stack);
},
);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Section/examples/section-accessible.example.ts b/packages/ui-extensions/src/surfaces/admin/components/Section/examples/section-accessible.example.ts
new file mode 100644
index 0000000000..14f5e472c5
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/Section/examples/section-accessible.example.ts
@@ -0,0 +1,34 @@
+import {extension, Section, TextField, BlockStack} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.action.render',
+ (root) => {
+
+ const stack = root.createComponent(BlockStack);
+
+ const shippingSection = root.createComponent(Section, {
+ heading: 'Shipping configuration',
+ accessibilityLabel: 'Configure shipping dimensions and weight for this product',
+ });
+
+ const weight = root.createComponent(TextField, {
+ label: 'Weight (kg)',
+ name: 'weight',
+ });
+ const length = root.createComponent(TextField, {
+ label: 'Length (cm)',
+ name: 'length',
+ });
+ const width = root.createComponent(TextField, {
+ label: 'Width (cm)',
+ name: 'width',
+ });
+
+ shippingSection.appendChild(weight);
+ shippingSection.appendChild(length);
+ shippingSection.appendChild(width);
+
+ stack.appendChild(shippingSection);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Section/examples/section-nested.example.ts b/packages/ui-extensions/src/surfaces/admin/components/Section/examples/section-nested.example.ts
new file mode 100644
index 0000000000..02018fc105
--- /dev/null
+++ b/packages/ui-extensions/src/surfaces/admin/components/Section/examples/section-nested.example.ts
@@ -0,0 +1,37 @@
+import {extension, Section, Text, BlockStack} from '@shopify/ui-extensions/admin';
+
+export default extension(
+ 'admin.product-details.block.render',
+ (root) => {
+
+ const stack = root.createComponent(BlockStack);
+
+ const outer = root.createComponent(Section, {
+ heading: 'Product compliance',
+ });
+
+ const intro = root.createComponent(
+ Text,
+ {},
+ 'Regulatory status for product distribution.',
+ );
+
+ const inner = root.createComponent(Section, {
+ heading: 'Safety certifications',
+ });
+
+ const cert1 = root.createComponent(Text, {}, 'UL Listed — Class II');
+ const cert2 = root.createComponent(Text, {}, 'CE Marking — Approved');
+ const cert3 = root.createComponent(Text, {}, 'FCC Part 15 — Compliant');
+
+ inner.appendChild(cert1);
+ inner.appendChild(cert2);
+ inner.appendChild(cert3);
+
+ outer.appendChild(intro);
+ outer.appendChild(inner);
+
+ stack.appendChild(outer);
+ root.appendChild(stack);
+ },
+);
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Select/Select.doc.ts b/packages/ui-extensions/src/surfaces/admin/components/Select/Select.doc.ts
index 6c46d2c97d..e30f2d75e7 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Select/Select.doc.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Select/Select.doc.ts
@@ -3,24 +3,28 @@ import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
const data: ReferenceEntityTemplateSchema = {
name: 'Select',
description:
- 'Use this when you want to give users a predefined list of options to choose from.',
+ 'The Select component provides a dropdown menu for choosing a single value from a predefined list of options. It supports option groups, placeholder text, disabled and read-only states, and inline validation errors.\n\nFor visible radio/checkbox lists, use [ChoiceList](/docs/api/admin-extensions/{API_VERSION}/ui-components/forms/choicelist).',
requires: '',
thumbnail: 'select-thumbnail.png',
isVisualComponent: true,
type: '',
definitions: [
{
- title: 'SelectProps',
- description: '',
+ title: 'Properties',
+ description:
+ 'Configure the following properties on the Select component.',
type: 'SelectProps',
},
],
- category: 'Components',
+ related: [],
+ category: 'UI components',
subCategory: 'Forms',
defaultExample: {
image: 'select-default.png',
+ description:
+ 'Assign a product to a warehouse location from a dropdown of four regions. This example uses `Select` with four warehouse options, and a [Button](/docs/api/admin-extensions/{API_VERSION}/components/actions/button) that assigns the warehouse and closes the modal.',
codeblock: {
- title: 'Simple Select example',
+ title: 'Assign warehouse location',
tabs: [
{
title: 'React',
@@ -28,15 +32,77 @@ const data: ReferenceEntityTemplateSchema = {
language: 'tsx',
},
{
- title: 'JS',
+ title: 'TS',
code: './examples/basic-select.example.ts',
- language: 'js',
+ language: 'ts',
},
],
},
},
-
- related: [],
+ examples: {
+ description: '',
+ examples: [
+ {
+ description:
+ 'Guide merchants to make a selection by adding a `placeholder` that appears before any option is chosen. This example uses a placeholder message in a product classification dropdown, making it clear that a selection is expected.',
+ codeblock: {
+ title: 'Add placeholder prompt text',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/Select/examples/select-placeholder.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/select-placeholder.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ {
+ description:
+ 'Validate that a `required` select field has a value before submission using the `error` prop. This example shows an inline error when merchants attempt to save without choosing a shipping class, preventing incomplete data from reaching your backend.',
+ codeblock: {
+ title: 'Validate required selection on submit',
+ tabs: [
+ {
+ title: 'React',
+ code: '../../../../../../ui-extensions-react/src/surfaces/admin/components/Select/examples/select-error.example.tsx',
+ language: 'tsx',
+ },
+ {
+ title: 'TS',
+ code: './examples/select-error.example.ts',
+ language: 'ts',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ subSections: [
+ {
+ type: 'Generic',
+ title: 'Best practices',
+ anchorLink: 'best-practices',
+ sectionContent: `- **Use Select for four or more options:** When there are only two or three options, use a [ChoiceList](/docs/api/admin-extensions/{API_VERSION}/ui-components/forms/choicelist) with radio buttons instead because all options are visible without interaction.
+- **Order options logically:** Arrange options in an order that makes sense to merchants, such as alphabetically, by popularity, or by a natural sequence. Place the most common choice first when there's no inherent order.
+- **Use option groups for long lists:** When the list has many options, group related items using option groups to make scanning easier.
+- **Write a clear label:** The required \`label\` prop describes what the selection controls. Use specific labels like "Country" or "Sort by".
+- **Use a placeholder when no default makes sense:** Set the \`placeholder\` prop (like "Select a country") when there's no obvious default value. This communicates that the merchant needs to make a choice.`,
+ },
+ {
+ type: 'Generic',
+ title: 'Limitations',
+ anchorLink: 'limitations',
+ sectionContent: `- Select supports single selection only. For multi-selection, use a [ChoiceList](/docs/api/admin-extensions/{API_VERSION}/ui-components/forms/choicelist) with \`multiple\` set to \`true\`.
+- The dropdown options can't be customized with colors, icons, or rich content.
+- Select doesn't support search or filtering within the options list. For large option sets, use option groups to help merchants find items faster.
+- Dynamic loading of options as the user scrolls isn't supported.`,
+ },
+ ],
};
export default data;
diff --git a/packages/ui-extensions/src/surfaces/admin/components/Select/Select.ts b/packages/ui-extensions/src/surfaces/admin/components/Select/Select.ts
index 0b86aa7084..407613b2f4 100644
--- a/packages/ui-extensions/src/surfaces/admin/components/Select/Select.ts
+++ b/packages/ui-extensions/src/surfaces/admin/components/Select/Select.ts
@@ -1,14 +1,20 @@
import {createRemoteComponent} from '@remote-ui/core';
+/**
+ * Props for the Select component, a form control that lets the user choose
+ * one value from a predefined list of options presented in a dropdown menu.
+ */
export interface SelectProps {
/**
- * Whether the select can be changed.
+ * Whether the select is disabled. When `true`, the field can't be
+ * interacted with and appears in a dimmed state.
*/
disabled?: boolean;
/**
- * Indicate an error to the user. The field will be given a specific stylistic treatment
- * to communicate problems that have to be resolved immediately.
+ * An error message to display beneath the field. When set, the field
+ * receives a specific stylistic treatment to communicate a problem that
+ * needs to be resolved immediately.
*/
error?: string;
@@ -19,129 +25,160 @@ export interface SelectProps {
id?: string;
/**
- * Content to use as the field label.
+ * The visible label displayed above the select field. This label
+ * is also used by assistive technologies to describe the field.
*/
label: string;
/**
* An identifier for the field that is unique within the nearest
- * containing `Form` component.
+ * containing Form component. This value is used when submitting
+ * the form data.
*/
name?: string;
/**
- * Callback when focus is removed.
+ * A callback that fires when the field loses focus. Use this
+ * to trigger validation or other side effects when the user
+ * moves away from the select.
*/
onBlur?(): void;
/**
- * A callback that is run whenever the selected option changes. This callback
- * is called with the string `value` of the selected `option`. This component
- * is [controlled](https://reactjs.org/docs/forms.html#controlled-components),
- * so you must store this value in state and reflect it back in the `value`
- * prop of the select.
+ * A callback that fires whenever the selected option changes.
+ * The callback receives the string `value` of the newly selected
+ * option. This component is controlled, so you must store this
+ * value in state and reflect it back in the `value` prop.
*/
onChange?(value: string): void;
/**
- * Callback when input is focused.
+ * A callback that fires when the field receives focus. Use this
+ * to trigger side effects when the user interacts with the select.
*/
onFocus?(): void;
/**
- * The options a user can select from.
- *
- * When both `options` and children `Option` or `OptionGroup` are provided,
- * the options will be merged together, with the `options` property
- * taking precedence.
+ * The options a user can select from, provided as an array of
+ * `OptionDescription` or `OptionGroupDescription` objects. Each
+ * option requires a `label` and `value`.
*/
options: (OptionDescription | OptionGroupDescription)[];
/**
* A short hint that describes the expected value of the field.
+ * The placeholder is displayed when no `value` is set, giving
+ * the user guidance on what to select.
*/
placeholder?: string;
/**
- * Whether the field is read-only.
+ * Whether the field is read-only. When `true`, the current value
+ * can't be changed by the user but the field remains focusable
+ * and its value is still included in form submissions.
*/
readOnly?: boolean;
/**
- * Whether the field needs a value. This requirement adds semantic value
- * to the field, but it will not cause an error to appear automatically.
- * If you want to present an error when this field is empty, you can do
- * so with the `error` prop.
+ * Whether the field requires a value. This adds semantic meaning
+ * to the field for assistive technologies, but it won't cause an
+ * error to appear automatically. If you want to present an error
+ * when this field is empty, use the `error` prop.
*/
required?: boolean;
/**
- * The active option for the select. This should match to one of the
- * `value` properties in the `options` property or one of the `