Element States
Acquiescence provides comprehensive element state detection that goes beyond simple presence checks. Understanding these states is key to effectively using the library.
Available States
Visibility States
visible
The element is visible on the page. An element is considered visible when:
- It has a positive width and height (> 0)
- The
visibilityCSS property is set tovisible - The
displayCSS property is notnone - The
opacityis greater than 0 - Element passes
checkVisibility()check
const result = await inspector.queryElementState(element, 'visible');
// result.matches: true if visible, false otherwise
// result.received: 'visible' or 'hidden'Special Cases
Elements with display: contents: Elements with display: contents are not rendered themselves, but their children are. Acquiescence checks if any child element is visible.
Text nodes: For elements with only text content, Acquiescence creates a range to check if the text has positive dimensions.
hidden
The inverse of visible. The element is not visible on the page.
const result = await inspector.queryElementState(element, 'hidden');
// result.matches: true if hidden, false otherwiseInteraction States
enabled
The element is not disabled and can be interacted with. Checks:
- Native
disabledattribute (for form elements) aria-disabled="true"on the element or any ancestor
const result = await inspector.queryElementState(button, 'enabled');
// result.matches: true if enabled, false if disabled
// result.received: 'enabled' or 'disabled'ARIA Disabled
Unlike the native disabled attribute, aria-disabled applies to all descendants in the accessibility tree. Acquiescence respects this by checking the entire ancestor chain.
disabled
The inverse of enabled. The element is disabled.
const result = await inspector.queryElementState(button, 'disabled');editable
The element can accept text input. For an element to be editable, it must be:
- Not disabled
- Not readonly
- One of:
- An
<input>,<textarea>, or<select>element - An element with
contenteditable="true" - An element with a role that supports
aria-readonly
- An
const result = await inspector.queryElementState(input, 'editable');
// result.matches: true if editable
// result.received: 'editable', 'disabled', or 'readOnly'WARNING
If you query editable on an element that cannot be editable (like a <div> without contenteditable), the state does not match: queryElementState() returns { matches: false, received: 'error:noteditable' }, and queryElementStates() returns { status: 'error', message: 'noteditable' }.
Checked States
checked, unchecked, and indeterminate
The element is checked, not checked, or in a mixed state. These states apply to checkboxes, radio buttons, and elements with a role allowing aria-checked, such as checkbox, radio, switch, or menuitemcheckbox. A native checkbox is indeterminate when its indeterminate property is set; an element with a role is indeterminate when aria-checked="mixed".
const result = await inspector.queryElementState(checkbox, 'checked');
// result.matches: true if checked
// result.received: 'checked', 'unchecked', or 'indeterminate'
// result.isRadio: true for a radio button, which clicking cannot uncheckFor any other element, the state does not match: received is 'error:notcheckable', and queryElementStates() returns { status: 'error', message: 'notcheckable' }.
Viewport States
inview
The element is currently visible within the viewport. Uses the Intersection Observer API to determine if the element intersects with the viewport.
An element is considered inview if:
- It intersects with the viewport
- It can be scrolled into view (not hidden by
overflow: hidden)
const result = await inspector.queryElementState(element, 'inview');
// result.matches: true if in viewport and scrollable
// result.received: 'inview', 'notinview', or 'unviewable'notinview (received state only)
The element is not currently in the viewport but could be scrolled into view. This is a received state value, not a queryable state.
unviewable (received state only)
The element cannot be scrolled into view because it's hidden by an ancestor with overflow: hidden. This is a received state value, not a queryable state.
const result = await inspector.queryElementState(element, 'inview');
if (result.received === 'unviewable') {
console.log('Element is hidden by overflow and cannot be scrolled to');
}Animation States
stable
The element's position has been stable for at least one animation frame. This is useful for ensuring elements aren't moving before interacting with them.
const result = await inspector.queryElementStates(element, ['visible', 'stable']);
if (result.status === 'failure' && result.missingState === 'stable') {
console.log('Element is still moving or animating');
}When to use stable
Use the stable state when:
- Elements are animating into view
- Dealing with lazy-loaded content that shifts the page
- Working with carousels or scrolling animations
- Ensuring precise click coordinates
Combining States
You can check multiple states at once using queryElementStates():
const result = await inspector.queryElementStates(
button,
['visible', 'enabled', 'stable', 'inview']
);
if (result.status === 'success') {
console.log('Button is fully ready for interaction!');
} else if (result.status === 'failure') {
console.log(`Button is missing: ${result.missingState}`);
}Common State Combinations
| Combination | Use Case |
|---|---|
['visible', 'enabled'] | Button ready to click |
['visible', 'enabled', 'editable'] | Input ready to type |
['visible', 'inview', 'stable'] | Element ready for screenshot |
['visible', 'enabled', 'stable', 'inview'] | Element ready for precise interaction |
State Check Order
When using queryElementStates(), states are checked in the order provided, with one exception:
Stability is checked first
The stable state is always checked first, regardless of its position in the array. This is because:
- Stability checking requires waiting for animation frames
- Other state checks are synchronous (or nearly so)
- If an element isn't stable, there's no point checking other states yet
// 'stable' is checked first, even though it's last in the array
await inspector.queryElementStates(element, ['visible', 'enabled', 'stable']);Helper Methods
For convenience, Acquiescence provides synchronous helper methods for common state checks:
isElementVisible(element: Element): boolean
Synchronously checks if an element is visible.
if (inspector.isElementVisible(element)) {
console.log('Element is visible');
}isElementDisabled(element: Element): boolean
Synchronously checks if an element is disabled.
if (!inspector.isElementDisabled(button)) {
button.click();
}isElementReadOnly(element: Element): boolean | 'error'
Synchronously checks if an element is readonly. Returns 'error' if the element type doesn't support readonly.
const readOnly = inspector.isElementReadOnly(input);
if (readOnly === true) {
console.log('Input is readonly');
} else if (readOnly === false) {
console.log('Input is editable');
} else {
console.log('Element does not support readonly');
}isElementScrollable(element: Element): boolean
Checks if an element can be scrolled into view.
if (!inspector.isElementScrollable(element)) {
console.log('Element is hidden by overflow: hidden');
}elementsContainText(elements: Element[], text: string): boolean[]
Checks, for each element, whether its rendered text contains a string, ignoring case and treating each run of whitespace as one space.
const items = Array.from(document.querySelectorAll('li'));
const matches = inspector.elementsContainText(items, 'In stock');
const inStock = items.filter((_, index) => matches[index]);elementsMatchAriaStates(elements: Element[], states: AriaStates): boolean[]
Checks, for each element, whether it has every given ARIA state: checked, pressed, expanded, selected, level, or disabled. A state that does not apply to an element does not match.
const [expanded] = inspector.elementsMatchAriaStates([menuButton], { expanded: true });findElementsByLabel(scopes, text: string, exact: boolean): Element[]
Finds the elements within some documents or elements whose labels match a text. An element's labels are the elements its aria-labelledby refers to; failing that, its aria-label; failing that, the label elements of a form control. Without exact, a label matches if it contains the text, ignoring case.
const [email] = inspector.findElementsByLabel([document], 'Email address', false);getElementLabels(element: Element): string[]
Gets the texts of an element's labels, as findElementsByLabel() matches them.
console.log(inspector.getElementLabels(input)); // ['Email address']findOpenShadowRoots(scopes): ShadowRoot[]
Finds the open shadow roots within some documents, elements, or shadow roots, including nested ones, so that a search can include them. Closed shadow roots cannot be found.
const scopes = [document, ...inspector.findOpenShadowRoots([document])];Next Steps
- Learn about Interaction Readiness
- See Examples
- Explore the API Reference