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
| Prop | Type | Default / behavior |
|---|---|---|
children | JSX.Element | Subtree to observe. |
mode | Mode | Partial<Record<PolicyId, Mode>> | 'enforce'. Unspecified policies in a mode map remain enforced. |
policies | PolicyId[] | All four policies by default. |
cooldownMs | number | 500. Age policy threshold in milliseconds. |
rectThresholdPx | number | 4. Continuity movement tolerance. |
refractoryMs | number | 300. DoubleFire recency window. |
semanticsTextCheck | boolean | false. Also compare target text. |
clickSuppressMs | number | 500. Matching native click window. |
onDecision | (decision: Decision) => void | Called for allowed and suspicious decisions. |
onSuspect | (decision: Decision) => void | Called when a policy verdict is suspicious, including report mode. |
onBlocked | (element: Element) => void | Called after an enforced activation is canceled; replaces the default shake. |
blockAnimation | boolean | true. Enables the default shake when no custom callback is set. |
blockAnimationMs | number | 300. Default shake duration. |
root | Element | Document | Optional 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
refto the element’srefattribute:<button ref={ref}>. isGuardedis 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
}
}