Solid
SolidJS has a dedicated wrapper: @vskstudio/takt-solid (component, accessor, declarative click, web component), built on the @vskstudio/takt-core core.
pnpm add @vskstudio/takt-solid @vskstudio/takt-core solid-js (^1.8) and @vskstudio/takt-core (>=0.8.1) are peer dependencies. It is a thin SSR-safe layer (init only happens on mount, in the browser), fully typed, that never changes the wire payload or the core’s privacy guarantees. It exposes two entry points:
@vskstudio/takt-solid— the<Takt>component, theuseTakt()accessor,createTaktEvent(), the<TaktEvent>component, the<TaktBadge>/<TaktEmbed>widgets, plus the core helpers it re-exports (badgeUrl(),embedUrl(),createStats(),PublicApiError).@vskstudio/takt-solid/element— the Solid-free<takt-analytics>web component for plain HTML.
<Takt> component + useTakt()
Place <Takt> once near the root: it boots analytics in onMount, emits the initial pageview, wires up SPA navigation and publishes the instance into a module store. useTakt() then retrieves it from anywhere in the app — as long as you call the accessor after that mount.
import type { JSX } from 'solid-js'
import { Takt } from '@vskstudio/takt-solid'
export function App(props: { children: JSX.Element }) {
return (
<Takt domain="example.com" outbound files={['pdf', 'zip']}>
{props.children}
</Takt>
)
} Call useTakt() inside the handler, not in the component body: a descendant’s body runs before <Takt>’s onMount, so an instance captured there would stay the no-op forever and every event would go nowhere. A click handler, a createEffect or createTaktEvent() all resolve the instance after mount.
import { useTakt } from '@vskstudio/takt-solid'
export function SignupButton() {
return (
<button
onClick={() =>
useTakt().track('Signup', {
props: { plan: 'pro' },
revenue: { amount: '29.00', currency: 'EUR' }
})
}
>
Sign up
</button>
)
} <Takt> component props:
| Prop | Type | Default | Role |
|---|---|---|---|
domain | string | location.hostname | Site identifier |
endpoint | string | https://taktlytics.com/api/event | Ingestion URL. Pass /api/event for a same-origin first-party proxy. |
scriptOrigin | string | — | First-party origin to derive the endpoint from (that origin followed by /api/event). endpoint wins over it. |
outbound | boolean | false | Tracks outbound links |
files | boolean \| string[] | false | Tracks downloads (optional extension list) |
spa | boolean | true | Tracks client navigation (auto pageviews) |
track404 | boolean | false | Reports a 404 event on error pages ([data-takt-404] / <meta name="takt:404"> marker, or a 404 HTTP status). |
tagged | boolean | false | Auto-tracks clicks on [data-takt-event] elements |
respectDnt | boolean | true | Respects Do Not Track |
excludeLocalhost | boolean | true | Ignores localhost / private IPs |
enabled | boolean | true | Master switch — false disables all tracking |
sampleRate | number | 1 | Fraction of sessions tracked (0–1) |
trackQuery | boolean | false | Keeps the query string in URLs |
queryParams | string[] | — | Parameters kept when trackQuery is false (allowlist) |
exclude | string[] | — | Path prefixes never tracked. Segment-bounded: /app covers /app and /app/… but not /application. |
scrubUrl | (url: string) => string | — | Transforms every URL before it is sent |
Config props are read once when <Takt> mounts — changing them afterwards has no effect; remount the component to reconfigure.
enabled, sampleRate, trackQuery, queryParams, exclude, scrubUrl) are available as props, with no separate core instance. Only debug is not exposed: to enable it, instantiate the core yourself with createTakt() — and do not use <Takt> at the same time, or you will double your pageviews.useTakt() never throws: called outside a <Takt>, during SSR, or before <Takt> has mounted, it returns a no-op instance that sends nothing over the network and warns once in the console (useTakt() called before <Takt> mounted). That message is the symptom of the pitfall above.
createTaktEvent() + <TaktEvent>
For declarative click tracking, two ways that resolve the active instance at click time (no stale closure), with a fallback to the core’s default instance.
createTaktEvent() returns an { onClick } you spread onto any element:
import { createTaktEvent } from '@vskstudio/takt-solid'
export function BuyButton() {
const onBuy = createTaktEvent({
name: 'Purchase',
revenue: { amount: '29.00', currency: 'EUR' }
})
return <button {...onBuy}>Buy</button>
} <TaktEvent> wraps a single child and composes its existing onClick, so you can annotate an element without touching its handler:
import { TaktEvent } from '@vskstudio/takt-solid'
export function SignupCta(props: { onClick: () => void }) {
return (
<TaktEvent name="Signup" props={{ plan: 'pro' }}>
<button onClick={props.onClick}>Sign up</button>
</TaktEvent>
)
} The child’s original onClick still fires; tracking runs alongside it. The child must render an actual DOM element: otherwise tracking is disabled, with a console warning.
<TaktBadge> and <TaktEmbed> widgets
To display a site’s public stats (see Widgets), the package ships two drop-in components:
import { TaktBadge, TaktEmbed } from '@vskstudio/takt-solid'
export function Footer() {
return (
<>
<TaktBadge domain="example.com" variant="b" glyph="dash" lang="en" />
<TaktEmbed domain="example.com" theme="dark" lang="en" />
</>
)
} <TaktBadge> renders an <img> (loading="lazy", decoding="async"): variant is a, b or d, glyph is unplug, dash, off or eyeoff. <TaktEmbed> renders a 404×264 <iframe> by default, loading="lazy" and sandboxed: theme is light, dark or auto. Both accept lang (fr / en), a host prop to point at another Takt origin, and forward their remaining attributes to the rendered element. The re-exported createStats() client serves the same public stats as JSON.
<takt-analytics> web component
For plain HTML or a non-Solid framework, import the ./element subpath (auto-registered, bundles the core, no Solid runtime):
import '@vskstudio/takt-solid/element' <takt-analytics domain="example.com" outbound files></takt-analytics> Attributes mirror the props, in kebab-case:
- Flags (presence = on, value ignored):
outbound,files,track-404,tagged. - On by default, set
="false"(or="0") to turn them off:spa,respect-dnt,exclude-localhost. - Values:
domain,endpoint,script-origin,sample-rate, plusquery-paramsandexcludewhich take a comma-separated list.enabledandtrack-queryare only read when the attribute is present, and are turned off with="false"/="0".
<takt-analytics domain="example.com" spa="false" respect-dnt="false"></takt-analytics> files takes no extension list here, and scrubUrl has no equivalent: those are values only the <Takt> component receives as props.
<Takt> boots inside onMount and is guarded by Solid's isServer: nothing touches window/document on the server, and useTakt() returns the no-op during the SSR pass. Importing ./element on the server is a no-op while customElements does not exist.