Using Polaris web components
Polaris web components are Shopify’s UI toolkit for building interfaces that match the Shopify Checkout design system. This toolkit provides a set of custom HTML elements (web components) that you can use to create consistent, accessible, and performant user interfaces for the Checkout UI Extensions.
Anchor to stylingStyling
Polaris web components come with built-in styling that follows Shopify’s design system. The components will automatically apply the correct styling based on the properties you set and the context in which they are used. For example, headings automatically display at progressively less prominent sizes based on how many levels deep they are nested inside of sections. All components inherit a merchant’s brand settings and the CSS cannot be altered or overridden.
Example
JSX
Examples
Example
JSX
<s-box padding="base" background="subdued" border="base" borderRadius="base" class="my-custom-class" > Content </s-box>
Anchor to custom-layoutCustom layout
When you need to build custom layouts you can use s-stack, s-grid and s-box.
s-stackands-griddo not include spacing between children by default. To apply white space between children use thegapproperty- When
s-stackisdirection="inline"it will automatically wrap children to a new line when space is limited. s-gridwill allow children to overflow unless template rows/columns are properly set.- Order is important for shorthand properties, e.g. border takes
size-keyword,color-keyword,style-keyword
Anchor to scaleScale
Our components use a middle-out scale for multiple properties like padding, size and gap.
Our scale moves from the middle out:
small-300is smaller thansmall-100large-300is bigger thanlarge-100small-100andlarge-100have aliases ofsmallandlargebaseis the default value
Example
Examples
Example
Default
export type Scale = | 'small-300' | 'small-200' | 'small-100' | 'small' // alias of small-100 | 'base' | 'large' // alias of large-100 | 'large-100' | 'large-200' | 'large-300';
Anchor to responsive-valuesResponsive values
Some properties accept responsive values, which enables you to change the value of the property depending on a parent inline size.
The syntax for a responsive value generally follows the ternary operator syntax. For example, @container (inline-size > 500px) large, small means that the value will be large if the container is more than 500px wide, and small if the container is 500px or less. The syntax rules are:
- Begin the value with
@container - Optionally add a name to target a specific container
- Use the
inline-sizekeyword inside of parentheses to query the inline-size of the container. This is the condition that will be evaluated to determine which value to use. - Set the value if that condition is true
- Set the value to be used if the condition is false.
For greater compatibility on older browsers, container queries must follow a mobile-first approach. The fallback value (when the condition is false) must always represent your smallest supported design, with the condition value (when the condition is true) providing styles for larger containers. For example, <Stack direction="@container (inline-size > 300px) inline, block"> ensures that browsers that do not support container styles get a design that works in all container sizes.
Anchor to responsive-values-using-s-query-containerUsing s-query-container
When using responsive values, you must also place the <s-query-container> component in the location you want to query the inline-size.
By default, the responsive value will query against the closest parent; to look up a specific parent, this component also accepts a containername attribute which adds a name to the container. Then add that name after @container in your responsive query to target it.
Anchor to responsive-values-values-with-reserved-charactersValues with reserved characters
Some values could contain reserved characters used in the responsive value syntax, such as () or ,. To use these values, escape them by wrapping them in quotes.
Anchor to responsive-values-advanced-patternsAdvanced patterns
The syntax is flexible enough to support advanced patterns such as compound conditions, and|or conditions, and nested conditions.
HTML
Examples
HTML
<s-box padding="@container (inline-size > 500px) large-400, small" > This padding will be "large-400" when the container is more than 500px. Otherwise it will be "small". </s-box>
Anchor to interactive-elementsInteractive elements
s-button, s-link and s-clickable render as anchor elements when they have a href and render as a button element when they have an without a href. The HTML specification states that interactive elements cannot have interactive children.
s-clickable is an escape hatch for when s-link and s-button are not able to implement a specific design. You should always try to use s-link and s-button first.
Interactive components with target="auto" automatically use _self for internal links and _blank for external URLs. This behavior ensures a consistent navigation experience for users without requiring developers to manually set the correct target for each link.
Anchor to variant-tone-and-colorVariant tone and color
The tone is used to apply a group of color design tokens to the component such as critical, success or info.
The color adjusts the intensity of the tone making it more subdued or strong.
The variant is used to change how the component is rendered to match the design language this is different for each component.
Example
Examples
Example
Default
<s-button tone="critical" variant="primary"> Primary Critical Button </s-button> <s-badge tone="success" color="strong"> Success Strong Badge </s-badge>
Anchor to using-with-preactUsing with Preact
For UI Extensions, Shopify provides Preact as the framework of choice. Using Polaris web components with Preact is very similar to using them with React.
Example
JSX
Examples
Example
JSX
export function ProductExtension() { return ( <s-box padding="base"> <s-stack gap="base"> <s-text>Enable special pricing</s-text> <s-checkbox onChange={() => console.log('Checkbox toggled')} /> <s-number-field label="Discount percentage" suffix="%" min="0" max="100" /> </s-stack> </s-box> ); }
Anchor to properties-vs-attributesProperties vs attributes
Polaris web components follow the same property and attribute patterns as standard HTML elements. Understanding this distinction is important for using the components effectively.
Anchor to properties-vs-attributes-key-conceptsKey concepts
- Attributes are HTML attributes that appear in the HTML markup.
- Properties are JavaScript object properties accessed directly on the DOM element.
- Most attributes in Polaris web components are reflected as properties, with a few exceptions like
valueandcheckedwhich follow HTML’s standard behavior.
Anchor to properties-vs-attributes-how-jsx-properties-are-appliedHow JSX properties are applied
When using Polaris web components in JSX, the framework determines how to apply your props based on whether the element has a matching property name.
If the element has a property with the exact same name as your prop, the value is set as a property. Otherwise, it’s applied as an attribute. Here’s how this works in pseudocode:
For Polaris web components, you can generally just use the property names as documented, and everything will work as expected.
Examples
Anchor to event-handlingEvent Handling
Polaris web components use standard DOM events, making them work seamlessly with your preferred framework. You can attach event handlers using the same patterns as with native HTML elements.
Anchor to event-handling-basic-event-handlingBasic Event Handling
Event handlers in Polaris components work just like standard HTML elements. In frameworks, use the familiar camelCase syntax (like in Preact). In plain HTML, use lowercase attributes or .
Anchor to event-handling-form-input-eventsForm Input Events
Polaris form components support two primary event types for tracking input changes:
- onInput: Fires immediately on every keystroke or value change
- onChange: Fires when the value is committed; for text fields that happens on blur; for checkboxes and radio buttons immediately after
.
Choose the appropriate event based on your needs:
- Use
for real-time validation or character counting - Use
for validation after a user completes their input
Anchor to event-handling-focus-managementFocus Management
Track when users interact with form elements using these events:
- onFocus: Fires when an element receives focus
- onBlur: Fires when an element loses focus
Anchor to event-handling-form-values-and-typesForm Values and Types
Important details about form values in Polaris web components:
- All form elements return string values in their events, even numeric inputs
- Multi-select components (like
s-choice-list) use avaluesprop (array of strings) - Access values in event handlers via
Anchor to event-handling-controlled-vs.-uncontrolled-componentsControlled vs. Uncontrolled Components
Polaris components can be used in two ways:
Uncontrolled (simpler): Component manages its own internal state - use prop
Controlled (more powerful): Your code manages the component’s state - use value prop
Use controlled components when you need to:
- Validate input as the user types
- Format or transform input values
- Synchronize multiple inputs
Anchor to event-handling-technical-detailsTechnical Details
Under the hood, Polaris web components handle event registration consistently across frameworks:
- In Preact, Polaris components properly register events via
instead of setting attributes - Event names are automatically converted to lowercase (
becomesclick) - All event handlers receive standard DOM events as their first argument
For example, when you write , the component:
- Sees that
"onclick" in elementistrue - Registers your handler via
- Passes the event object to your handler when clicked
Basic Event Handling Examples
Examples
Basic Event Handling Examples
JSX
<s-button onClick={() => console.log('Clicked!')}> Inline Handler </s-button> <s-button onClick={(event) => { console.log('Event details:', event.type); console.log('Target:', event.currentTarget); }}> With Event Object </s-button>HTML
<s-button onclick="console.log('Button clicked!')"> Click me </s-button> <s-button id="eventButton"> Click me (addEventListener) </s-button> <script> const eventButton = document.getElementById('eventButton'); eventButton.addEventListener('click', () => { console.log('Button clicked via addEventListener!'); }); </script>
Anchor to slotsSlots
Slots allow you to insert custom content into specific areas of Polaris web components. Use the slot attribute to specify where your content should appear within a component.
Key points:
- Named slots (e.g.,
slot="title") place content in designated areas - Multiple elements can share the same slot name
- Elements without a slot attribute go into the default (unnamed) slot
Examples
Banner
Examples
Examples
Banner
<s-banner heading="Order created" status="success"> The order has been created successfully. <s-button slot="secondary-actions">View order</s-button> <s-button slot="secondary-actions">Download invoice</s-button> </s-banner>
Anchor to methodsMethods
Methods are functions available on components for programmatic control. Components like Modal, Sheet, and Announcement provide methods such as or dismiss() to control their behavior imperatively when needed.
Use methods when you need to trigger actions that can’t be achieved through property changes alone, such as closing an overlay after an async operation or resetting component state.
Example
Examples
Example
JSX
function Methods() { const modalRef = useRef(null); return ( <> <s-button command="--show" commandFor="modal-1" > Open modal </s-button> <s-modal id="modal-1" ref={modalRef} heading="Test Modal" > <s-text>Modal content</s-text> <s-button onClick={() => { modalRef.current.hideOverlay(); }} > Close modal </s-button> </s-modal> </> ); }JS
function Methods() { const button = document.createElement('s-button'); const modal = document.createElement('s-modal'); button.textContent = 'Open Modal'; button.commandFor = 'modal-1'; modal.id = 'modal-1'; modal.heading = 'Test Modal'; const closeButton = document.createElement('s-button'); closeButton.textContent = 'Close modal'; closeButton.onclick = () => modal.hideOverlay(); modal.appendChild(closeButton); document.body.appendChild(button); document.body.appendChild(modal); }
Anchor to using-formsUsing Forms
The Form component provides a way to manage form state and submit data to your app’s backend or directly to Shopify using Direct API access. When the form is submitted or reset the relevant callback in the form component will get triggered.
Using this, you can control what defines a component to be dirty by utilizing the input’s defaultValue property.
Rules:
When the
is set, the component will be considered dirty if the value of the input is different from the. You may update thewhen the form is submitted to reset the dirty state of the form.When the
is not set, the component will be considered dirty if the value of the input is different from the initial value or from the last dynamic update to the input’s value that wasn’t triggered by user input.
Note: In order to trigger the dirty state, each input must have a name attribute.
Trigger the Form’s dirty state
Examples
Trigger the Form’s dirty state
Using `defaultValue`
import { render } from 'preact'; import { useState } from 'preact/hooks'; export default function extension() { render(<Extension />, document.body); } const defaultValues = { text: 'default value', number: 50, }; function Extension() { const [textValue, setTextValue] = useState(''); const [numberValue, setNumberValue] = useState(''); return ( <s-form onSubmit={() => console.log('submit', {textValue, numberValue})}> <s-stack gap="base"> <s-text-field label="Default Value" name="my-text" defaultValue={defaultValues.text} value={textValue} onChange={(e) => setTextValue(e.target.value)} /> <s-number-field label="Percentage field" name="my-number" defaultValue={defaultValues.number} value={numberValue} onChange={(e) => setNumberValue(e.target.value)} /> </s-stack> </s-form> ); }Using implicit default
import { render } from 'preact'; import { useState } from 'preact/hooks'; export default function extension() { render(<Extension />, document.body); } async function Extension() { const data = await fetch('/data.json'); const {text, number} = await data.json(); return <App text={text} number={number} />; } function App({text, number}) { // The initial values set in the form fields will be the default values const [textValue, setTextValue] = useState(text); const [numberValue, setNumberValue] = useState(number); return ( <s-form onSubmit={() => console.log('submit', {textValue, numberValue})}> <s-stack gap="base"> <s-text-field label="Default Value" name="my-text" value={textValue} onChange={(e) => setTextValue(e.target.value)} /> <s-number-field label="Percentage field" name="my-number" value={numberValue} onChange={(e) => setNumberValue(e.target.value)} /> </s-stack> </s-form> ); }
Anchor to accessibilityAccessibility
Polaris web components are built with accessibility in mind. They:
- Use semantic HTML under the hood
- Support keyboard navigation
- Include proper ARIA attributes
- Manage focus appropriately
- Provide appropriate color contrast
- Log warnings when component properties are missing and required for accessibility
To ensure your application remains accessible, follow these best practices:
- Always use the
labelanderrorproperties for form elements - Use appropriate heading levels with
s-headingor theheadingproperty - Ensure sufficient color contrast
- Test keyboard navigation
- Use
to hide labels and keep them visible to assistive technologies - Use
to specify thearia-roleof the component
Example
JSX
Examples
Example
JSX
{/* Good - provides a label */} <s-text-field label="Email address"></s-text-field> {/* Bad - missing a label */} <s-text-field></s-text-field>
Anchor to troubleshootingTroubleshooting
Common issues and debugging tips for using Polaris web components.
Anchor to troubleshooting-common-issuesCommon issues
- Properties not updating: Ensure you’re using the property name as documented, not a different casing or naming convention.
- Event handlers not firing: Check that you’re using the correct event name (e.g.,
for click events). - Form values not being submitted: Make sure your form elements have
nameattributes.
Anchor to troubleshooting-debugging-tipsDebugging tips
- Inspect the element in your browser’s developer tools to see the current property and attribute values.
- Use
console.logto verify that event handlers are being called and receiving the expected event objects. - Check for any errors in the browser console that might indicate issues with your component usage.