Embed a Page
Add a "What's new?" button to your own app that opens your Jamdesk changelog in a modal, with an unread dot. Requires one script tag and no build step.
Put a "What's new?" trigger inside your own product. A button or floating launcher opens your docs changelog entries in a modal. A dot marks unread updates per visitor. Paste one <script> tag. Jamdesk hosts and versions the widget.
The unread dot is built around the changelog. The widget can open any docs page in the modal, though. See Point the modal at any page.
Try it live
This page runs the real widget. Click the button to open the modal your visitors get, loaded with this site's changelog:
That button is the <Widget> MDX component. Use it to embed the widget in a Jamdesk docs page. It is one tag, needs no script, and finds your site on its own. Use the <script> snippet below to embed the widget in your own product or app, where MDX components don't run. The widget and modal are the same. The rest of this page covers the script.
Prerequisites
- A published Jamdesk site on its
*.jamdesk.appsubdomain. The widget always loads from there, even if you also serve docs on a custom domain. - A changelog page built from
<Update>entries withrss: truein its frontmatter (see Opt in withrss: true).
Quick Start
Open your dashboard and go to Integrations → What's New widget. Set the page and launcher options. Copy the generated snippet. It looks like this:
<script
src="https://acme.jamdesk.app/_jd/widget.js"
data-base="https://acme.jamdesk.app"
data-page="/changelog"
data-theme="auto"
async
></script>
Paste it into your app's HTML, before the closing </body> tag. It adds a floating What's new launcher in the corner. The launcher opens your changelog in a modal. An unread dot appears when there's an entry the visitor hasn't seen.
Replace acme with your own subdomain. The dashboard card fills this in. It points data-base at the right origin, including the /docs path if you host docs under a subpath.
Pin a Version or Self-Host
The widget is open source. The hosted snippet above always serves the latest version. The jamdesk-widget repository has two more ways to load it, to freeze a known version or serve the file yourself.
Pin a version with jsDelivr. Load a tagged release from the CDN. The bytes never change under you:
<script
src="https://cdn.jsdelivr.net/gh/jamdesk/jamdesk-widget@v1.0.0/widget.js"
data-base="https://acme.jamdesk.app"
data-page="/changelog"
async
></script>
Self-host. Download widget.js from the latest release and serve it from your own origin. Use this when a strict script-src policy rules out third-party scripts.
Set data-base to your *.jamdesk.app origin for a pinned or self-hosted file. The hosted snippet reads it from the script's own URL. A CDN or your own server can't. Each release publishes a Subresource Integrity hash to pin the exact bytes. The repository README covers the three install paths.
Opt in with rss: true
The widget reads the newest entry from your RSS feed. A page feeds the widget only when its frontmatter sets rss: true:
---
title: Changelog
rss: true
---
<Update label="June 2026" date="2026-06-01">
**Spec validation at build time.** Every deploy validates the OpenAPI specs your `docs.json` references.
</Update>
Without rss: true, the widget loads but shows no entries. The launcher stays hidden. A docs page that only demonstrates the <Update> component (with no rss: true) stays out. A demo date never lights up the dot.
Each <Update> that feeds the widget needs a date (any value Date.parse reads, like 2026-06-01). rss: true on the page is not enough. The date orders the feed and identifies the newest entry. The widget skips entries without one. With no dated entries, the floating launcher stays hidden and the unread dot never appears, even with rss: true set.
Configure the Snippet
Every option is a data- attribute on the script tag. The dashboard card writes these. You can also hand-edit the snippet.
| Attribute | Values | Default | Purpose |
|---|---|---|---|
data-base | Your site URL | script origin | The *.jamdesk.app origin (plus /docs if you host under a subpath). |
data-page | Path | /changelog | Any docs path to open in the modal, not just the changelog. See Point the modal at any page. |
data-theme | auto, light, dark | auto | Force the modal's color scheme, or follow the visitor's system setting. |
data-position | bottom-right, bottom-left, top-right, top-left | bottom-right | Which corner the floating launcher sits in. Ignored when data-trigger is set. |
data-label | Text | What's new | The floating launcher's button text. |
data-width | CSS length | 560px | Modal width. See Size the modal. |
data-height | CSS length | 680px | Modal height. |
data-radius | CSS length | 12px | The modal's corner radius. Lower it for squarer corners. |
data-unread | off to disable | on | Whether to show the unread dot. |
data-unread-color | Hex or CSS color name | #e5484d | The unread dot's color. |
data-button-color | Hex or CSS color name | #111 | Floating launcher background. Ignored when data-trigger is set. |
data-button-text-color | Hex or CSS color name | #fff | Floating launcher text color. |
data-trigger | CSS selector | (none) | Bind to your own element instead of the floating launcher. |
data-project | Slug | derived from data-base | The key the per-visitor "seen" state is stored under. Override only if one origin serves more than one changelog. |
Point the modal at any page
data-page opens any path on your docs site, not only /changelog. The modal renders the page you name without its site chrome. Use it for an announcement or a migration note:
<script
src="https://acme.jamdesk.app/_jd/widget.js"
data-base="https://acme.jamdesk.app"
data-page="/announcements/2026-migration"
data-unread="off"
async
></script>
Two behaviors stay tied to your changelog feed:
- The unread dot tracks your changelog's newest entry, not the page in the modal. Set
data-unread="off"when the modal opens another page. Otherwise the dot lights up for changelog updates the visitor never sees there. - The floating launcher appears only when your changelog has an entry (see Opt in with
rss: true). On a site with no changelog, bind to your own element withdata-trigger. Your element always shows.
Launcher modes
A bound element gets the unread dot. The widget renders no floating button, and data-position and data-label no longer apply:
<script
src="https://acme.jamdesk.app/_jd/widget.js"
data-base="https://acme.jamdesk.app"
data-page="/changelog"
data-trigger="#whats-new"
async
></script>
Size the modal
The modal opens at 560 × 680 px. Change it with data-width and data-height. A bare number reads as pixels. Any px, vw, vh, rem, em, or % value works too:
<script
src="https://acme.jamdesk.app/_jd/widget.js"
data-base="https://acme.jamdesk.app"
data-page="/changelog"
data-width="720px"
data-height="600px"
async
></script>
The modal is capped at 92vw wide and 86vh tall. A large size still fits on a phone. An unrecognized value falls back to the default. The corners default to a 12px radius. Square them off or round them further with data-radius (any CSS length).
Style the launcher button
The floating launcher is a dark pill by default. Recolor it with data-button-color (background) and data-button-text-color (text). Use a hex value or a CSS color name:
<script
src="https://acme.jamdesk.app/_jd/widget.js"
data-base="https://acme.jamdesk.app"
data-page="/changelog"
data-button-color="#4f46e5"
data-button-text-color="#ffffff"
async
></script>
These two attributes style only the widget's own floating button. They have no effect on an element bound with data-trigger. That element keeps its own styling.
Customize the unread dot
The unread dot is red (#e5484d) and on by default. Recolor it with data-unread-color (a hex value or a CSS color name). Turn it off with data-unread="off":
<!-- Recolor the dot -->
<script src="https://acme.jamdesk.app/_jd/widget.js" data-base="https://acme.jamdesk.app" data-unread-color="#7c3aed" async></script>
<!-- Turn the dot off -->
<script src="https://acme.jamdesk.app/_jd/widget.js" data-base="https://acme.jamdesk.app" data-unread="off" async></script>
Turning the dot off keeps the launcher and modal. It removes only the indicator.
The Unread Dot
The widget compares the newest entry's id against a value in the browser's localStorage (one key per project). A dot shows on the launcher when they differ. Opening the modal marks the entry seen. The dot stays cleared until the next update ships.
The state lives in localStorage, per browser and per visitor. There's no account or tracking. Clearing site data resets it. A visitor in a fresh browser sees the dot once. It returns when you publish something new.
Examples
<script
src="https://acme.jamdesk.app/_jd/widget.js"
data-base="https://acme.jamdesk.app"
data-page="/changelog"
data-position="bottom-left"
data-unread-color="#22c55e"
async
></script>Content-Security-Policy
If your site sends a strict Content-Security-Policy, allow your *.jamdesk.app origin in three directives. Otherwise the widget silently does nothing:
Content-Security-Policy:
script-src https://acme.jamdesk.app;
frame-src https://acme.jamdesk.app;
connect-src https://acme.jamdesk.app;
script-srcloadswidget.js.frame-srcrenders the modal's iframe.connect-srcfetches the changelog metadata for the unread dot.
A missing directive shows no error banner. The launcher won't appear, or the modal stays blank. Check your browser console for CSP violations if the widget doesn't show.
Password-Protected Sites
Don't embed the widget if your docs site is password-protected. The unlock screen belongs on your *.jamdesk.app site, not inside a third-party iframe. Embedding it asks visitors to type your site password into a frame on another origin. That looks like a phishing prompt. Keep the widget for public changelogs.
