Source Code

<div class="slm-page">
  <main class="slm-content"><p>&#8593; Page content above the footer</p></main>
  <footer class="slm">
    <div class="slm-inner">
      <div class="slm-panel">
        <h3>Find a store near you</h3>
        <div class="slm-search-wrap">
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
          <input class="slm-search" id="slmSearch" type="text" placeholder="Search by city or zip code...">
        </div>
        <ul class="slm-list" id="slmList"></ul>
        <p class="slm-empty" id="slmEmpty" hidden>No stores match your search.</p>
      </div>
      <div class="slm-map" id="slmMap">
        <div class="slm-map-grid"></div>
        <div class="slm-pins" id="slmPins"></div>
        <div class="slm-tooltip" id="slmTooltip" hidden></div>
      </div>
    </div>
    <div class="slm-bottom">
      <span>&copy; 2026 Northline Outfitters</span>
      <a href="#">View all 42 locations &rarr;</a>
    </div>
  </footer>
</div>

Store Locator Footer with Map — Free Snippet

Store Locator Footer with Map · Footers · Plain HTML, CSS & JS · Live preview

What's included

Features

Single stores array drives both the list and the map pins — always in sync
Live case-insensitive search filters by city or store name as you type
Two-way active state: clicking a list item or a pin highlights the other
Auto-hiding tooltip shows store name and city above the clicked pin
Pure CSS grid-pattern map background — no API key, no external tiles
Percentage-based x/y pin coordinates, easy to swap for a real map projection
Empty-state message when a search matches no stores
Responsive: map and list stack vertically on narrow screens
Zero dependencies, vanilla JavaScript only

About this UI Snippet

Store Locator Footer — Searchable Branch List with Interactive Map Pins

Screenshot of the Store Locator Footer with Map snippet rendered live

Retail and hospitality sites often need a footer that answers one specific question fast: where is the nearest location? This snippet builds a two-panel store locator footer — a searchable, scrollable list of branches on the left, and a lightweight stylized map on the right where every store renders as a clickable pin, all driven from a single stores array so the list and the pins can never drift out of sync.

One data array, two synchronized views

Rather than hand-writing the list items and the map pins as separate static HTML blocks, render() loops over the stores array once and builds both the <li> list entry and the absolutely-positioned .slm-pin button for every store in the same pass. Each store object carries an x/y percentage pair used directly as the pin's left/top CSS position, so adding a new location only means pushing one object into the array — the list, the pin, and the search index all update automatically.

A live text search that filters both panels at once

Typing into #slmSearch calls render(searchEl.value) on every input event. The filter checks whether the typed query is a substring of either the store's city or its name, case-insensitively, and skips any store that doesn't match — from both the list and the map in the same pass, so a search for "seattle" instantly narrows the pin field down to a single marker rather than leaving stale pins from a previous view.

No map API key or external tiles

The map surface is pure CSS: a repeating linear-gradient grid pattern stands in for map tiles, so this snippet works fully offline and needs zero external requests, API keys, or billing setup. Swap .slm-map-grid's background for an actual tile layer (Mapbox GL, Leaflet, Google Maps) in production and keep the same x/y-driven pin logic — only the coordinate system changes from arbitrary percentages to real latitude/longitude projected into pixel space.

Clicking either side highlights the other

Clicking a list item or a map pin calls setActive(i), which re-renders both panels with the .active class applied to the matching entry, and briefly shows a tooltip above the corresponding pin with the store name and city before auto-hiding after under two seconds. This two-way binding — click the list, see the pin light up; click the pin, see the list scroll into focus — is the core interaction that makes a store locator feel like a real map rather than two disconnected widgets glued together.

Hover tooltips without a library

showTooltip()/hideTooltip() toggle a single shared #slmTooltip element's position and content rather than creating one tooltip per pin, which keeps the DOM small even with dozens of locations. The tooltip is positioned with the same x/y percentages as the pin itself, offset upward with a CSS transform, so it always tracks its target correctly regardless of how many stores are currently rendered.

Extending it for production

