Modifiers
Modifiers refine an assertion beyond "an element matching the selector exists." They're chained inside the attribute value using CSS-like bracket syntax after the selector:
fs-assert-updated='#count[text-matches=\d+]'
fs-assert-updated='#logo[src=/img/new.png][alt=New Logo]'
fs-assert-updated='.panel[classlist=active:true,hidden:false]'
fs-assert-added-success='.success[text-matches=Order #\d+]'
Multiple modifiers chain by concatenating bracket groups. All modifiers must satisfy for the assertion to pass.
Reserved modifiers
Reserved modifier keys have a fixed meaning that doesn't depend on the target element's attributes.
| Modifier | Description |
|---|---|
text-matches |
Text content matches a regex (partial match, unanchored) |
value-matches |
Form control .value property matches a regex (partial match, unanchored) |
checked |
Checkbox/radio .checked DOM property |
disabled |
Disabled state (.disabled + aria-disabled="true") |
focused |
Focus state (document.activeElement === el) |
focused-within |
Focus-within state (el.matches(':focus-within')) |
count |
Cardinality — count, count-min, count-max |
classlist |
Class presence check — active:true,hidden:false |
detail-matches |
CustomEvent event.detail filter — string equality on triggers, regex on emitted |
Attribute checks (unreserved keys)
Any bracket key that isn't reserved is treated as an attribute check. The key is the attribute name, the value is a regex full match (auto-anchored with ^(?:...)$). See attribute-check.md for the full rules.
fs-assert-updated='#logo[src=/img/new.png][width=100][alt=Logo]'
fs-assert-updated='.panel[data-state=active|ready]'
fs-assert-visible='button[aria-expanded=true]'
Anchoring — the load-bearing rule
text-matchesandvalue-matchesare partial match (unanchored). Pattern can match anywhere in the string. Use^exact$to anchor explicitly.- Attribute checks (unreserved keys) are full match (auto-anchored). Pattern must match the entire value.
This difference exists because text content is usually something like "3 items remaining" where a partial match (\d+) is what you want, while attribute values are usually exact tokens (data-state="active") where full match prevents accidental substring matches.
Self-referencing — check the element itself
Omit the selector before the [ to check the instrumented element rather than a descendant:
<div id="todo-count"
fs-assert-updated="[text-matches=\d+/\d+ remaining]">
An empty selector means "check this element." See self-referencing.md for the full pattern.
Multiple modifier chaining
Every bracket group must pass for the assertion to resolve:
fs-assert-updated='#logo[src=/img/new.png][width=100][alt=Logo]'
Three checks — src, width, alt — all must hold. Missing any one keeps the assertion pending until every modifier is satisfied or the timeout fires.
Common mistakes
- Using CSS attribute selectors inside modifier values. The bracket parser treats
[as a modifier delimiter.[data-id="123"] .btn[disabled=true]is misparsed. Use id or class selectors in the selector part and put attribute checks in modifier brackets. - Using
text-matcheswith a literal dynamic value. Use a regex:[text-matches=\d+]not[text-matches=42]. The latter looks for the literal string "42" which works for static content but breaks the moment the value changes. - Using
countwith self-referencing (no selector). Count of self is always 1.countrequires an explicit selector that matches a collection. - Using
value-matchesand expecting MutationObserver to detect.valuechanges. The.valueproperty is not an HTML attribute — typing doesn't triggerMutationObserver. Pair it with event triggers likechangeorblur, not withupdated. - Using
value-matcheson a checkbox to read its state.checkbox.valuereturns the staticvalueattribute, not the checked state. Usechecked=truefor checkbox/radio state.