# Embed the What's new widget

The "What's new" widget puts your changelog inside your own website or app: a button that opens a panel with the 10 newest entries, and a red badge with the number of entries the visitor has not seen yet. This page shows how to embed it in plain HTML and in a React app, and how to drive it from your own code.

## Before you start

- The changelog is **published** and has an active **custom domain**. The widget loads from that domain, so the console shows no snippet until one is connected. See [Changelog settings](https://docs.evohub.io/changelog-settings.md#custom-domain).
- In **Settings**, **Widget**, **Show the widget** is on. When it is off, the widget shows nothing.
- The changelog is public. On a changelog with password access, the widget is off.

The widget's label comes from **Button label** in the same tab (1 to 30 characters; "What's new" by default), and its colour from the changelog's brand colour.

## Add the script

:::steps
### Copy the snippet
In **Settings**, **Widget**, under **Add it to your site**, copy the **Script** snippet. It has your changelog's domain and slug filled in:

```html
<script src="https://changelog.example.com/widget.js" data-site="acme" async></script>
```

### Paste it into your pages
Add the tag to every page where the widget should appear, before the closing `</body>` tag. The script is small, has no dependencies and loads asynchronously.

### Check it
Open your page. A button appears in the corner you chose under **Position**. Click it to open the panel.
:::

`data-site` must be the changelog's slug, and the script must be loaded from the changelog's own domain. If the slug does not match that changelog, the widget does not appear and the browser console says why.

### Attributes

| Attribute | What it does |
| --- | --- |
| `data-site` | Required. The changelog's slug. |
| `data-position` | `bottom-right`, `bottom-left` or `none`. Overrides **Position** in the settings. With `none`, no floating button is drawn: you open the panel yourself (see below). |
| `data-selector` | A CSS selector for your own button or link, for example `#whats-new`. See [Use your own button](#use-your-own-button). |
| `data-theme` | `light`, `dark` or `auto` (the default, which follows the visitor's system setting). |

## Use your own button

To open the widget from a link or button in your own navigation instead of a floating button, point `data-selector` at it:

```html
<button id="whats-new" type="button">What's new</button>

<script src="https://changelog.example.com/widget.js" data-site="acme" data-selector="#whats-new" async></script>
```

With a selector, the widget:

- draws no floating button,
- puts the unread badge on the top-right corner of every element the selector matches,
- opens the panel below that element when it is clicked (on narrow screens, the panel opens from the bottom of the screen instead).

The elements must already be on the page when the script has loaded; the widget looks for them once.

## React example

In a single-page app, load the script once and keep the element it attaches to on the page — for example in your app's header:

```tsx
import { useEffect } from "react";

const CHANGELOG = "https://changelog.example.com";
const SITE = "acme";

export function WhatsNewButton() {
  useEffect(() => {
    // The widget runs once per page; a second copy of the script does nothing.
    if (document.querySelector(`script[src="${CHANGELOG}/widget.js"]`)) return;
    const script = document.createElement("script");
    script.src = `${CHANGELOG}/widget.js`;
    script.async = true;
    script.dataset.site = SITE;
    script.dataset.selector = "#whats-new";
    document.body.appendChild(script);
  }, []);

  return (
    <button id="whats-new" type="button">
      What's new
    </button>
  );
}
```

Render `WhatsNewButton` in a part of the layout that stays mounted, such as the header. If the button is removed from the page and drawn again later, the badge and the click handler do not come back until the page is reloaded; in that case use the JavaScript API below instead.

## The JavaScript API

Once the script has loaded, it adds `window.EvoHubChangelog`:

| Call | What it does |
| --- | --- |
| `EvoHubChangelog.open()` | Opens the panel. |
| `EvoHubChangelog.close()` | Closes it. |
| `EvoHubChangelog.unread()` | Returns how many entries the visitor has not seen. |

When it has loaded, and each time the count changes, the widget fires an `evohub-changelog:unread` event on `document`, with the count in `event.detail.count`. Use it to draw a badge of your own.

A click anywhere outside the panel closes it, and that includes the click that called `open()`. Stop that click from bubbling up to the document, or the panel closes as soon as it opens:

```html
<button id="news" type="button">What's new <span id="news-count"></span></button>

<script src="https://changelog.example.com/widget.js" data-site="acme" data-position="none" async></script>
<script>
  document.getElementById("news").addEventListener("click", function (event) {
    event.stopPropagation();
    if (window.EvoHubChangelog) window.EvoHubChangelog.open();
  });
  document.addEventListener("evohub-changelog:unread", function (event) {
    document.getElementById("news-count").textContent = event.detail.count || "";
  });
</script>
```

In React, call `event.stopPropagation()` in the `onClick` handler in the same way.

## What the visitor sees

- The panel lists the 10 newest entries, pinned entries first, each with its date, version, categories, title and summary. A title opens the entry on your changelog in a new tab.
- The footer links to the whole changelog and, when email subscriptions are on, to **Subscribe**.
- Entries the visitor has not seen are highlighted and counted in the badge; above 9 it shows "9+". On a visitor's first visit, entries from the last 30 days count as new. Opening the panel marks everything in it as seen.
- The panel closes with **Esc**, the close button or a click outside it, and keyboard focus stays inside it while it is open.
- The widget's text follows the changelog's **Language** setting.

What a visitor has seen is kept in their browser's local storage, so it is per browser, not per person. The widget sets no cookies. It keeps the latest entries in local storage for a minute, so page views within a minute do not fetch them again. When the changelog counts its readers, widget opens and clicks are counted too, except for visitors who send Do Not Track or Global Privacy Control.

## Embed the list in a page

Where you cannot add scripts, or to show the list inside a page rather than in a panel, use the iframe from the same **Add it to your site** card:

```html
<iframe src="https://changelog.example.com/embed" title="What's new" loading="lazy" style="border:0;width:100%;height:480px"></iframe>
```

Under **Allowed sites** in **Settings**, **Widget**, list the origins allowed to show the iframe, such as `https://app.example.com` (up to 20; `http://localhost` is accepted for testing). Leave it empty to allow any site. **Allowed sites** applies only to the iframe; the script works on any site.

## Content Security Policy

If your site sets a Content Security Policy, allow your changelog's domain:

```text
script-src https://changelog.example.com; connect-src https://changelog.example.com; style-src https://changelog.example.com; frame-src https://changelog.example.com
```

`script-src` and `connect-src` are needed for the script. `style-src` is only needed for older browsers that cannot style the widget from the script, and `frame-src` only for the iframe.

## Troubleshooting

- **Nothing appears**: check that the changelog is published and public, that **Show the widget** is on, and that the script's address is the changelog's domain and `data-site` is its slug. The browser console shows a warning starting with `[EvoHub Changelog]` when the slug does not match.
- **The panel opens and closes at once**: your click handler calls `open()` without stopping the click; see [The JavaScript API](#the-javascript-api).
- **No badge on your own button**: the element the selector names was not on the page when the script loaded, or it was drawn again since.
- **A new entry does not show yet**: the widget's list can be up to a couple of minutes old in the browser and at the edge.

## Related

- [Reaching readers](https://docs.evohub.io/reaching-readers.md)
- [Changelog settings](https://docs.evohub.io/changelog-settings.md)
- [Changelog overview](https://docs.evohub.io/changelog-overview.md)
