# Analytics

`Analytics` measures page views and declared events with the shared Umami instance, on by default, and provides the privacy dialog where visitors turn statistics off.

## Usage

Mount one `Analytics` in the root layout, inside `Page` so its dialog follows the page language. `Footer` then shows the privacy control after its notice.

```svelte
<script>
  import { page } from "$app/state";
  import { Analytics, Footer, Header, Main, Page, SkipLink } from "@dg/ui";

  /** @type {{ children?: import('svelte').Snippet }} */
  let { children } = $props();
</script>

<Page lang="en">
  <SkipLink />
  <Header app="Example" path={page.url.pathname} />
  <Main>{@render children?.()}</Main>
  <Footer />
  <Analytics {page} />
</Page>
```

To measure actions, declare their names once and describe them in the dialog. `track` sends a declared name and drops any other.

```svelte
<script>
  import { page } from "$app/state";
  import { Analytics, Button, track } from "@dg/ui";
</script>

<Analytics {page} events={["archive_opened"]}>
  <p>This site also counts openings of the archive.</p>
</Analytics>
<Button label="Open the archive" onclick={() => track("archive_opened")} />
```

## Notes

- The host serves `/analytics-config.json` as `{ "endpoint": "https://…/api/send", "website": "…" }`. Workspace Caddyfiles serve the distributed·gallery statistics website, shared by every app, by default. Without this file, an HTTPS endpoint without credentials and a website ID, statistics stay off and `Footer` shows no control. Development servers serve no configuration.
- Statistics are on by default. The dialog turns them off and on again; the choice is saved in `localStorage` under Umami's own `umami.disabled` key, with no expiry. Do Not Track and Global Privacy Control always turn them off, and the dialog hides its control. Unreadable storage counts as turned off; when a choice cannot be saved, statistics stay off for the visit and the dialog reports it.
- Each change of path sends one page view. The path is sent without query or fragment and starts with the domain, such as `/example.com/about`, so reports keep the apps of the shared website apart without a filter; pages with status 404 are recorded under `/404`, such as `/example.com/404`. The domain is also Umami's distinct ID, which it hashes into the visitor identifier, so no identifier spans two sites. The first measurement carries the origin of an external referrer; no title, screen size or language is sent. Events carry the current path and their name, never custom properties.
- Event names are fixed identifiers. Never derive them from content. Measurement failures are silent and never affect the page.
- `Analytics` renders only its dialog and forwards no attributes. Mount it once per application.

## Parameters

| Name       | Type                                            | Default     | Description                                                                                                                |
| ---------- | ----------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- |
| `page`     | `{ url: { pathname: string }, status: number }` | `Required`  | SvelteKit's `page` state from `$app/state`, which supplies the path and status of each view.                               |
| `events`   | `string[]`                                      | `[]`        | Event names that `track` may send.                                                                                         |
| `children` | `Snippet`                                       | `undefined` | App-specific measurement details, shown in the dialog's formatted text after the general description, usually a paragraph. |

## Accessibility

The privacy control in `Footer` is a native button named by the dialog title, styled as a link in the notice typography, with the shared control target height. The dialog follows [`Dialog`](dialog.md): it is named by its title, traps focus and returns it to the control on close. Statistics status uses `.feedback`; a choice that cannot be saved is announced as an alert. The built-in text follows the `Page` language.
