Skip to content

useBetterDomTraversing (JavaScript)

Language JavaScript (and super languages)
biome.json
{
"linter": {
"rules": {
"nursery": {
"useBetterDomTraversing": "error"
}
}
}
}

Prefer modern DOM traversal APIs over positional indexes and chained walks.

Named first-child accessors, querySelector(), and closest() describe intent more clearly than childNodes[0], children[n], and repeated .parentElement access. Merging chained .querySelector() calls with static selectors has the same benefit.

Fixes are unsafe because the replacement is not always equivalent:

  • .childNodes[0] is undefined when empty; .firstChild is null
  • .closest() looks for any matching ancestor, not an exact number of .parentElement hops
  • chained .querySelector() calls search inside the first match, while a combined selector searches from the original node

props.children is ignored because that is component data, not DOM traversal.

element.childNodes[0];
code-block.js:1:1 lint/nursery/useBetterDomTraversing  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ℹ This indexes .childNodes to get the first child.

> 1 │ element.childNodes[0];
│ ^^^^^^^^^^^^^^^^^^^^^
2 │

ℹ A positional index is harder to read than a named first-child accessor.

ℹ This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.

ℹ Unsafe fix: Use .firstChild instead.

1 │ - element.childNodes[0];
1 │ + element.firstChild;
2 2 │

element.children[0];
code-block.js:1:1 lint/nursery/useBetterDomTraversing  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ℹ This indexes .children to get the first element child.

> 1 │ element.children[0];
│ ^^^^^^^^^^^^^^^^^^^
2 │

ℹ A positional index is harder to read than a named first-child accessor.

ℹ This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.

ℹ Unsafe fix: Use .firstElementChild instead.

1 │ - element.children[0];
1 │ + element.firstElementChild;
2 2 │

element.children[2];
code-block.js:1:1 lint/nursery/useBetterDomTraversing ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ℹ This uses a positional index on .children.

> 1 │ element.children[2];
│ ^^^^^^^^^^^^^^^^^^^
2 │

ℹ A CSS selector describes which child you want without relying on a numeric index.

ℹ Replace this with .querySelector() and a selector for the child.

ℹ This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.

element.parentElement.parentElement;
code-block.js:1:1 lint/nursery/useBetterDomTraversing ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ℹ This walks ancestors by chaining .parentElement.

> 1 │ element.parentElement.parentElement;
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 │

ℹ .closest() looks up a matching ancestor without repeating .parentElement.

ℹ Replace this chain with .closest() and a selector for the ancestor.

ℹ This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.

element.querySelector("a").querySelector("b");
code-block.js:1:1 lint/nursery/useBetterDomTraversing  FIXABLE  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

ℹ These .querySelector() calls are chained.

> 1 │ element.querySelector("a").querySelector("b");
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 │

ℹ One .querySelector() call with a combined selector is easier to read.

ℹ This rule belongs to the nursery group, which means it is not yet stable and may change in the future. Visit https://biomejs.dev/linter/#nursery for more information.

ℹ Unsafe fix: Merge the .querySelector() calls.

1 │ - element.querySelector("a").querySelector("b");
1 │ + element.querySelector(":scope·a·b");
2 2 │

element.firstChild;
element.firstElementChild;
element.querySelector("li");
element.closest("form");
const child = props.children[0];