BMLT Crumb Widget
An embeddable NA meeting finder widget for any website.
Live DemoOverview
Crumb Widget is a self-contained JavaScript widget that queries a BMLT root server and renders a fully featured meeting finder — search, filters, list view, and an interactive map.
It ships as a single JavaScript file with all CSS injected at runtime. No stylesheets, no build steps, no framework required on the host page.
Quick Start
To get started, add a container element to your page, point it at your BMLT root server, and load the widget script. Here is a complete example:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Meeting Finder</title>
</head>
<body>
<div
id="crumb-widget"
data-root-server="https://myserver.com/main_server/"
data-service-body="3"
></div>
<script type="module" src="https://cdn.aws.bmlt.app/crumb-widget.js"></script>
</body>
</html>
The widget initializes automatically when the script loads, so no additional JavaScript is needed.
Important: Be sure your page includes <meta name="viewport" content="width=device-width, initial-scale=1.0" /> in the <head>. This is important for proper rendering on mobile devices and small screens.
Data Attributes
All configuration is set as data-* attributes on the #crumb-widget div.
| Attribute | Description | |
|---|---|---|
| data-root-server | Required |
Full URL to your BMLT root server.
Example: https://example.org/main_server
|
| data-service-body | Optional |
Filter meetings to one or more service bodies. Accepts a single numeric ID or a
comma-separated list. Omit to show all meetings on the server.
Single: "42"
Multiple: "42,57,103"
Note: recursive lookup is always enabled — child service bodies are automatically included and cannot be disabled. |
| data-view | Optional |
Default view when the widget loads.
Values: list (default) or map
Can be overridden at runtime by the ?view= query parameter (see URL Query Parameters).
|
Global Config Object
For options not available as data attributes, define CrumbWidgetConfig
as a global variable before loading the script.
<script>
var CrumbWidgetConfig = {
defaultView: 'map'
};
</script>
<script src="app.js"></script>
| Property | Type | Description |
|---|---|---|
| defaultView | 'list' | 'map' |
Override the default view. Takes precedence over data-view. |
| language | string |
Override the UI language (e.g. 'es', 'fr').
Defaults to navigator.language, falling back to English.
Supported: en, es, fr, de, pt, it, sv, da.
|
| columns | string[] |
Columns to show in list view. Default: ['time', 'name', 'location', 'address'].
Omit any column name to hide it. Available values: time, name, location (venue/building name),
address (street address with in-person/online badges), service_body.
service_body is hidden by default — add it explicitly to show the service body name.
|
| geolocation | boolean |
Show a Near Me button and auto-geolocate on page load to find meetings near the user.
Requires HTTPS. Default: false. If geolocation fails or is denied, an error is shown — no
fallback load occurs. Note: serviceBodyIds filters have no effect on
geolocation searches; results are determined entirely by proximity.
|
| geolocationRadius | number |
Search radius in miles when using geolocation. Default: 10.
|
| darkMode | 'auto' | true | false |
Built-in dark color scheme. 'auto' follows the visitor's OS preference
(prefers-color-scheme); true forces dark mode regardless of OS setting;
false (default) disables it. Works alongside dark mode tiles.
|
| map.tiles | TilesConfig |
Custom map tile provider. Replaces the default OpenStreetMap tiles. See Tile Provider below. |
| map.tiles_dark | TilesConfig |
Alternate tile provider used when prefers-color-scheme: dark.
Swaps automatically on OS theme change.
See Dark Mode Tiles below.
|
| map.markers.location | MarkerConfig |
Custom map marker for meeting locations. Replaces the default NA marker. See Marker Config below. |
Language
The widget automatically detects the visitor's language from navigator.language and falls back to English.
Override it with the language config property:
<script>
var CrumbWidgetConfig = {
language: 'es'
};
</script>
<script src="app.js"></script>
Language codes are matched on the base tag, so 'fr-CA' uses fr.
Supported languages:
| Code | Language |
|---|---|
en | English |
es | Spanish |
fr | French |
de | German |
pt | Portuguese |
it | Italian |
sv | Swedish |
da | Danish |
Geolocation
Enable the Near Me button with geolocation: true. When enabled, the widget
also attempts to geolocate the user automatically on page load and show meetings nearby.
Requires a secure context (HTTPS).
If geolocation is denied or unavailable, an error message is displayed — there is no silent fallback to loading all meetings. This is intentional: on large servers without service body scoping, loading all meetings could return tens of thousands of results.
Service body filters have no effect on geolocation searches. When using Near Me, results
are determined entirely by the user's coordinates and geolocationRadius. If no
serviceBodyIds are configured, the Near Me button cannot be toggled off — geolocation is the
only data source.
While in map view with Near Me active, panning or zooming the map reveals a Search this area
button at the top of the map. Clicking it reloads meetings centered on the current map viewport, using the
same geolocationRadius. The map stays at the user's position — it does not jump to fit the new results.
<script>
var CrumbWidgetConfig = {
geolocation: true,
geolocationRadius: 25 // miles, default 10
};
</script>
<script src="app.js"></script>
URL Query Parameters
The ?view= query parameter sets the initial view and overrides data-view
and defaultView:
| Value | Behaviour |
|---|---|
list | Force list view on load |
map | Force map view on load |
auto |
Geolocate on load — success shows map with nearby results; failure falls back to list with all meetings |
Example: https://example.org/meetings/?view=auto
?view=list and ?view=map disable auto-geolocation even if geolocation: true is set.
CSS Variables
The widget exposes CSS custom properties so you can theme it without touching JavaScript.
Set any of these on the #crumb-widget element — only specify the ones you want to override.
| Variable | Default | Controls |
|---|---|---|
| --bmlt-font-family | system-ui, -apple-system, sans-serif | Widget font stack |
| --bmlt-font-size | 16px | Base font size |
| --bmlt-background | #ffffff | Widget and controls bar background |
| --bmlt-text | #111827 | Primary text color |
| --bmlt-border | #e5e7eb | Borders and dividers |
| --bmlt-accent | #2563eb | Active buttons, links, focus indicators |
| --bmlt-accent-light | #eff6ff | Row hover, active filter panel background |
| --bmlt-border-radius | 8px | Card and button corner radius |
| --bmlt-row-alt | #f9fafb | Alternating (even) row background |
| --bmlt-in-person | #15803d | In-person badge text |
| --bmlt-in-person-bg | #dcfce7 | In-person badge background |
| --bmlt-virtual | #1d4ed8 | Virtual badge text |
| --bmlt-virtual-bg | #dbeafe | Virtual badge background |
| --bmlt-surface | #ffffff | Input, button, and dropdown panel backgrounds |
| --bmlt-hover | #f9fafb | Neutral hover state (buttons and rows not using accent) |
| --bmlt-divider | #f3f4f6 | Internal row and list divider lines |
| --bmlt-text-secondary | #6b7280 | Secondary / muted text (table headers, time, location) |
| --bmlt-text-muted | #9ca3af | Very muted text (footer count, empty-state messages) |
#crumb-widget {
--bmlt-accent: #dc2626;
--bmlt-accent-light: #fef2f2;
--bmlt-border-radius: 0px;
}
Dark Mode
The widget ships with a built-in dark palette. Enable it with the darkMode config option —
no extra CSS required. Works alongside the dark mode tile option.
<script>
var CrumbWidgetConfig = {
darkMode: 'auto'
};
</script>
<script src="app.js"></script>
<script>
var CrumbWidgetConfig = {
darkMode: true
};
</script>
<script src="app.js"></script>
To use a custom dark palette instead, override the CSS variables directly:
@media (prefers-color-scheme: dark) {
#crumb-widget {
--bmlt-background: #1e293b;
--bmlt-text: #f1f5f9;
--bmlt-border: #334155;
--bmlt-accent: #60a5fa;
--bmlt-accent-light: #1a3460;
--bmlt-row-alt: #243044;
--bmlt-in-person: #86efac;
--bmlt-in-person-bg: #14532d;
--bmlt-virtual: #93c5fd;
--bmlt-virtual-bg: #1e3a5f;
--bmlt-surface: #2a3a50;
--bmlt-hover: #2d4060;
--bmlt-divider: #263244;
--bmlt-text-secondary: #94a3b8;
--bmlt-text-muted: #64748b;
}
}
CSS Helper Classes
These classes are applied internally and can also be targeted in your own CSS for further customization.
| Class | Used on |
|---|---|
| bmlt-btn-primary | Filled accent buttons — active filter chips, view toggle, Join Meeting |
| bmlt-btn-secondary | Outlined buttons — Get Directions |
| bmlt-link | Link-style buttons — Back, email contact |
| bmlt-badge-in-person | In-person venue badge |
| bmlt-badge-virtual | Virtual venue badge |
| bmlt-card | Detail section cards |
| bmlt-row | Meeting list table rows |
#crumb-widget .bmlt-btn-secondary:hover {
background-color: transparent;
opacity: 0.8;
}
Tile Provider
By default the map uses OpenStreetMap tiles.
Switch to any Leaflet-compatible tile provider
by setting map.tiles.
| Property | Type | Description |
|---|---|---|
| url | string |
Tile URL template. Use {z}, {x}, {y} placeholders (and {r} for retina where supported). |
| attribution | string |
Attribution HTML displayed in the map's attribution control. Required by most tile providers' terms of service. |
<script>
var CrumbWidgetConfig = {
map: {
tiles: {
url: 'https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.png',
attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}
}
};
</script>
<script src="app.js"></script>
Dark Mode Tiles
Set map.tiles_dark to use a different tile layer for visitors with
prefers-color-scheme: dark. The layer swaps automatically when the OS
theme changes — no page reload needed. If omitted, the same tiles are used in all
color schemes.
<script>
var CrumbWidgetConfig = {
map: {
tiles: {
url: 'https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.png',
attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
},
tiles_dark: {
url: 'https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png',
attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
},
}
};
</script>
<script src="app.js"></script>
<script>
var CrumbWidgetConfig = {
map: {
tiles: {
url: 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=<pk.your.access.token>',
attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
},
tiles_dark: {
url: 'https://api.mapbox.com/styles/v1/mapbox/dark-v10/tiles/{z}/{x}/{y}?access_token=<pk.your.access.token>',
attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
},
}
};
</script>
<script src="app.js"></script>
CrumbWidgetConfig is set, you need to load app.js dynamically after the token is ready. The example below uses the Google Map Tiles API — you'll need a referer-restricted API key with the Map Tiles API enabled.
<script>
(async () => {
const key = 'YOUR_REFERER_RESTRICTED_GOOGLE_API_KEY';
const { session } = await fetch(
`https://tile.googleapis.com/v1/createSession?key=${encodeURIComponent(key)}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mapType: 'roadmap', language: 'en-US', region: 'US' })
}
).then(r => r.json());
window.CrumbWidgetConfig = {
map: {
tiles: {
attribution: '© Google',
url: `https://tile.googleapis.com/v1/2dtiles/{z}/{x}/{y}?session=${session}&key=${key}`
}
}
};
const s = document.createElement('script');
s.src = 'app.js';
document.body.appendChild(s);
})();
</script>
Marker Config
The map.markers.location object lets you replace the default NA map pin
with any image or inline SVG.
| Property | Type | Description |
|---|---|---|
| html | string |
HTML rendered inside the marker element. Typically an <img> tag or inline SVG. |
| width | number |
Icon width in pixels. |
| height | number |
Icon height in pixels. The anchor point is set to the bottom-center of the icon. |
<script>
var CrumbWidgetConfig = {
map: {
markers: {
location: {
html: '<img src="https://example.com/my-marker.png">',
width: 23,
height: 33
}
}
}
};
</script>
<script src="app.js"></script>
Example: Basic Embed
Show all meetings on a public BMLT server:
<div
id="crumb-widget"
data-root-server="https://latest.aws.bmlt.app/main_server"
></div>
<script src="https://cdn.aws.bmlt.app/crumb-widget.js"></script>
Example: Filter by Service Body
Show only meetings belonging to service body 42 (and its children):
<div
id="crumb-widget"
data-root-server="https://bmlt.sezf.org/main_server"
data-service-body="42"
></div>
<script src="app.js"></script>
Example: Multiple Service Bodies
Pass a comma-separated list to include meetings from several service bodies:
<div
id="crumb-widget"
data-root-server="https://bmlt.sezf.org/main_server"
data-service-body="42,57,103"
></div>
<script src="app.js"></script>
Example: Default to Map View
<div
id="crumb-widget"
data-root-server="https://bmlt.sezf.org/main_server"
data-service-body="42"
data-view="map"
></div>
<script src="app.js"></script>
Example: WordPress
The easiest way to add Crumb Widget to a WordPress site is the official Crumb plugin (GitHub). It wraps this widget in a shortcode with a settings screen — no code required.
Installation
- Search for Crumb in the WordPress plugin directory, or upload the plugin ZIP manually.
- Activate the plugin.
- Go to Settings → Crumb and enter your root server URL.
- Add the shortcode to any page or post.
Shortcode
[bmlt_client]
The root server URL set in the plugin settings is used by default. You can override it — or filter by service body — directly in the shortcode:
[bmlt_client root_server="https://your-server/main_server" service_body="42"]
Advanced configuration
All global config options (language, geolocation, columns, map tiles, etc.)
can be set from your theme's functions.php via the bmltclient_config filter:
add_filter( 'bmltclient_config', function( $config ) {
return array_merge( $config, [
'language' => 'en',
'geolocation' => true,
'geolocationRadius' => 20,
'height' => 800,
'columns' => [ 'time', 'name', 'location', 'address', 'service_body' ],
] );
} );
Features
- List view — meeting table sorted by day and time, with venue type badges
- Map view — interactive Leaflet map for in-person meetings (including those with an online component); click a marker to see meetings at that location; pan or zoom to reveal a Search this area button when Near Me is active
- Detail view — full meeting info: schedule, address with Google Maps directions, virtual meeting join button, formats, and notes
- Text search — real-time filter across meeting name, location, and notes
- Weekday filter — toggle individual days of the week
- Venue type filter — in-person or virtual (meetings with both an in-person location and an online link appear under both)
- Time of day filter — morning, afternoon, evening, or night
- Format filter — multi-select dropdown to filter by meeting format (e.g. Open, Closed, Speaker); shows only formats present in the loaded meeting data
- Locale-aware time display — meeting times are shown in 12-hour (AM/PM) or 24-hour format based on the user's browser locale automatically
- Recursive service body support — child service bodies are always included
- Multiple service bodies — comma-separate IDs to combine regions
Website Builders
Crumb Widget can also be embedded on common website builders, but the setup varies by platform.
Google Sites
Google Sites supports embedding HTML, CSS, and JavaScript code using Insert → Embed → Embed code. You can also use a full-page embed if you want the meeting finder to live on its own page.
- Open your site in Google Sites.
- Click Insert → Embed.
- Choose Embed code.
- Paste your widget markup and script.
- Click Insert, then publish your site.
<div
id="crumb-widget"
data-root-server="https://myserver.com/main_server/"
data-service-body="3"
></div>
<script type="module" src="https://cdn.aws.bmlt.app/crumb-widget.js"></script>
Note: In Google Sites, JavaScript must be included inside <script> tags when using the embed code option.
Squarespace
Squarespace supports custom code, but JavaScript support depends on your plan. Their documentation says advanced code blocks and code injection are available on Core, Plus, Advanced, Business, Commerce Basic, and Commerce Advanced plans.
- Edit the page where you want the widget.
- Add a Code Block.
- Paste the widget container and script.
- Save and publish.
<div
id="crumb-widget"
data-root-server="https://myserver.com/main_server/"
data-service-body="3"
></div>
<script type="module" src="https://cdn.aws.bmlt.app/crumb-widget.js"></script>
Important: If JavaScript is disabled in your Squarespace code block or unavailable on your plan, the widget will not load. You may need a higher-tier Squarespace plan for JavaScript-based embeds.
Wix
Wix supports custom code through Settings → Custom Code. Wix says this requires your site to be published and to have a connected domain. Since connecting your own domain is a Premium or Studio feature, you may need a paid plan to use the JavaScript embed method.
- Publish your site.
- Make sure the site has a connected domain.
- Go to Settings → Custom Code.
- Add the widget script to the page where you want it to load.
- Add a container element in the page content where the widget should render.
<div
id="crumb-widget"
data-root-server="https://myserver.com/main_server/"
data-service-body="3"
></div>
<script type="module" src="https://cdn.aws.bmlt.app/crumb-widget.js"></script>
Important: Wix’s custom code feature is intended for third-party snippets and depends on Wix platform restrictions. If you do not see the Custom Code option, check whether your site is published and whether it has a connected domain.
Browser Support
All modern browsers. Internet Explorer is not supported.
- Chrome / Edge 90+
- Firefox 90+
- Safari 14+