In-app help

Buttons that open a mado demo over your own product, and a help hub your users can search. There are no tooltips over your live UI — a demo is a recording, so it keeps working when your front end changes underneath it.

Install

One script tag, anywhere in your app shell:

<script async src="https://mado.show/p/help.js" data-hub="hk_your_hub_key"></script>

Your key is under Help hubs in Studio.

Buttons

Any element with data-mado-open opens that demo in a modal over the page. data-step deep-links a specific step, and data-vars is a JSON object of personalisation values — {"name":"Dana"} fills {name} placeholders in the demo. data-mado-hub opens the help hub instead of a single demo.

<button data-mado-open="invoice-basics">Show me how</button>

<!-- at a specific step -->
<button data-mado-open="invoice-basics" data-step="s4">Show me how</button>

<!-- personalised -->
<button data-mado-open="invoice-basics" data-vars='{"name":"Dana"}'>Show me how</button>

<!-- the help hub -->
<button data-mado-hub>Help</button>

JavaScript

The same actions from your own event handlers. Mado.hub.setDone shows checkmarks in the hub for demos your users have already completed — progress stays in your app; we store nothing about your users.

Mado.open('invoice-basics', { step: 's4', vars: { name: 'Dana' } });

Mado.hub.open({ query: 'export' });
Mado.hub.setPage('/invoices');          // single-page apps; default is location.pathname
Mado.hub.setDone(['invoice-basics']);   // checkmarks — progress stays in your app; we store nothing about your users

Mado.on('open', ({ what, demo }) => { /* what: 'demo' or 'hub' */ });
Mado.on('close', ({ what, demo }) => { /* … */ });
Mado.on('search', ({ query, results }) => { /* … */ });
Mado.on('complete', ({ demo }) => { /* … */ });

The script loads asynchronously. Calls made before it has loaded can be queued; they run in order once it has. None of these calls ever throws into your page.

// Before help.js has loaded (it loads async), queue calls instead:
window.Mado = window.Mado || {};
Mado.q = Mado.q || [];
Mado.q.push(['hub.setPage', '/invoices']);
Mado.q.push(['on', 'complete', ({ demo }) => { /* … */ }]);

To hide the floating button and open the hub only from your own buttons, add data-launcher="none" to the script tag, or set the launcher to none in Studio.

Page rules

In Studio, give a demo page rules like /invoices or /projects/*/settings; the hub lists matching demos first, based on Mado.hub.setPage or, by default, location.pathname.

Native apps

There's no SDK. Open the hosted hub (https://mado.show/h/:key) or a demo link (https://mado.show/d/:slug) in an in-app browser — the demos themselves are real phone recordings either way.

iOS — Swift

import SafariServices

let url = URL(string: "https://mado.show/d/invoice-basics")!
let safari = SFSafariViewController(url: url)
present(safari, animated: true)

Android — Kotlin

import androidx.browser.customtabs.CustomTabsIntent

val intent = CustomTabsIntent.Builder().build()
intent.launchUrl(context, Uri.parse("https://mado.show/d/invoice-basics"))

React Native

import * as WebBrowser from 'expo-web-browser';
import { Linking } from 'react-native';

async function openDemo(slug: string) {
  const url = `https://mado.show/d/${slug}`;
  try {
    await WebBrowser.openBrowserAsync(url);
  } catch {
    await Linking.openURL(url); // fallback if expo-web-browser isn't available
  }
}

Flutter

import 'package:url_launcher/url_launcher.dart';

await launchUrl(
  Uri.parse('https://mado.show/d/invoice-basics'),
  mode: LaunchMode.inAppBrowserView,
);

Privacy

No cookies, no local storage. Searches are stored for 90 days, lower-cased, capped at 80 characters, and shown to you only as counts.

Content Security Policy

If your app sets a CSP, allow:

script-src https://mado.show;
frame-src https://mado.show;
connect-src https://mado.show https://t.mado.show;

Size

help.js is under 4 KB gzipped and loads nothing until your page is idle. The search panel loads on first open.