--- title: Using Polaris web components description: >- Learn essential concepts for working with Polaris web components, including styling, layouts, scale, Preact integration, and interactive features across UI extension surfaces. api_name: app-surfaces source_url: html: 'https://shopify.dev/docs/api/app-surfaces/using-polaris-web-components' md: 'https://shopify.dev/docs/api/app-surfaces/using-polaris-web-components.md' --- # Using Polaris web components Polaris web components are the UI building blocks that you use to display data and trigger functionality in UI extensions. These components are native UI elements that follow [Shopify's design system](https://shopify.dev/docs/apps/design) and are built with [remote-dom](https://github.com/Shopify/remote-dom), Shopify's library for building cross-platform user interfaces. This guide covers essential concepts for working with Polaris web components across all UI extension surfaces. *** ## Preact UI extensions are scaffolded with [Preact](https://preactjs.com/) by default. This means you can use Preact patterns and principles within your extension. Since Preact is included as a standard dependency, you have access to all of its features including [hooks](https://preactjs.com/guide/v10/hooks/) like `useState` and `useEffect` for managing component state and side effects. You can also use [Preact Signals](https://preactjs.com/guide/v10/signals/) for reactive state management, and take advantage of standard web APIs just like you would in a regular Preact application. ## Example: Using Preact hooks to manage state ```jsx import '@shopify/ui-extensions/preact'; import {render} from 'preact'; import {useState} from 'preact/hooks'; export default async () => { render(, document.body); }; function Extension() { const [count, setCount] = useState(0); return ( <> Count: {count} setCount(count + 1)} > Increment ); } ``` *** ## Accessibility Polaris web components have accessibility built in. They use semantic HTML, support keyboard navigation, include proper ARIA attributes, manage focus, and provide appropriate color contrast. Components also log warnings when required accessibility properties are missing. To keep your app accessible: * Always set `label` and `error` properties on form elements. * Use appropriate heading levels with `s-heading` or the `heading` property. * Test keyboard navigation throughout your extension. * Use `labelAccessibilityVisibility` to visually hide labels while keeping them available to assistive technologies. * Use `accessibilityRole` to specify the ARIA role of a component. ## Example: Ensuring accessibility with labels ```jsx {/* Good - provides a label */} {/* Bad - missing a label */} ``` *** ## Commands Commands let you control components declaratively without writing JavaScript. Set `commandFor` to the ID of the target component, and `command` to the action you want to perform. The browser handles the interaction automatically. Available commands: * `--toggle`: Toggle the target component's visibility. * `--show`: Show the target component. * `--hide`: Hide the target component. * `--auto`: Perform the most appropriate action for the target component (default). * `--copy`: Copy the target `ClipboardItem`. ## Example: Controlling components with commands ```jsx This modal is controlled using the commands API. No JavaScript event handlers are needed—the browser handles all interactions automatically. Close Modal Controls Toggle Modal Show Modal Hide Modal Auto Command (Toggle) ``` *** ## Execution model UI extensions execute the module's default export so it can render a user interface. UI extensions are powered by [remote-dom](https://github.com/Shopify/remote-dom/), a fast and secure environment for custom ([non-DOM](#styling)) UIs. Remote-dom enables UI extensions to run in an isolated sandbox while maintaining high performance and security. This architecture ensures that extensions can't access sensitive data or interfere with the host application, while still providing a rich, interactive user experience. ## Example: Basic extension structure ```jsx import '@shopify/ui-extensions/preact'; import {render} from 'preact'; export default async () => { render(, document.body); }; function Extension() { return Your extension; } ``` *** ## Forms The `s-form` component manages form state and handles submissions to your app's backend or directly to Shopify using API access. It triggers callbacks when the form is submitted or reset. Forms track whether inputs are "dirty" (changed from their original value) using the `defaultValue` property: * **With defaultValue set**: An input is dirty when its current value differs from `defaultValue`. Update `defaultValue` after submission to reset the dirty state. * **Without defaultValue set**: An input is dirty when its current value differs from the initial value or the last programmatic update. **Note:** Each input must have a `name` attribute for dirty state tracking to work. ## Example: Managing form dirty state ##### Using defaultValue ```jsx import { render } from 'preact'; import { useState } from 'preact/hooks'; export default function extension() { render(, document.body); } const defaultValues = { text: 'default value', number: 50, }; function Extension() { const [textValue, setTextValue] = useState(''); const [numberValue, setNumberValue] = useState(''); return ( console.log('submit', {textValue, numberValue})}> setTextValue(e.target.value)} /> setNumberValue(e.target.value)} /> ); } ``` ##### Using implicit default ```jsx import { render } from 'preact'; import { useState } from 'preact/hooks'; export default function extension() { render(, document.body); } async function Extension() { const data = await fetch('/data.json'); const {text, number} = await data.json(); return ; } 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 ( console.log('submit', {textValue, numberValue})}> setTextValue(e.target.value)} /> setNumberValue(e.target.value)} /> ); } ``` *** ## Handling events Polaris web components use standard DOM events, making them work with your preferred framework. You can attach event handlers using the same patterns as with native HTML elements. ### Basic event handling Event handlers in Polaris components work just like standard HTML elements. In frameworks, use the familiar camelCase syntax (like `onClick` in Preact). In plain HTML, use lowercase attributes or `addEventListener`. ## Example: Attaching event handlers ##### JSX ```jsx console.log('Clicked!')}> Inline Handler { console.log('Event details:', event.type); console.log('Target:', event.currentTarget); }}> With Event Object ``` ##### HTML ```html Click me Click me (addEventListener) ``` ### Form input events Form components support two event types for tracking changes: * `onInput`: Fires on every keystroke or value change. Use this for real-time validation or character counting. * `onChange`: Fires when the value is committed—on blur for text fields, or immediately after input for checkboxes and radio buttons. Use this to validate after the user finishes their input. ## Example: Handling input and change events ##### JSX ```jsx // OnInput fires on every keystroke console.log('Typing:', e.currentValue.value)} > // OnChange fires on blur or Enter press console.log('Value committed:', e.currentValue.value)} > // Using both together console.log('Real-time:', e.currentTarget.value)} onChange={(e) => console.log('Final value:', e.currentTarget.value)} > ``` ##### HTML ```html ``` ### Focus management Use `onFocus` and `onBlur` to track when users enter and leave form fields. ## Example: Managing focus events ##### JSX ```jsx console.log('Field focused')} onBlur={() => console.log('Field blurred')} > { e.currentTarget.setAttribute('label', 'Field is active!') }} onBlur={(e) => { e.currentTarget.setAttribute('label', 'Tab to next field to trigger blur') }} > ``` ##### HTML ```html ``` ### Form values and types All form elements return string values in events, even numeric inputs—convert them if needed. Access single values using `event.currentTarget.value`, or use `event.currentTarget.values` (an array of strings) for multi-select components like `s-choice-list`. ## Example: Working with form values and types ##### JSX ```jsx // Number field example - values are strings { // e.currentTarget.value is a string, convert if needed const quantity = Number(e.currentTarget.value); console.log('Quantity as number:', quantity); }} /> // Multi-select example - values is an array of strings { // e.currentTarget.values is an array of strings console.log('Selected colors:', e.currentTarget.values); }} > ``` ##### HTML ```html Red Blue Green ``` ### Controlled and uncontrolled components Form components can be **uncontrolled** (simpler) or **controlled** (more advanced): * **Uncontrolled**: The component manages its own state. Set `defaultValue` for the initial value. * **Controlled**: Your code manages the state. Set `value` and update it in your event handler. Use this when you need to validate as the user types, format values, or synchronize multiple inputs. ## Example: Controlled and uncontrolled components ##### JSX ```jsx // Uncontrolled component - internal state console.log('New value:', e.currentTarget.value)} /> // Controlled component - external state // In a real component, 'name' would be from framework state const name = "John Doe"; { console.log('Would update state:', e.currentTarget.value) }} /> ``` ##### HTML ```html ``` ### Technical implementation Polaris web components register events using `addEventListener` rather than setting attributes. Event names are automatically converted to lowercase. When you write ``, the component checks that `"onClick" in element` is `true`, then registers your handler with `addEventListener('click', handler)`. All event handlers receive standard DOM events as their first argument. ## Example: Event handling implementation ##### JSX ```jsx console.log('Clicked!')}> Click me console.log('Value changed:', e.currentTarget.value)} onFocus={() => console.log('Field focused')} onBlur={() => console.log('Field blurred')} > ``` ##### HTML ```html Click me ``` *** ## Interactive elements `s-button`, `s-link`, and `s-clickable` render as anchor elements when they have an `href`, or as button elements when they have an `onClick` without an `href`. Per the HTML spec, interactive elements can't contain other interactive elements. Set `target="auto"` to automatically open internal links in the same tab (`_self`) and external URLs in a new tab (`_blank`). Use `s-clickable` as an escape hatch when `s-link` or `s-button` can't achieve a specific design. Consider using `s-link` and `s-button` when possible. ## Example: Interactive elements as buttons and links ```jsx {/* s-button with onClick renders as a button element */} console.log('Action triggered')}> Trigger Action {/* s-button with href renders as an anchor element */} View Products {/* s-link with href renders as an anchor element */} Go to Settings {/* s-link with onClick renders as a button element */} console.log('Link action')}> Trigger Link Action {/* target="auto" uses _self for internal, _blank for external URLs */} Internal Link External Link {/* s-clickable is an escape hatch for custom interactive designs */} console.log('Custom click')}> Custom Clickable Area ``` *** ## Layouts Use `s-stack`, `s-grid`, and `s-box` to build custom layouts. `s-stack` and `s-grid` have no spacing between children by default. Use the `gap` property to add space. Setting `direction="inline"` on `s-stack` makes children wrap to a new line when space is limited. `s-grid` allows children to overflow unless you configure template rows or columns. For shorthand properties like `border`, order matters: use `size-keyword`, `color-keyword`, `style-keyword`. ## Example: Building a custom layout with stack and gap ```jsx import '@shopify/ui-extensions/preact'; import {render} from 'preact'; export default function extension() { render(, document.body); } function Extension() { return ( Heading Description { console.log('button was pressed'); }} > Button ); } ``` *** ## Methods Methods are functions available on components for programmatic control. Components like `Modal`, `Sheet`, and `Announcement` provide `hideOverlay()` 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: Calling component methods ##### JSX ```jsx function Methods() { const modalRef = useRef(null); return ( <> Open modal Modal content { modalRef.current.hideOverlay(); }} > Close modal ); } ``` ##### JS ```javascript 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); } ``` *** ## Properties and attributes Polaris web components follow standard HTML patterns. Attributes appear in markup, while properties are accessed directly on the DOM element. Most attributes are reflected as properties, except `value` and `checked` which follow HTML's standard behavior. In JSX, the framework checks if the element has a matching property name. If it does, the value is set as a property; otherwise, it's applied as an attribute: ## Example: How JSX applies properties vs attributes ```jsx if (propName in element) { // Set as a property element[propName] = propValue; } else { // Set as an attribute element.setAttribute(propName, propValue); } ``` In practice, just use the property names as documented and everything will work as expected. ## Example: Using documented property names ```jsx {/* This works as expected - the "gap" property accepts string values */} ...; {/* This also works - the "checked" property accepts boolean values */} ...; ``` *** ## Responsive values Some properties accept responsive values that change based on the parent container's inline size. ### Syntax Responsive values follow a ternary-like syntax: `@container (inline-size > 500px) large, small` means use `large` when the container exceeds 500px, otherwise use `small`. The pattern is: `@container [name] (condition) valueIfTrue, valueIfFalse` Use a mobile-first approach for browser compatibility. The fallback value (when the condition is `false`) should work at the smallest size. For example, `` ensures browsers without container query support get a layout that works everywhere. ## Example: Using responsive values with container queries ##### HTML ```html This padding will be "large-400" when the container is more than 500px. Otherwise it will be "small". ``` ##### Pseudocode ``` This padding will be "large-400" when the container is more than 500px. Otherwise it will be "small". ``` ### Using s-query-container Wrap your content in `` to enable responsive value queries. By default, queries target the closest container. To target a specific ancestor, add `containername` to that container and reference it in your query: `@container outer (inline-size > 500px)`. ## Example: Targeting named containers ##### HTML ```html This padding will be "large-400" when the "outer" container is more than 500px. Otherwise it will be "small". ``` ##### Pseudocode ``` This padding will be "large-400" when the "outer" container is more than 500px. Otherwise it will be "small". ``` ### Values with reserved characters If your values contain brackets `()` or commas `,`, wrap them in quotes to escape them. ## Example: Escaping reserved characters ```html ... ``` ### Advanced patterns The syntax supports compound conditions, `and`/`or` logic, and nested conditions. ## Example: Advanced responsive patterns ##### Compound ```html The padding will be "small" when the container is between 300px and 500px wide. Otherwise it will be "large-500". ``` ##### And | or ```html This padding will be "large-400" when the container is more than 500px wide and when the container is smaller than 1000px wide. Otherwise it will be "small". ``` ##### Nested ```html This padding will be "base" when the container is greater than 500px wide, "large-400" when the container is larger than 1000px wide, and "small" otherwise. ``` *** ## Scale Properties like `padding`, `size`, and `gap` use a middle-out scale. Values radiate from `base` (the default): `small-100` through `small-500` get progressively smaller, while `large-100` through `large-500` get progressively larger. Use `small` and `large` as shorthand for `small-100` and `large-100`. ## Example: Scale values from small to large ```ts export type Scale = | 'small-500' | 'small-400' | 'small-300' | 'small-200' | 'small-100' | 'small' // alias of small-100 | 'base' | 'large' // alias of large-100 | 'large-100' | 'large-200' | 'large-300' | 'large-400' | 'large-500'; ``` *** ## Slots 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. * Named slots (for example, `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. ## Example: Using slots for custom content ```jsx The order has been created successfully. View order ``` *** ## Styling Components automatically apply styling based on the properties you set and the context in which they're used. For example, headings display at progressively less prominent sizes based on nesting depth within sections. All components inherit merchant brand settings, and the CSS can't be altered or overridden. Component styling is controlled by the merchant's branding settings and can't be overridden with custom CSS. Extensions render using Shopify's custom HTML elements (like `` or ``) rather than standard DOM elements like `
` or `