# The Selectors API: querySelector and querySelectorAll

> Learn how to select DOM elements with querySelector and querySelectorAll, scope a search, loop over results, and safely escape dynamic values.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2018-03-13 | Updated: 2026-07-18 | Topics: [Web Platform](https://flaviocopes.com/tags/platform/) | Canonical: https://flaviocopes.com/selectors-api/

The DOM gives us two methods that accept CSS selectors:

- `querySelector()` returns the first matching element
- `querySelectorAll()` returns every matching element

You can call them on `document`, an element, or a document fragment.

If you want to test a selector first, try the [CSS selector tester](https://flaviocopes.com/tools/css-selector-tester/).

## Select the first matching element

Use `querySelector()` when you need one element:

```js
const heading = document.querySelector('h1')
```

It returns an `Element` or `null` when nothing matches:

```js
const form = document.querySelector('#signup')

if (form) {
  form.classList.add('ready')
}
```

The method returns the first match in document order.

## Select every matching element

Use `querySelectorAll()` when several elements can match:

```js
const buttons = document.querySelectorAll('button')
```

It returns a **static `NodeList`**. The list does not update when matching elements are later added or removed.

You can loop over it with `forEach()`:

```js
buttons.forEach((button) => {
  button.disabled = true
})
```

Or use `for...of`:

```js
for (const button of buttons) {
  button.disabled = true
}
```

Convert the result to an array when you need array methods that `NodeList` does not provide:

```js
const labels = [...document.querySelectorAll('.label')]
```

## Use CSS selectors

Both methods accept valid CSS selector strings.

Select by ID:

```js
document.querySelector('#signup')
```

Select by class:

```js
document.querySelectorAll('.card')
```

Select by attribute:

```js
document.querySelectorAll('input[required]')
```

Select with a combinator and a pseudo-class:

```js
document.querySelectorAll('nav > a:not([aria-current])')
```

You can also pass a comma-separated selector list:

```js
document.querySelectorAll('button, input, select')
```

CSS pseudo-elements such as `::before` do not represent DOM elements, so they do not produce matches.

An invalid selector throws a `SyntaxError`. It does not return `null`.

## Search inside an element

Call the methods on an element to limit the search to its descendants:

```js
const menu = document.querySelector('#main-menu')
const links = menu.querySelectorAll('a')
```

Use `:scope` when the selector should refer to the element itself as the search root:

```js
const directItems = menu.querySelectorAll(':scope > li')
```

Without `:scope`, a selector can match based on ancestors outside the element before returning descendants from inside it. `:scope` makes the intended boundary clear.

## Escape dynamic values

HTML `id` and `class` values do not have to be valid CSS identifiers.

If a value becomes part of a selector, escape it with `CSS.escape()`:

```js
const id = 'question?1'
const element = document.querySelector(`#${CSS.escape(id)}`)
```

Do not place an arbitrary string into a selector without escaping it.

When you already know an ID and do not need selector syntax, `getElementById()` is even clearer:

```js
const element = document.getElementById('signup')
```

See the MDN references for [`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector), [`querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll), and [`CSS.escape()`](https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape_static).
