Store Availability
Search and display store locations with real-time stock availability and opening hours.
For installation instructions, see Getting Started with Unify Widgets.
Basic Implementation
As a dialog (default)
<unify-store-availability-widget
api-key="your-api-key"
item-id="your-item-id"
></unify-store-availability-widget>
<button onclick="document.querySelector('unify-store-availability-widget').open = true">
Where to buy
</button>Inline (no dialog)
<unify-store-availability-widget
api-key="your-api-key"
item-id="your-item-id"
no-dialog
></unify-store-availability-widget>Component Attributes
| Attribute | Type | Required | Description |
|---|---|---|---|
api-key | String | Yes | API key for Unify requests |
item-id | String | Yes | Item identifier for stock-store API calls |
open | Boolean | No | Controls dialog visibility when dialog mode is enabled |
no-dialog | Boolean | No | Renders content inline instead of inside a dialog |
demo | Boolean | No | Enables demo mode (X-Demo-Mode: true header) |
Component Properties
| Property | Type | Required | Description |
|---|---|---|---|
config | Object | No | Configuration object (see Config Options) |
Config Options
Pass configuration via the config property to customize text labels, behavior, and thresholds.
document.querySelector('unify-store-availability-widget').config = {
dialogTitle: 'Find in store',
locale: 'en-US',
maxRange: 20,
// ... more options
};Dialog & Layout
| Option | Default | Description |
|---|---|---|
dialogTitle | "Store availability" | Title of the search dialog |
dialogDescription | "Check the availability of your item in the store of your choice - if needed, contact the store." | Description text below title |
Search & Loading
| Option | Default | Description |
|---|---|---|
addressLabel | "Address" | Search input label |
addressPlaceholder | "Your address" | Search input placeholder |
submitButtonLabel | "Submit" | Search submit button text |
searchingLabel | "Searching..." | Search loading state text |
geolocateButtonLabel | "Use my location" | Geolocation button label |
geolocatingLabel | "Locating..." | Geolocation loading state |
loadingMessage | "Searching for stores..." | Text shown while stores are loading |
validationErrorMessage | "Please enter a full address including your city name" | Address validation error message |
searchErrorMessage | "An error occurred while searching. Please try again." | API error fallback |
noStoresMessage | "No stores found for the provided information." | Empty result message |
Search Options
| Option | Default | Description |
|---|---|---|
requireFullAddress | false | Require at least one letter in address before submit (prevents postal code-only searches) |
availableStock | false | Initial state: only display available stock |
availableStockLabel | "Only display available stock" | Toggle label |
hideAvailableStockToggler | false | Hide the stock filter checkbox |
maxRange | 10 | Maximum search distance in km |
Caching
The widget keeps an in-memory cache of stock responses, keyed by API key, item ID, demo mode, maxRange, and the searched address. A reopened dialog reuses fresh data instead of re-geolocating and re-fetching, avoiding flicker and redundant network calls. The cache lives for the lifetime of the element and is never persisted.
| Option | Default | Description |
|---|---|---|
stockCacheTtl | 300000 | Time (ms) a cached stock response stays valid (default 5 minutes). Set to 0 to disable caching entirely. Only successful responses are cached. |
Store Display Options
| Option | Default | Description |
|---|---|---|
hideStoreAddress | false | Hide store address in results |
storeAddressLines | 1 | Number of address lines to display (when the address has multiple lines) |
hideStoreContact | false | Hide store phone number |
hideOpeningHours | false | Hide open/closed status and hours |
openingHoursDisplayMode | "full" | "full" shows a static Open/Closed label; "simple" shows a live status computed from today's hours (e.g. "Open until 19:00") |
storeOpenLabel | "Open" | Keyword shown for open stores, in both full and simple modes |
storeClosedLabel | "Closed" | Keyword shown for closed stores, in both full and simple modes |
storeOpenUntilLabel | "until" | Connector word inserted between the keyword and the time in simple mode while the store is currently open (e.g. "Open" + "until" + "19:00") |
storeOpensAtLabel | "- opens at" | Connector word inserted between the keyword and the time in simple mode before opening or during a midday break (e.g. "Closed" + "- opens at" + "09:00") |
storeOpeningHoursLabel | "Opening Hours" | Section heading for hours |
noOpeningHoursMessage | "No opening hours available" | Fallback when hours unavailable |
exceptionalDaysLabel | "Exceptional Days" | Header for holiday hours |
informationLabel | "Information" | Header for extra opening hours info |
Stock Display
| Option | Default | Description |
|---|---|---|
displayStockQuantity | true | Show quantity in stock labels |
stockLimitedThreshold | 10 | Quantity threshold between "limited" and "available" |
stockAvailableLabelWithQuantity | "Available (%qty% remaining)" | Available stock label with %qty% |
stockAvailableLabel | "Available" | Available stock label without quantity |
stockLimitedLabelWithQuantity | "Limited stock (%qty% remaining)" | Limited stock label with %qty% |
stockLimitedLabel | "Limited stock" | Limited stock label without quantity |
stockUnavailableLabel | "Out of stock" | Out-of-stock label |
Click & Go Options
| Option | Default | Description |
|---|---|---|
showClickAndGoInfo | false | Show click-and-go availability badge |
storeClickAndGoAvailableLabel | "⏱️ Click and Go Available" | Click-and-go available label |
storeClickAndGoNotAvailableLabel | "🚫 Click and Go Not Available" | Click-and-go unavailable label |
showStorePickupButton | false | Show "Store Pickup" button for each store |
storePickupButtonLabel | "Store Pickup" | Button text for store pickup action |
Custom Store CTAs
Beyond the built-in Store Pickup button, you can render any number of custom call-to-action buttons on each store card. Each click dispatches a store-custom-cta-clicked event (or a custom event name) carrying the store and CTA metadata, so you can wire the buttons to your own actions (favorite store, get directions, reserve, contact, etc.).
| Option | Default | Description |
|---|---|---|
customStoreCtas | [] | Array of custom CTA descriptors (see CTA object shape). Rendered in order after the Store Pickup button. |
customStoreCtaDefaultLabel | "Custom CTA" | Fallback label used when a CTA has no label |
customStoreCtaEventName | "store-custom-cta-clicked" | Event name dispatched when any custom CTA is clicked. Set a custom string to route all CTA clicks to your own event. |
CTA object shape
Each entry in customStoreCtas is a plain object:
| Key | Type | Default | Description |
|---|---|---|---|
id | String | custom-cta-{n} | Identifier for the CTA. Normalized to lowercase kebab-case ([a-z0-9_-]) and reused as the per-CTA CSS part suffix (store-custom-cta-button--{id}) and in the event detail.cta.id. |
label | String | customStoreCtaDefaultLabel | Button text |
disabled | Boolean | false | Always render the button disabled |
disableIfOutOfStock | Boolean | false | Disable the button when the store has no stock (stock.quantity <= 0) |
disableIfNoClickAndGo | Boolean | false | Disable the button when the store does not support click & go |
hideWhenDisabled | Boolean | false | Hide the button entirely instead of rendering it disabled |
data | Object | null | Arbitrary payload echoed back in the event detail.cta.data |
document.querySelector('unify-store-availability-widget').config = {
customStoreCtas: [
{ id: 'favorite-store', label: 'Add to favorites' },
{
id: 'reserve',
label: 'Reserve in store',
disableIfOutOfStock: true,
hideWhenDisabled: true,
data: { flow: 'reservation' },
},
],
};Formatting
| Option | Default | Description |
|---|---|---|
locale | "en-US" | Locale for date and number formatting (e.g., "fr-FR", "de-DE") |
weekdayFormat | "long" | Weekday display format: "long" (Monday), "short" (Mon), "narrow" (M) |
Error Handling
| Option | Default | Description |
|---|---|---|
errorMessage | "An error occurred while fetching store data. Please try again later." | Generic error message |
Styling
The widget exposes CSS custom properties (prefixed --unify-) as the stable styling API, set on the widget element or any ancestor, and CSS ::part() selectors for structural elements (subject to change between versions — prefer custom properties where possible).
To discover what's available, inspect the <unify-store-availability-widget> element in your browser's DevTools:
- Check the Computed tab for all
--unify-*custom properties (editable live in the Styles panel) - Look inside the shadow DOM for elements carrying a
partattribute — each is targetable via::part()
Custom CTA button tokens (--unify-store-custom-cta-button-*) fall back to their Store Pickup equivalents (--unify-store-pickup-button-*) when unset. Custom CTA buttons also carry a per-id part (store-custom-cta-button--{id}) in addition to the shared store-custom-cta-button part, so a single CTA can be targeted individually.
Deprecated (inert):
--unify-dialog-zindexand--unify-backdrop-zindexno longer have any effect — the dialog renders in the browser top layer viashowModal(), which always paints above page content regardless ofz-index.
Events
dialog-opened
dialog-openedFired when the dialog open/close state changes.
document.querySelector('unify-store-availability-widget').addEventListener('dialog-opened', (event) => {
console.log('Dialog is', event.detail.open ? 'open' : 'closed');
});Detail shape:
{
open: boolean // true when opened, false when closed
}store-pickup
store-pickupFired when user clicks the "Store Pickup" button (only dispatched if showStorePickupButton config option is true).
document.querySelector('unify-store-availability-widget').addEventListener('store-pickup', (event) => {
console.log('Store selected:', event.detail.location.name);
console.log('Item:', event.detail.itemId);
});Detail shape:
{
itemId: string, // The item ID passed to the widget
source: 'location', // Always 'location'
location: { // Raw store object as returned by the API
id: string,
name: string,
address: string,
zip_code: string,
city: string,
distance: number,
phone_number: string,
stock: { quantity: number, status: string },
opening_hours: { /* ... */ },
click_and_go: boolean
},
store: { // Normalized summary (camelCase, safe defaults)
id: string | null,
name: string | null,
address: string | null,
zipCode: string | null,
city: string | null,
phoneNumber: string | null,
clickAndGo: boolean,
closed: boolean,
stockQuantity: number
}
}The raw API object is always available under
location. Thestorekey is a normalized, camelCase view with guaranteed keys and safe defaults — prefer it when forwarding data to your own systems.
store-custom-cta-clicked
store-custom-cta-clickedFired when the user clicks a custom CTA button. The event name can be overridden per-integration via the customStoreCtaEventName config option — if you set it, listen for that name instead.
document.querySelector('unify-store-availability-widget').addEventListener('store-custom-cta-clicked', (event) => {
const { cta, store } = event.detail;
console.log('CTA clicked:', cta.id, 'for store', store.name);
});Detail shape:
Carries the same itemId, source, location, and store keys as store-pickup, plus a cta object describing the clicked button:
{
itemId: string,
source: 'location',
location: { /* raw store object */ },
store: { /* normalized summary — see store-pickup */ },
cta: {
id: string, // Normalized CTA id
label: string, // Resolved label (falls back to customStoreCtaDefaultLabel)
index: number, // Position in the customStoreCtas array
data: object | null // The `data` payload from the CTA descriptor
}
}Examples
Basic setup
const widget = document.querySelector('unify-store-availability-widget');
widget.config = {
dialogTitle: 'Find in store',
storeOpenLabel: 'Open now',
storeClosedLabel: 'Closed',
maxRange: 25,
locale: 'en-US',
};
// Open the search dialog
widget.open = true;With Click & Go and Store Pickup
const widget = document.querySelector('unify-store-availability-widget');
widget.config = {
dialogTitle: 'Availability near you',
showClickAndGoInfo: true,
showStorePickupButton: true,
storePickupButtonLabel: 'Order for pickup',
maxRange: 30,
};
// Handle store pickup action
widget.addEventListener('store-pickup', (event) => {
const { location, itemId } = event.detail;
console.log(`User selected ${location.name} for item ${itemId}`);
// Send to your backend or order system
});
widget.open = true;With custom CTAs
const widget = document.querySelector('unify-store-availability-widget');
widget.config = {
customStoreCtas: [
{ id: 'directions', label: 'Get directions' },
{
id: 'reserve',
label: 'Reserve in store',
disableIfOutOfStock: true,
data: { flow: 'reservation' },
},
],
};
widget.addEventListener('store-custom-cta-clicked', (event) => {
const { cta, store } = event.detail;
if (cta.id === 'directions') {
window.open(`https://maps.google.com/?q=${encodeURIComponent(store.address)}`);
} else if (cta.id === 'reserve') {
startReservation(store, cta.data);
}
});
widget.open = true;Styled with custom theme
<style>
unify-store-availability-widget {
/* Colors */
--unify-dialog-bg: #f8f9fa;
--unify-dialog-color: #212529;
--unify-backdrop-bg: rgba(0, 0, 0, 0.6);
/* Store status */
--unify-store-status-open-color: #28a745;
--unify-store-status-closed-color: #dc3545;
/* Buttons */
--unify-geolocate-button-bg: #007bff;
--unify-geolocate-button-color: white;
/* Spacing & sizing */
--unify-input-border-radius: 8px;
--unify-dialog-width: 700px;
}
</style>
<unify-store-availability-widget
api-key="your-api-key"
item-id="your-item-id"
></unify-store-availability-widget>Updated 25 days ago