DEVELOPER DOCUMENTATION

SolidJS API reference

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

COMPONENT · PROVIDER

ActivationGuardProvider

Provides one guard to descendant components through Solid 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.

import { ActivationGuardProvider, type Decision } from '@sparshlabs/solidjs'

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

export function App() {
  return (
    <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 SolidStart and other SSR setups and attaches once the page hydrates.

Props

PropTypeDefault / behavior
childrenJSX.ElementSubtree to observe.
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 from a descendant component. It returns a ref callback and an isGuarded accessor that becomes true when the element is actually blocked.

import { Show } from 'solid-js'
import { useActivationGuard } from '@sparshlabs/solidjs'

export function SaveDraftButton() {
  const { ref, isGuarded } = useActivationGuard()

  return (
    <>
      <button ref={ref} type="button">Save draft</button>
      <Show when={isGuarded()}>
        <p role="status">The screen changed. Try again.</p>
      </Show>
    </>
  )
}
  • Pass the returned ref to the element’s ref attribute: <button ref={ref}>.
  • isGuarded is a signal accessor; call it (isGuarded()) in JSX or an effect. It resets after an allowed activation.
  • 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.

import { ActivationGuardProvider } from '@sparshlabs/solidjs'

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

export function App() {
  return (
    <ActivationGuardProvider onBlocked={animateBlocked}>
      <ApplicationShell />
    </ActivationGuardProvider>
  )
}

RELATED TYPES

Decision and mode

Mode, PolicyId, and Decision are re-exported from @sparshlabs/solidjs. 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/solidjs'
import type { Accessor } from 'solid-js'

export interface UseActivationGuardResult {
  ref: (node: Element) => void
  isGuarded: Accessor<boolean>
}

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