Home
Wiki
Home
Wiki
  1. 2. Components
  • Back to home
  • 1. Themes
  • Vs Code
    • Getting Started
  • Kitchenware
    • Layout
      • New Layout
      • Legacy Layout
    • Components
      • Announcement
      • Banner Carousel
      • Banner With Products Carousel
      • Blog Category List
      • Blog List
      • Brand List
      • Brands Carousel
      • Breadcrumb
      • Call To Action
      • Cart
      • Categories List
      • Change Password
      • Checkout
      • Cookie Manager
      • Filter list
      • Footer
      • Forgot Password
      • Form
      • Hero Carousel
      • Icon Block
      • Invitation
      • Last Visited Products
      • Layout
      • Login
      • Map
      • Nav Bar
      • Offer
      • Product Attachments
      • Product Attributes
      • Product Documentation
      • Product Expected
      • Product Modal
      • Products Block
      • Products Carousel
      • Product Single
      • Profile
      • Quote
      • Register
      • Related Products
      • Search
      • Stores
      • Subscribe Newsletter
      • Text with Image
      • Top Bar
      • Video
    • Reusables
      • Getting Started
    • Assets
      • Getting Started
    • SDK
      • Products
        • _findProductsByCategory
        • _findProductsByIds
        • _findProductsByTitle
        • _findProductsByFilter
        • _findProductsByCriteria
        • _findProductsAndCalculate
        • _findProductsThenCalculate
        • _getProductAttributeSet
        • _setLastVisited
      • Categories
        • _findCategoryTreeById
        • _findCategoriesByIds
        • _findCategoryByAlias
        • _findCategoryTreeByAlias
        • _getCategoryContent
      • Collections
        • _getCollectionContent
        • _findCollectionsByIds
        • _findCollectionsByIdsThenCalculate
      • Brands
        • _getBrandContent
        • _findBrandsByIds
      • Cart
        • _addToCartMulti
        • _addToCart
        • _setCart
        • _clearCart
        • _setCartListener
        • _removeFromCart
        • _calculateCart
      • Checkout
        • _startCheckout
        • _updateCheckout
        • _completeCheckout
      • Shopping Lists
        • _getShoppingLists
        • _updateShoppingList
        • _createShoppingList
        • _deleteShoppingList
        • _getShoppingListByAlias
      • Navigation
        • _getFooterMenu
        • _getHeaderMenu
      • Users
        • _getUserById
      • Utils
        • _calculateCurrency
        • _getCurrencySymbol
        • _getCulture
        • _subscribeToNewsletter
        • _findUnitsByIds
  • Noir
    • 0. Introduction
    • 6. FAQ
    • 1. Structure
      • _Overview
      • LayoutA.liquid
      • ComponentsList.liquid
      • Metas.liquid
      • CssVariables.liquid
      • Json.liquid
      • GoogleTagManager.liquid
      • StagingButton.liquid
    • 2. Components
      • _Overview
      • Announcement
      • BannerCarousel
      • BlogCategoryList
      • BlogHighlights
      • BlogList
      • BrandList
      • Breadcrumb
      • Cart
      • CategoriesList
      • ChangePassword
      • Checkout
      • CookieManager
      • CustomTable
      • Faq
      • FilterList
      • Footer
      • ForgotPassword
      • Form
      • IconBlock
      • Invitation
      • LastVisitedProducts
      • Login
      • Map
      • NavBar
      • Offer
      • ProductAttachments
      • ProductAttributes
      • ProductComparison
      • ProductDocumentation
      • ProductMixList
      • ProductsBlock
      • ProductsCarousel
      • ProductSingle
      • Profile
      • Quote
      • Register
      • RelatedProducts
      • SingleBlog
      • Stores
      • TextWithImage
      • ThankYouPage
      • TopBar
      • Wishlist
      • OrderReviews
      • ProductReviews
    • 3. Reusables
      • _Overview
      • Addresses
      • AddressForm
      • AnnouncementModal
      • BackToTop
      • BillingRetail
      • BlogPostingStructuredData
      • BrandListStructuredData
      • CollectionStructuredData
      • Company
      • General
      • GroupChildrenTable
      • Login
      • LoginModal
      • MonthlyTransactions
      • Offers
      • Orders
      • Payment
      • ProductAttachments
      • ProductAttributes
      • ProductAvailabilityAction
      • ProductComparisonButton
      • ProductComparisonFloatingButton
      • ProductGridItem
      • ProductGridItemClassic
      • ProductListItem
      • ProductModal
      • ProductStructuredData
      • ProfileInfo
      • PromptModal
      • Quotes
      • Register
      • Services
      • Shipping
      • ShoppingLists
      • ShoppingListsButton
      • ShoppingListsNavbar
      • StoresStructuredData
      • Toast
      • Transactions
      • Users
      • VariantContent
      • VariantContentClassic
      • WishlistButton
      • ProductRating
    • 4. Assets
      • Fonts
      • Images
      • Templates
      • Javascript
        • _Overview
        • theme.js
      • Css - Scss
        • _Overview
        • ThemeClasses
    • 5. SDK
      • _Overview
      • LiquidGlobals
      • ServicesSDK
  1. 2. Components

