Element Selectors
Element selectors let a script describe the interface element it needs. Their syntax is similar to CSS: start with an element type, then narrow the result by title, state, or hierarchy.
const buttons = root.queryElements('Button[title="Save"]');In most cases, begin by selecting the target in Element Menu Mode. Open the Copy submenu and choose Copy Selector. Paste that result into your script, then simplify it for the way you plan to use it.
Start with a Copied Selector
Section titled “Start with a Copied Selector”Cursor Crane generates a selector that aims to identify the current element precisely. Interface titles, list contents, and web text can change, though. After copying a selector, run it once and remove unstable parts until only useful distinguishing conditions remain.
For example, this selector is very specific:
Window[title="Project Alpha"] Group Button[title="Run"]If the project name changes often and the Run button in its group is already distinctive, you can simplify it to:
Group Button[title="Run"]Common Patterns
Section titled “Common Patterns”| Goal | Pattern | Example |
|---|---|---|
| Find by element type | Button, TextField, List | Button |
| Find by title | [title="..."] | Button[title="Save"] |
| Find by identifier | #id | Button#save-button |
| Find by accessibility role without a Kind | [role="AX..."] | [role="AXWebArea"] |
| Find by state | :enabled, :focused | TextField:focused |
| Find any element | * | *[visible=true] |
Supported Kinds
Section titled “Supported Kinds”The built-in Kind names are Application, Unknown, Popover, Group, Button, TextField, Cell, Image, Toolbar, ScrollArea, OverflowedWebContent, List, Link, and Draggable. A Kind is Cursor Crane’s internal classification and does not necessarily correspond to a single accessibility role. In a selector’s type position, a token can match either the element’s Kind or its accessibility role; accessibility roles may be written with or without the AX prefix, so Button matches AXButton.
Not every accessibility element has one of those Kinds. You can use any raw accessibility role in the type position, such as WebArea or AXWebArea, or omit the type entirely and filter by role:
WebAreaAXWebArea[role="AXWebArea"]Use * when you want an explicit wildcard, for example *[role="AXWebArea"]. Start with a copied selector when you are not sure which Kind or role to use.
Express Hierarchy
Section titled “Express Hierarchy”A space means “anywhere inside,” > means “directly inside,” and a comma means “match either”:
Window ButtonWindow > ButtonButton[title="Save"], Button[title="Cancel"]Window Buttonfinds buttons at any depth inside a window.Window > Buttonfinds only direct child buttons of a window.- The final line finds both Save and Cancel buttons.
Filter by Text and Attributes
Section titled “Filter by Text and Attributes”Attribute conditions support exact, starts-with, ends-with, and contains matching:
| Pattern | Meaning | Example |
|---|---|---|
= | Matches exactly | Button[title="Save"] |
^= | Starts with | StaticText[value^="Error"] |
$= | Ends with | TextField[title$="name"] |
*= | Contains | Button[label*="Continue"] |
Supported Attributes
Section titled “Supported Attributes”| Attribute | Also accepts | Matches |
|---|---|---|
id | identifier; #id shorthand | The accessibility identifier, or the DOM identifier when no accessibility identifier is present. |
domId | domIdentifier | The DOM identifier. |
role | — | The raw accessibility role, such as AXButton. |
subrole | — | The raw accessibility subrole. |
title | — | The element title. |
label | description | The accessibility label or description. |
value | — | The element value. |
type | — | The element type. |
enabled | — | Whether the element is enabled. |
visible | — | Whether the element is visible. |
hidden | — | Whether the element is hidden. |
focused | — | Whether the element has focus. |
domClass | — | The DOM class tokens; see below. |
Wrap text containing spaces in single or double quotes.
Button[enabled=true]TextField[focused=true]StaticText[value*="Completed"]Match DOM Identifiers and Classes
Section titled “Match DOM Identifiers and Classes”domId (also spelled domIdentifier) and domClass are special DOM attributes. They are normally available only for content exposed by a browser, webview, or Electron app; native macOS controls generally do not provide them. Use them when a copied selector shows that the attribute is available.
*[domId="compose-button"]Button[domIdentifier="compose-button"]domClass exposes a DOM class list. Use it as an explicit attribute; CSS-style class shorthand is not supported.
Button[domClass="primary active"]Button[domClass*="primary active"]=requires exactly the same set of DOM class tokens. Their order does not matter.*=requires the element to contain every listed DOM class token.Button.primaryandButton[class="primary"]are invalid forms; useButton[domClass="primary"]instead.
DOM class values are split on whitespace before matching, so menu does not match a menu-item class. Copied selectors include domId and domClass when those attributes are available on the selected element.
Choose a Result by Position
Section titled “Choose a Result by Position”| Pattern | Meaning |
|---|---|
:first | Takes the first matching result. |
:nth(n) | Takes the nth result, starting at 1. |
:enabled / :disabled | Keeps enabled or disabled elements only. |
:visible / :hidden | Keeps visible or hidden elements only. |
:focused | Keeps the currently focused element only. |
List Row:nth(2) Button:firstThis finds the second row in a list, then takes the first button in that row.
Query Visible Content
Section titled “Query Visible Content”Element queries are not especially cheap: queryElements() traverses children and can block noticeably for a large accessibility hierarchy. Avoid repeating the same query. When several targets share a parent, query that parent once, then query each target from the parent instead of searching the entire window every time. For long lists, tables, and scroll areas, queryVisibleElement() is better when you only care about the content a person can see right now:
const visibleButtons = root.queryVisibleElement('Button:enabled', 50);
const toolbar = root.queryElements('Toolbar[title="Formatting"]', 1)[0];const boldButton = toolbar?.queryElements('Button[title="Bold"]', 1)[0];const italicButton = toolbar?.queryElements('Button[title="Italic"]', 1)[0];Both methods accept an optional second argument that limits how many results they return.
Use a Selector in a Script
Section titled “Use a Selector in a Script”Find the window first, then search from that window. The bundled example.element-query.js demonstrates the complete flow:
const application = ctx.elementInspector.getElementByPid(window.application.pid);const root = application?.getWindows().find( element => element.windowId === window.windowId);
const matches = root?.queryVisibleElement('Button[enabled=true]', 50) ?? [];Querying elements requires "accessibilityAPI" in meta.permissions. After finding an element, you can highlight it, move to it, set its value, or trigger one of its supported actions. See Script API for details.