Skip to content

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.

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 Toolbar Button[title="Run"]

If the project name changes often and the Run button in the toolbar is already distinctive, you can simplify it to:

Toolbar Button[title="Run"]
GoalPatternExample
Find by element typeButton, TextField, ListButton
Find by title[title="..."]Button[title="Save"]
Find by identifier#idButton#save-button
Find by state:enabled, :focusedTextField:focused
Find any element**[visible=true]

You can usually use familiar names such as Button, TextField, StaticText, Window, List, Row, Table, and ScrollArea. Start with a copied selector when you are not sure which type to use.

A space means “anywhere inside,” > means “directly inside,” and a comma means “match either”:

Window Button
Window > Button
Button[title="Save"], Button[title="Cancel"]
  • Window Button finds buttons at any depth inside a window.
  • Window > Button finds only direct child buttons of a window.
  • The final line finds both Save and Cancel buttons.

Attribute conditions support exact, starts-with, ends-with, and contains matching:

PatternMeaningExample
=Matches exactlyButton[title="Save"]
^=Starts withStaticText[value^="Error"]
$=Ends withTextField[title$="name"]
*=ContainsButton[label*="Continue"]

Common attributes are title, label, value, enabled, visible, and focused. Wrap text containing spaces in single or double quotes.

Button[enabled=true]
TextField[focused=true]
StaticText[value*="Completed"]
PatternMeaning
:firstTakes the first matching result.
:nth(n)Takes the nth result, starting at 1.
:enabled / :disabledKeeps enabled or disabled elements only.
:visible / :hiddenKeeps visible or hidden elements only.
:focusedKeeps the currently focused element only.
List Row:nth(2) Button:first

This finds the second row in a list, then takes the first button in that row.

queryElements() traverses children and is useful when you want to search a complete window. It can block noticeably for a large accessibility hierarchy. 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);

Both methods accept an optional second argument that limits how many results they return.

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.