Stores

Purpose#

The Stores component renders a store directory as cards, grouped by store group.
It:
shows an optional header and description, aligned by the component's Alignment setting
lets shoppers search by postal code, store name or group name
lets shoppers filter by group name with a dropdown
shows the number of matching stores
lists each group's title with its stores in a responsive grid (1 column on mobile, 2 from the sm breakpoint, 4 from md)
falls back to the company “main store” address, or a no-stores message, when no store groups are configured
The Default view has no embedded map; each card links to Google Maps instead. For an interactive map, use the WithMap view.

Inputs (model contract)#

Model shape (storefront example)#

{
  "name": "Stores",
  "view": "Default",
  "section": "SectionA",
  "settings": {
    "header": "Our stores",
    "alignment": "Left",
    "body": "<p>Sample text</p>",
    "groups": [
      {
        "title": "Sample title",
        "stores": [
          {
            "name": "Sample text",
            "address": {
              "address1": "Sample text",
              "address2": "",
              "city": "Sample text",
              "state": "Sample text",
              "country": "",
              "postalCode": "",
              "translation": {}
            },
            "phone": "Sample text",
            "email": "sample@example.com",
            "workingHours": "<p>Sample text</p>",
            "url": "https://example.com/store",
            "image": {
              "link": "https://example.com/media/store.jpg",
              "alt": "Sample text"
            },
            "imageTranslation": {}
          },
          "..."
        ]
      },
      "..."
    ],
    "id": "Component Id",
    "section": "SectionA",
    "type": "NoirStores",
    "name": "Stores",
    "configuredInContentApi": true,
    "view": "Default",
    "displayName": "",
    "cssClass": ""
  },
  "translations": {
    "searchPlaceholder": "Sample text",
    "allGroups": "Sample text",
    "noStoresFound": "Sample text",
    "...": "..."
  }
}

Required fields#

settings.id

Optional fields#

settings.header — rendered as the component heading.
settings.alignment — Left (default), Center or Right; applies to the header and description.
settings.body — description, rendered as HTML (Markdown is converted on the server).
settings.cssClass
settings.groups[]
When missing or empty, the template falls back to:
the company address from GlobalData.Company.Address (if present)
otherwise translations.NoStoresAvailable
Per store: image, address, phone, workingHours (HTML), url. Empty values are hidden.
translations.*

Translations#

KeyUsed for
SearchPlaceholderSearch input placeholder and label
FilterByGroupAccessible label of the group dropdown
AllGroupsThe “all groups” option of the dropdown
Result / ResultsResults count label (singular / plural)
WebsiteLink to the store's url
ViewOnMapGoogle Maps link
NoStoresFoundNo store matches the search or filter
MainStoreHeading of the fallback company address
NoStoresAvailableNo groups and no company address

Template behavior (Liquid + Alpine)#

Renders the header and description when either is set, aligned by settings.alignment.
Everything is server-rendered in Liquid, so the first paint is instant and crawlable without JavaScript. Alpine only shows and hides that markup; it never re-renders the cards.
When groups exist:
renders the search input, the group filter and the results count
shows the group dropdown (<select>) only when there is more than one distinct group title; its first option shows all groups
renders each non-empty group as a title followed by a card grid; on mobile only the first titled group starts expanded
each card shows, when present: image, store name, address, phone (tel: link), email (mailto: link), working hours, website link and a Google Maps search link built from the store name and address (address1, postalCode, city, state, country)
When there are no groups, renders the company “main store” address from GlobalData.Company.Address.*, or NoStoresAvailable.
Renders the StoresStructuredData reusable (Schema.org ItemList of LocalBusiness) for every configured store.

JavaScript#

Global object#

Components/Stores/Default.js exposes:
Liquid binds it via Alpine:
<section x-data='storesdefault.initComponent(groups)'>

initComponent#

Creates the Alpine state: groups (each with title and stores), searchQuery and selectedGroup.

init#

Tracks whether the viewport is below the Tailwind md breakpoint (64rem); below it each titled group becomes an accordion with one group open at a time. A new search or group filter opens the first matching group.

applyFilters / matches#

applyFilters() runs once on init and again whenever searchQuery or selectedGroup changes. It stores in matches, for each group (by position), a Set of the indexes of the stores that pass the group filter and the search, and updates resultsCount:
selectedGroup keeps only groups with that title (empty means all groups).
searchQuery keeps a whole group when its title matches, otherwise only the stores whose name, address, postal code, city or country matches.
Key behaviors / edge cases
Matching is case- and accent-insensitive (normalize); the searchable text is normalized once on init.
The search input is debounced by 200 ms.
Groups are keyed by position, so groups sharing a title filter and expand independently.

