# Button and ks:subscribe

The button is the easiest way to turn visitors of your website into subscribers. It needs no API key, only the calendar id.

```html
<div class="ks-subscribe" data-calendar="cal_XXXXXXXXXXXX"></div>
<script src="https://app.kalender-sync.de/embed/v1/button.js" async></script>
```

<SubscribeButtonPreview />

## Attributes

| Attribute | Effect | Default |
|---|---|---|
| `data-calendar` | Calendar id (`cal_` plus 12 characters). Required; without a valid id nothing renders. | |
| `data-color` | Hex color `#rrggbb`. The button picks the text color itself for enough contrast. | `#2563eb` |
| `data-label` | Custom button text | "Subscribe to calendar" / "Kalender abonnieren" |
| `data-lang` | `de` or `en` | The page's `lang`, then the browser language, otherwise English |

You need the script once per page. It renders every element with the class `ks-subscribe` and a `data-calendar`.

## How the button works

- It renders into a **shadow DOM**. Your page's CSS does not reach in, and its CSS does not leak out.
- It sets **no cookies**, stores nothing and loads nothing besides the script itself.
- The menu entries are plain links to `https://app.kalender-sync.de/api/public/subscribe/{calendar_id}/go/{app}`. Only that request creates the subscription and forwards to the calendar app. Your page sends no JavaScript requests to us, and you need no CORS.
- Apps in the menu: Apple Calendar, Google Calendar, Microsoft 365, Outlook.com, Outlook (desktop) and "Other app", which opens the subscribe page with the feed link. The app that fits the device comes first.
- Escape closes the menu, so does a click outside.

## The `ks:subscribe` event

We don't track visitors. So that you still know how your button is used, it fires the **`ks:subscribe`** event on every click on a menu entry. It bubbles out of the shadow DOM (`composed: true`), so you can listen on the element or on `document`.

```js
document.addEventListener('ks:subscribe', (event) => {
  // event.detail = { calendar: 'cal_XXXXXXXXXXXX', app: 'google' }
  const { calendar, app } = event.detail;

  // Example: forward to your own analytics, under your consent
  window.plausible?.('Calendar subscribe', { props: { calendar, app } });
});
```

`app` is one of: `apple`, `google`, `office365`, `outlook`, `outlook_desktop`, `other`.

The event runs entirely on your page. Whether and how you use it is up to you, and so is the consent for it.

## Pages that render later

Page builders (Elementor, Divi …) and single-page apps (React, Vue …) often insert content after the script ran. Then call this after inserting:

```js
window.KalenderSync?.mountButtons();
```

Buttons that are already rendered stay unchanged. To re-render a button with other attributes, replace the element with a new one and call `mountButtons()` again.

Example for React:

```jsx
useEffect(() => {
  window.KalenderSync?.mountButtons();
}, []);

return <div className="ks-subscribe" data-calendar="cal_XXXXXXXXXXXX" />;
```

## Your own buttons instead of the script

If you don't want to embed a script, link the calendar's `subscribe_page_url` with a button of your own. Subscriptions through the subscribe page are counted the same way.

The `subscribe_links` from the [calendar response](https://docs.kalender-sync.de/en/api/calendars/#the-response) work too, but they are not counted in the [statistics](https://docs.kalender-sync.de/en/api/stats/).

## Content Security Policy

If your website sets a CSP, add to your existing directives:

```
script-src https://app.kalender-sync.de;
style-src 'unsafe-inline';
img-src data:;
```

You only need `style-src` and `img-src` if your CSP restricts those directives at all: the button brings inline styles in its shadow DOM, and the Microsoft icon is embedded as a `data:` image. `connect-src` and `frame-src` are not needed.