Swap the six hardcoded stores for an API response, add a "use my location" button that computes real distances with the Geolocation API and re-sorts the list, and replace the CSS grid map with a real tile provider once you have API credentials — the render/search/active-state logic in this snippet needs no changes to support any of that.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Instead of tracing the pin-and-list sync logic by hand, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how a single stores array and one render() call keep the list and the map pins from ever drifting apart, even as the search filter changes what is visible. The same assistant can help you optimize it, for instance asking whether the linear x/y percentage coordinates should instead be a proper geographic projection once you plug in a real map provider. It is also useful for extending the footer: ask it to add a real Geolocation-API-based "use my location" button that sorts stores by actual distance, wire the map background to Leaflet or Mapbox GL, or add store hours with a live open/closed indicator per pin. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a footer "store locator" section in plain HTML, CSS, and JavaScript, with a searchable list on one side and a lightweight map on the other, no map library or API key.

Requirements:
- A single JavaScript array of store objects, each with a name, city, distance label, and x/y percentage coordinates, used as the one source of truth to render both a scrollable list of stores and a set of absolutely-positioned pin buttons on a map panel — never hand-write the list and the pins as separate hardcoded HTML blocks.
- A text search input that filters both the list and the map pins together on every keystroke, matching case-insensitively against either the store name or city, and shows an empty-state message when nothing matches.
- Clicking a store in the list must highlight the corresponding pin on the map (a distinct active color) and briefly show a tooltip above it with the store name and city that auto-hides after a couple of seconds; clicking a pin directly must produce the same highlighted state from the other side.
- The map surface itself must be pure CSS (for example a repeating linear-gradient grid pattern) rather than any external map tiles or image, so the whole component works with zero network requests.
- A single shared tooltip element that repositions itself to whichever pin is active, rather than creating a separate tooltip node for every store.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA two-panel store locator footer renders with six sample stores in the list and matching pins on the map.
  2. 2
    Type in the search boxThe list and the map pins filter together to only the stores matching the city or name you typed.
  3. 3
    Click a store in the listThe matching pin turns red, a tooltip appears above it, and it auto-hides after under two seconds.
  4. 4
    Click a pin directlyThe same active state applies from the map side — list and map are two views of one data array.
  5. 5
    Edit the stores arrayIn the JS panel, add, remove, or reposition store objects — x and y are percentages of the map panel.
  6. 6
    Export in your formatClick HTML, JSX, or Tailwind to download the version you need.

Real-world uses

Common Use Cases

Retail chains and multi-location brands
Give shoppers a fast, footer-level way to find the nearest branch without navigating to a dedicated locator page.
Restaurants and franchise sites
Pair store hours and distance with a visual pin so diners can pick the closest location at a glance.
Real estate and multi-branch service businesses
List office locations with a searchable directory that scales to dozens of branches without cluttering the page.
Teaching data-driven dual-view rendering
A clean example of rendering two different DOM structures from one source array and keeping them in sync via a shared active-index and a single render() call.
Related: Sitemap Directory Footer
See the Sitemap Directory Footer for a related footers pattern worth pairing with this one.
Related: Footer Locale & Currency Switcher
See the Footer Locale & Currency Switcher for a related footers pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

No. The map surface is a pure CSS grid-pattern background so the snippet works offline with zero dependencies or API keys. Swap the .slm-map-grid background and the pin x/y coordinate system for a real tile provider such as Leaflet or Mapbox GL in production.

render(filter) is the single function that builds both panels. It loops the stores array once, checks each store city and name against the typed query, and only appends matching stores to both the list and the pins container — so both panels are always built from the same filtered set.

Push a new object into the stores array with name, city, dist, and x/y percentage coordinates for where its pin should sit on the map panel. No other code changes are needed — render() picks up any object in the array automatically.

They are plain percentages of the map panel width and height, used directly as the left and top CSS values on the absolutely positioned pin button. When swapping in a real map library, replace these with that library projecting real latitude/longitude into pixel coordinates.

showTooltip() and hideTooltip() reposition and rewrite a single shared #slmTooltip element rather than creating a tooltip node per store, which keeps the DOM lean even with a large number of locations.

Yes. Keep the stores array as component state or a prop, derive the filtered list with a computed/derived value from the search string, and render both the list and the pins from that same filtered array so they never fall out of sync.