DEVELOPER DOCUMENTATION

Svelte API reference

Provider, element-feedback hook, scoped wrapper, callbacks, and shared guard options for Svelte 5.

COMPONENT · PROVIDER

ActivationGuardProvider

Provides one guard to descendant components through Svelte context and attaches it to the rendered subtree after the first render. When root is omitted, the provider creates a display: contents wrapper; pass an element or document as root to attach to an existing boundary.

Changing a guard configuration option recreates the guard and resets in-flight interaction observations. Keep configuration stable during a press; callback changes are picked up without recreating the guard.

<script lang="ts">
  import { ActivationGuardProvider, type Decision } from '@sparshlabs/svelte'
  import ApplicationShell from './ApplicationShell.svelte'

  function handleDecision(decision: Decision) {
    if (!decision.allowed) {
      console.info('Suspicious activation:', decision.reason)
    }
  }
</script>

<ActivationGuardProvider mode="enforce" cooldownMs={500} onDecision={handleDecision}>
  <ApplicationShell />
</ActivationGuardProvider>

The guard is created in an effect, so it never runs during server-side rendering. The provider works unchanged in SvelteKit and attaches once the page hydrates.

Props

PropTypeDefault / behavior
childrenSnippetSubtree to observe (the component's content).
modeMode | Partial<Record<PolicyId, Mode>>'enforce'. Unspecified policies in a mode map remain enforced.
policiesPolicyId[]All four policies by default.
cooldownMsnumber500. Age policy threshold in milliseconds.
rectThresholdPxnumber4. Continuity movement tolerance.
refractoryMsnumber300. DoubleFire recency window.
semanticsTextCheckbooleanfalse. Also compare target text.
clickSuppressMsnumber500. Matching native click window.
onDecision(decision: Decision) => voidCalled for allowed and suspicious decisions.
onSuspect(decision: Decision) => voidCalled when a policy verdict is suspicious, including report mode.
onBlocked(element: Element) => voidCalled after an enforced activation is canceled; replaces the default shake.
blockAnimationbooleantrue. Enables the default shake when no custom callback is set.
blockAnimationMsnumber300. Default shake duration.
rootElement | DocumentOptional explicit DOM root.

HOOK · ELEMENT FEEDBACK

useActivationGuard()

Call it in a descendant component's <script>. It returns an attachment and an isGuarded accessor that becomes true when the element is actually blocked.

<script lang="ts">
  import { useActivationGuard } from '@sparshlabs/svelte'

  const { ref, isGuarded } = useActivationGuard()
</script>

<button {@attach ref} type="button">Save draft</button>
{#if isGuarded()}
  <p role="status">The screen changed. Try again.</p>
{/if}
  • Attach the returned ref to the element: <button {@attach ref}>.
  • isGuarded is a reactive accessor; call it (isGuarded()) in markup, $derived, or $effect. It resets after an allowed activation. Destructuring the result keeps it reactive.
  • Like getContext, it must be called while the component initialises, not inside an event handler.
  • Without a provider ancestor, the hook is a no-op and does not throw.

COMPONENT · SCOPED GUARD

ActivationGuard

This is an alias of ActivationGuardProvider with the same props. A nested instance installs a separate guard on its subtree; options are not merged with an ancestor.

<ActivationGuard mode={{ age: 'report' }}>
  <FrequentlyUpdatedPanel />
</ActivationGuard>

Nesting providers: each provider installs its own capture-phase listeners and policy pipeline — nothing is merged or inherited from an ancestor. When providers overlap the same DOM, the outermost one sees pointer/click/key events first and, if it blocks, the inner provider never sees that interaction at all. This is exactly how <ActivationGuard> is meant to be used for a scoped override, but accidentally wrapping the same subtree with two top-level ActivationGuardProviders is usually a mistake. sparsh logs a console.warn whenever a new guard's root overlaps one that's already active, so you can tell the two cases apart — ignore it for an intentional scoped override, fix it otherwise.

BLOCK FEEDBACK

Customize the blocked animation

onBlocked runs after cancellation and receives the interactive element. A callback exception is ignored so it cannot affect the guard’s decision.

<script lang="ts">
  import { ActivationGuardProvider } from '@sparshlabs/svelte'
  import ApplicationShell from './ApplicationShell.svelte'

  function animateBlocked(element: Element) {
    element.animate(
      [
        { transform: 'scale(1)' },
        { transform: 'scale(0.96)' },
        { transform: 'scale(1)' },
      ],
      { duration: 220, easing: 'ease-out' },
    )
  }
</script>

<ActivationGuardProvider onBlocked={animateBlocked}>
  <ApplicationShell />
</ActivationGuardProvider>

RELATED TYPES

Decision and mode

Mode, PolicyId, and Decision are re-exported from @sparshlabs/svelte. A decision’s allowed field records the policy verdict; enforced says whether it actually stopped the activation. See Core concepts for policy behavior and accessibility details.

import type { InputKind, Phase, PolicyId } from '@sparshlabs/svelte'
import type { Attachment } from 'svelte/attachments'

export interface UseActivationGuardResult {
  ref: Attachment<Element>
  isGuarded: () => boolean
}

export interface Decision {
  allowed: boolean
  enforced: boolean
  policy?: PolicyId
  reason?: string
  event: { kind: InputKind; phase: Phase }
  target: {
    identity: unknown
    ageMs: number
    guardKey?: string
  }
}