TSML UI (12 Step Meeting List User Interface) is an interactive meeting finder makes the 12 Step Meeting List interface available for use on any web page, regardless of platform.
Here are two demos:
-
San Francisco / Marin (uses a custom database as a data source)
-
Santa Cruz (WordPress / 12 Step Meeting List data source)
To use TSML UI on your website you only need to add some HTML to your web page. To get started, use our configuration instructions.
You don't need to do anything other than enable HTTPS on your website. To ensure all users see this functionality, make sure that anyone who enters a http:// address for your site is redirected to the https:// address.
12 Step Meeting List sites have this enabled by default when they have a permalink structure like /%postname%. If you are not using 12 Step Meeting List and still want this functionality, this can be achieved by resolving your meeting detail pages to the index page. On a WordPress website this can be achieved by adding this PHP code to your theme's functions.php:
add_action('init', function () {
add_rewrite_rule('^meetings/(.*)?', 'index.php?pagename=meetings', 'top');
});Then add this parameter to your embed code: data-path="/meetings".
Here is an example of extending the tsml_react_config object to include a definition for an additional meeting type.
var tsml_react_config = {
strings: {
en: {
types: {
BEACH: 'Beach Meeting',
},
},
},
};A.A. groups that wish to participate in the Meeting Guide app should be careful not to repurpose types already in use. A full list of A.A. meeting types can be found in the Meeting Guide format spec.
A.A. meeting type descriptions are automatically applied to Open and Closed types. These can be unset or overwritten as needed.
var tsml_react_config = {
strings: {
en: {
type_descriptions: {
O: 'This is a custom Open description',
C: undefined, //this type description has been unset
},
},
},
};If you wanted to add a list that is pre-filtered to a single type, "Women" in this example, you can add this code:
var tsml_react_config = {
defaults: { type: ['women'] },
show: {
controls: false,
title: false,
},
};You can link directly to today's meetings using the special today keyword:
https://tsml-ui.code4recovery.org/tests/aasanjose.html#/?weekday=today
This automatically filters to the current day of the week.
Distances can be calculated in miles (mi) or kilometers (km).
var tsml_react_config = {
distance_unit: 'km',
};You can disable the add to calendar button if needed.
var tsml_react_config = {
calendar_enabled: false,
};Open Street Maps can be styled by picking a different tile layer URL and attribution and adding them to the tsml_react_config object. There are several free and paid tile layers listed here.
var tsml_react_config = {
map: {
tiles: {
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',
url: 'https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png',
},
},
};You can optionally specify a tiles_dark attribute which will be used when prefers-color-scheme: dark.
var tsml_react_config = {
map: {
tiles_dark: {
attribution: '© ...',
url: 'https://...',
},
},
};If you prefer the Mapbox map appearance (we switched away from Mapbox in May 2025), you can add this to your site (just replace <pk.your.access.token> with your Mabpox access token):
var tsml_react_config = {
map: {
tiles: {
attribution: `Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>`,
url: 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=<pk.your.access.token>',
},
tiles_dark: {
attribution: `Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>`,
url: 'https://api.mapbox.com/styles/v1/mapbox/dark-v10/tiles/{z}/{x}/{y}?access_token=<pk.your.access.token>',
},
},
};Markers can be adjusted by supplying an alternate SVG image:
var tsml_react_config = {
map: {
markers: {
location: {
html: `<svg…`,
height: 48,
width: 12,
},
},
},
};You can use CSS variables to customize TSML UI’s appearance. Here are the defaults:
#tsml-ui {
--alert-background: #faf4e0;
--alert-text: #998a5e;
--background: #fff;
--border-radius: 4px;
--focus: #0d6efd40;
--font-family: system-ui, -apple-system, sans-serif;
--font-size: 16px;
--in-person: #146c43;
--inactive: #b02a37;
--link: #0d6efd;
--online: #0a58ca;
--online-background-image: url(https://images.unsplash.com/photo-1588196749597-9ff075ee6b5b?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=1440&ixid=MnwxfDB8MXxhbGx8fHx8fHx8fHwxNjIyMTIzODkw&ixlib=rb-1.2.1&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=1920);
--text: #212529;
}Only specify the variables you wish to override in your code. Hex values (#123456) must be used when specifiying colors.
If your website theme supports responsive dark mode, TSML UI can match it with a media query: In this demo the background will be white if the user prefers a light appearance, and black if they prefer dark.
@media (prefers-color-scheme: dark) {
#tsml-ui {
--background: #000;
--color: #fff;
--link: #7bc8ff;
}
}This image will be shown instead of a map for online meetings. Should be roughly 2000px wide and tall.
#tsml-ui {
--online-background-image: url(/path/to/image.jpg);
}To remove:
#tsml-ui {
--online-background-image: none;
}If you are only listing meetings in a single timezone, e.g. Philadelphia, PA, then you should specify a data-timezone attribute in your embed code. This must be in the proper IANA timezone format e.g. America/New_York. TSML UI will assume that any meetings without a specified timezone are in that zone.
However if your site lists meetings in a variety of timezones, and you have a timezone key/column in your meeting data, then you may omit the data-timezone attribute and times will be translated into the user's timezone.
Note: The WordPress plugin 12 Step Meeting List does not yet support timezone keys in meeting data.
Metatypes are types that are not specified explicitly in the data, they are inferred from the data based on this logic:
-
A meeting is considered
In-Personif it doesn't have a type ofLocation Temporarily Closedand it has a specific street address. -
A meeting is considered
Onlineif it has aconference_urlthat matches our recognized formats and/or it has aconference_phone -
A meeting is considered
Activeif it'sIn-PersonorOnline, otherwise it'sInactive.
This app exists to help people find A.A. meetings, and after much discussion we decided that Hybrid was not a useful filter type for that purpose. We believe that people on the whole do not set out looking for a hybrid meeting, they simply want to know whether their online or in-person meeting happens to be hybrid. They will know this by its appearance in the list.
Second, while we can infer that a meeting is "online" if there is a Zoom URL (for example) in the listing, the app should not assume that, when there are online and in-person options, that means it is an actual "hybrid" meeting with a video screen and speakers in the room.
Web servants may add their own meeting types of course.
When meetings are tagged both Speaker (SP) and Discussion (D), TSML UI merges them into a combined Speaker/Discussion type. This enables users to use existing filters to locate Speaker-only and Discussion-only meetings.
Not yet! Please open a pull request and walk us through the process of adding it to NPM and we'll give it a shot.
Contributions are welcome. Ideally, please join Code for Recovery (we have no dues or fees) beforehand to discuss your proposed changes, or at a minimum file an issue. (The one exception: language translations do not need an issue beforehand.)
Here are the steps to follow when developing:
- clone (or, if you are not a member, fork and clone) this repository
- create a branch for your changes
- run
npm iin the project folder (install NPM if it is not installed) - run
npm run devto start the Vite dev server with HMR atindex.html - to test other examples in
public/tests/, runnpm run previewafter building
When you are ready to make a PR:
- clean up your diff, try to change as few lines as possible
- run prettier locally to autoformat your files
- alphabetize things like component props and CSS rules (if applicable)
- run
npm run buildto create the production bundle