filteredGroups / resultsCount#

Groups with at least one match, and the total number of matching stores.

isGroupVisible(key) / isStoreVisible(key, storeIndex)#

Used by x-show on the server-rendered groups and cards.

isGroupOpen(key) / toggleGroup(key, header)#

Mobile accordion state. Only one group is open at a time; opening one scrolls its header into view.

normalize#

Lower-cases a value and strips diacritical marks via NFD normalization.

Dependencies#

Alpine.js
Reusables/StoresStructuredData

Loading state#

This component has no skeleton loading state: it renders its content directly, and no --loading modifier class is applied.

WithMap view#

Components/Stores/WithMap.liquid renders the same server-rendered directory as Default (search, group filter, accordion and cards behave identically) plus an interactive Google map of the stores.

Requirements#

A Google Maps API key in the store setting MapsApiKey (GlobalData.Settings.MapsApiKey), with the Maps JavaScript API enabled and your storefront domains allowed as HTTP referrers.
A Google Maps Map ID in the store setting MapsMapId (GlobalData.Settings.MapsMapId), created in the same Google Cloud project as the key. The map uses Advanced Markers, which Google only draws on a map with a Map ID.
Without the key or the Map ID the map is not loaded and MapKeyMissing is shown; the list still works.
latitude and longitude on each store. Stores without coordinates stay in the list but get no marker. When no store has coordinates, Google Maps is not loaded at all and the list takes the full width.

Creating the API key#

1.
In the Google Cloud Console, create or select a project and enable billing (Google Maps requires it, even within the free usage tier).
2.
Go to APIs & Services → Library, search for Maps JavaScript API and choose Enable.
3.
Go to APIs & Services → Credentials → Create credentials → API key.
4.
Restrict the key: under Application restrictions choose Websites and add every storefront domain (for example https://shop.example.com/*); under API restrictions allow only Maps JavaScript API.
5.
Copy the key into the store's MapsApiKey setting.
The key is always visible in the page source, so the website restriction is what protects it.

Creating a Map ID#

1.
In the Google Cloud Console, open the project that owns your Maps API key.
2.
Go to Google Maps Platform → Map Management and choose Create Map ID.
3.
Enter a name, set Map type to JavaScript and pick Vector (recommended) or Raster.
4.
Save, copy the generated ID and paste it into the store's MapsMapId setting.
The Map ID is not a secret: it is visible in the page source by design. A map style attached to the Map ID in the Cloud Console (colours, hidden points of interest) is applied to the storefront map automatically.

Extra settings#

settings.markerLink.link — optional image shown inside a theme-coloured badge instead of Google's default pin.
settings.zoom — zoom used when one store is shown or selected (default 15). Fitting several stores never zooms in further than this.
Example of the extra fields:
{
  "view": "WithMap",
  "settings": {
    "markerLink": { "link": "https://example.com/media/marker.png" },
    "zoom": 15,
    "groups": [
      {
        "title": "Sample title",
        "stores": [
          { "name": "Sample text", "latitude": "37.983810", "longitude": "23.727539", "...": "..." }
        ]
      }
    ]
  }
}

Extra translations#

KeyUsed for
NearestButton that shows the store nearest to the shopper
ViewDirectionsDirections link in the marker popup
LocationUnavailableThe shopper's location could not be determined
MapUnavailableThe map failed to load
MapKeyMissingMapsApiKey or MapsMapId is not configured

Behavior#

Loads the Maps JavaScript API (loading=async, in the page language) and @googlemaps/markerclusterer only when the key and Map ID are set and at least one store has coordinates. When the component is on a page more than once, both scripts are loaded only once and shared.
Shows a skeleton over the map until its first tiles are loaded.
Uses Advanced Markers on the configured Map ID, clustered with cluster icons in the theme's primary colours.
Markers follow the search and group filter; the map refits to the visible stores.
Each card shows the store image under the store name, when present.
Clicking a marker opens a popup with the store name, image, address, phone, email, working hours, website and a directions link. View on map on a card does the same. Clicking the map outside the popup closes it.
Popup content is built as plain text, so store data is never interpreted as HTML.
Nearest asks for the shopper's location, fits the map to the shopper and the nearest store, and opens that store's popup.
If the API fails to load, rejects the key (gm_authFailure) or times out after 10 seconds, MapUnavailable is shown in place of the map and the list remains usable.

Global object#

Components/Stores/WithMap.js exposes storeswithmap.initComponent(groups, mapRequested, websiteLabel, directionsLabel, locationUnavailableLabel, markerLink, zoom, mapId).

Dependencies#

Alpine.js
Google Maps JavaScript API
@googlemaps/markerclusterer 2.5.3 (unpkg, with SRI)
Reusables/StoresStructuredData
Modified at 2026-10-27 11:08:58
Previous
SingleBlog
Next
TextWithImage
Built with