2-8-4 Jimbocho
Chiyoda City, Tokyo 101-0051
A searchable network of locations for HubSpot, on a real map. Google Maps with your own key, or a street map with a full-colour basemap that draws without one. The locations, the search, the distance sorting, the marker grouping and the list-to-map sync are the same on both.
A bookbinder with counters on four continents, drawn on the map data from Google with a key in the field. Ask it to sort by distance from you and it answers from wherever you are reading this, and the numbers on the cards and on the pins renumber together to follow the order shown. Each location carries its own list of short lines — parking, step-free access, click and collect — and each line can take an icon the editor picks from the HubSpot icon library. Some rows have an icon, some do not, and the text still starts on the same line. One card has no rows at all and closes up around it. Nothing on the Style tab has been changed here apart from the swatches the Google basemap is built from.
A furniture and interiors company across Canada. The photograph is a field on each location, and where it sits on the card, its shape and its corners are fields too — here it runs the full width above the text, three cards across, with the map held over them while the list moves. The last card has no photograph and the layout closes up around it rather than leaving a hole. The indigo, the eight pixel corners and the soft card shadow are field values on this instance.
A bakery across Ireland. There is no Google key on this instance and none is needed: Leaflet ships inside the module and the basemap is a field with presets in it. Plain cards here — no icon rows, no photographs. The light grey ground, the large light heading, the borderless cards on a soft shadow and the blue are field values, which is the point: none of the three instances on this page has a line of CSS behind it.
A charging network across the Netherlands and Belgium on the map-only layout. There are no cards here: the search box, the category filter, the near-me button and the radius picker sit above a map that fills the section, and every answer is read off the pins and their bubbles. Search a town and the pins thin out. Ask for the nearest and the picker appears with the distances applied. Click a pin and the map travels to it over the time set on the field and opens the address. This is also the instance with the place-name lookup switched on, so pressing Enter in the search box turns a town into a starting point and the radius picker appears with it. This one draws on the bundled street map with the full-colour basemap, so the layout is the only thing that changes between it and the instances above.
Store Locator Pro puts a searchable network of locations on a HubSpot page. Locations are a repeater, so an editor adds, removes and reorders them without touching a template. Every one carries a name, an address, coordinates, a phone number, an email address, opening hours, a category, a photograph, a link and a short note.
Search, category filtering, distance sorting and the radius limit all run in the visitor's browser against data already on the page. None of it calls a service.
Driven by your own Google Cloud key. Create a project at console.cloud.google.com, switch billing on, enable Maps JavaScript API, create an API key, and restrict it to your own domains before you paste it in. The key is readable by anyone who views the page — its safety comes from that restriction. An unrestricted key can be lifted off your page and used on someone else's site, and the loads land on your bill.
Google's Dynamic Maps allowance covers 10,000 map loads a month at no cost, then $7.00 per 1,000 loads up to 100,000 a month. Billing has to be switched on for the project either way, allowance or not.
It lands in Google's own colours. A field greyscales it, another builds the map's palette out of the swatches on the Style tab, and a map ID hands the styling to your Google Cloud console and stops the colour fields applying. Whether Google draws the shops and landmarks it knows about is its own switch and it applies whichever way the colours are set, except under a map ID, where the cloud style decides. Markers, grouping, the info window and the list-to-map sync are the module's own, drawn on Google's panes, so a pin looks the same whichever map is underneath it.
Leave the key field empty with the mode still on Google Maps and the street map is drawn instead. A published page always shows a map, and demo 3 on this page is that street map with no key anywhere on the instance.
The script loads once per page however many instances are on it. Two instances with different keys on one page: the first to load wins.
Leaflet, bundled inside the module's own JavaScript file. Nothing is fetched from a CDN. It loads once per page however many instances are on it, and a CDN outage cannot take your map with it.
The tile field ships with CARTO's light basemap so a map draws straight away. CARTO's terms cover low volume and non-commercial use — read them, and for a commercial site paste in the address from whichever provider you hold an account with. MapTiler, CARTO, Stadia and Thunderforest all have a free tier and none asks for a card to start. It looks like this:
https://api.maptiler.com/maps/basic-v2/{z}/{x}/{y}.png?key=YOUR_KEY
The OpenStreetMap Foundation's own tile servers are deliberately not the default. Their usage policy states that access may be withdrawn from any commercial use at any time, and a default that can be withdrawn would blank the map on every site running it.
Every tile provider requires a credit line. The attribution field carries it, and it appears in the corner of the map.
The radius picker is built from the radius options, and the radius it starts on is a separate field. Set that field to a number the options do not hold and it is added to the picker where it belongs in the order, so the limit applies from the first render and the visitor can read what it is set to.
The near-me button asks the browser for the visitor's position through the standard Geolocation API. That is a browser permission prompt — nothing is sent anywhere. The distance from that point to each location is worked out with the haversine formula against the stored coordinates, the list is reordered nearest first, and a radius picker appears. Radius options and the unit are fields.
Sorting reorders the cards in the page, so the nearest one is genuinely first.
There is one optional lookup and it is off out of the box. Switched on, pressing Enter in the search box sends that single term to the OpenStreetMap Nominatim service to turn it into coordinates, which then acts as the starting point for distance sorting. It never fires while someone types, and it is held to one request a second, which is what the Nominatim usage policy asks for. Their policy also asks for a contact address, and there is a field for it.
With the switch off, the search box is plain text matching over the location name, town, region, postcode, country, address and category. Nothing leaves the page.
Hover a card and its pin lifts. Click a location name and the map flies to it, goes in to street level and opens its bubble, so the visitor sees the door rather than a dot in a county. The level is a field, and the map lands on it whichever side it was on before the click. The flight has a duration field, on the street map and on Google Maps alike, and anyone who has asked their device for less motion gets the same arrival without it. Click a pin and the matching card scrolls into view, becomes active and takes focus. The location name is a real button, so all of this works from the keyboard, and the links inside the card stay ordinary links.
When the map sits over the list, it holds its place at the top of the screen while the cards move under it. Without that, clicking a card near the bottom of a long list does something off screen. It is a field, with the gap above it, and it applies on a phone to every layout, where the height it takes comes from the small-screen map height so it never eats the whole display.
Each card can carry a directions link that opens in Google Maps, Apple Maps, Waze or OpenStreetMap. These are plain URLs with the coordinates in them and no key of any kind:
https://www.google.com/maps/dir/?api=1&destination=51.5261,-0.0800
https://maps.apple.com/?daddr=51.5261,-0.0800
https://www.waze.com/ul?ll=51.5261,-0.0800&navigate=yes
They are written into the page on the server, so they work with scripting switched off. The whole feature can be turned off.
On a street map, markers that land within a set number of pixels of each other collapse into one marker showing how many are inside it. Click it and the map frames that group. The grouping distance is a field, and grouping can be switched off. It is worked out inside the module, so nothing extra is loaded.
Each location carries its own repeater of short lines. A row is a piece of text and, if you want one, an icon picked from HubSpot's icon library on the row itself. Use them for the things a visitor checks before setting off — parking, step-free access, click and collect, a late night.
Rows work with icons, without them and mixed. Where any row on a card carries an icon, the card keeps an icon column, so a row left without one starts its text on the same line as the rows above it. Where no row on a card has an icon, the column is gone and nothing is indented. A location with no rows renders no list and no gap. The icon size and colour, the gap to the text, the space between rows, the space above them and the text size and colour are all fields.
Each location takes a photograph. Whether photographs show at all is one switch; where the photograph sits — left of the text, right of it, or across the full width above it — its shape, its width and its corner radius are fields. A location left without one takes no space for it and the card closes up. With the numbering switch on, a card with no photograph shows its number in that place instead.
| Layout | What it gives you |
|---|---|
| List left, map right | The usual locator. The list scrolls in its own box while the map stays put. |
| Map left, list right | The same, mirrored. |
| List above the map | One column. Good for a short list. |
| Map above the list | One column, map first. |
| Map only | The map with its search controls and no cards. Search, category, near me and the radius picker all still apply, to the pins. Demo 4 on this page is that layout. |
| List only | A filterable directory in as many columns as you set. |
On small screens every layout becomes a single column, and whether the map or the list comes first is a field. The list stops being its own scroll box there, because a scroller inside the page scroll is unusable on a touch screen.
In the two side-by-side layouts the list is capped so the two columns are the same height. The cap follows whatever height the map is actually at: the map height field, the smaller height it settles to while it is held at the top of the screen, and any height a visitor has dragged the handle to. Drag the map taller and the list gets the same room in the same movement, so the two edges stay level and the list's own scroll area grows with it. A field switches this to a height of your own, or to letting the list be as tall as its cards need.
Around a hundred is the comfortable ceiling. Every location is real markup on the page, and filtering walks all of them on each keystroke. That is fast at this size and it keeps the whole thing free of a back end. If your network is larger than that, this is not the right shape for it.
Every string a visitor can read is a field: the search placeholder, the button text, the result count templates, the empty state, the no-results state, the labels on phone, email, hours and directions, and the descriptions read out to screen readers. Result counts are templates with {count}, {radius} and {unit} in them, so they translate without a developer.
The location name is a button with a pressed state, so the list can be driven from the keyboard. Pins are buttons with their own labels. The result count is a live region, so a screen reader hears the list change as filters are applied. Focus rings are visible and their colour and width are fields. The town, region, postcode and country are searchable even on a card that does not print them, and they are hidden from the accessibility tree so nothing is read twice.
A visitor whose system asks for less motion gets no lift on the pins, no eased transitions, no animated scrolling to a card and no animated panning on the street map. That follows the reader's own setting, so an editor cannot switch it off by accident.
Every colour, size, weight, letter spacing, radius and space is a field on the Style tab, grouped by what it affects: typography, key colours, section, container, heading, search controls, location cards, card text, map, marker groups, map bubble, and the empty and no-result states. Colour fields carry opacity as well as a colour and both are used. Body and heading fonts and the accent colour inherit from your HubSpot theme settings until you say otherwise. There are no hard-coded colours, sizes, fonts or strings in the module.
{z}, {x} and {y} and see what comes back.The same module four times over, four businesses, four countries. Two draw on Google Maps with a key in the field; the other two have no key and draw the bundled street map. One of them is the map on its own with no cards beside it. One shows the icon detail rows, one shows a photograph on every card, and one has plain cards. The first is the render the module arrives with, black and white; the other two are moved a long way from it on colour, corner radius, shadow and weight. Every map here is a real map with real streets on it, and every address is a real one. No CSS on this page touches the module: every difference between them is a field value on the instance.