fs-trigger says when an assertion starts watching for its expected outcome. Every instrumented element needs exactly one trigger (unless it's an OOB assertion — see oob).
Does the element appear, disappear, or load on its own? Use a lifecycle trigger: mount, unmount, load, error.
Should this fire continuously without any user action? Use invariant for perpetual monitoring, or event:<name> to hook into a custom event your app dispatches.
Custom event fires and event.detail.<key> matches (shallow string equality)
Placement rules
Attributes go on the trigger element — the element the user directly interacts with. Clicks on descendants (icon spans inside a button, text inside a label) resolve up to the nearest fs-trigger ancestor via closest(), so nested content works naturally.
For mount/unmount: place on the element being observed.
For load/error: place on the media element itself.
One trigger per element.fs-trigger accepts exactly one value. Need multiple? Split into multiple elements or use OOB assertions.
Multiple assertion types on one element are fine. Each creates a separate assertion with the same trigger and key.
Common mistakes
Placing fs-trigger="click" on a container <div> that wraps multiple unrelated children. It will fire for ANY click inside it, producing noisy assertions. Put the trigger on the specific element the user is meant to interact with.
Using mount where invariant is correct.mount fires once when the element enters the DOM. invariant continuously monitors while the element exists. For "this should always be visible" use invariant.
Expecting keydown to fire on a container. Keyboard events fire on the focused element. Put fs-trigger="keydown:Escape" on the focusable element (input, button, or an element with tabindex).