Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use an ID selector by putting # immediately before the element’s exact id value: #demo. In JavaScript, pass that selector to document.querySelector(), or use the ID-specific document.getElementById('demo'). IDs are case-sensitive, should be unique in the document, and must be escaped when their value is not a valid CSS identifier.

The basic ID selector

An ID selector matches an element whose id attribute has the specified value. The hash is selector syntax; it is not part of the HTML attribute value.

<button id="save-button">Save</button>

#save-button {
  border: 2px solid red;
  background: white;
}

The selector must match the value exactly. #save-button does not match id="Save-Button" because ID matching is case-sensitive.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Combine an ID with another selector

Place a type selector or universal selector before the ID in a compound selector. This narrows the match without changing the ID requirement.

p#summary {
  font-size: 1.5rem;
}

section#settings {
  padding: 1rem;
}

*#app {
  min-height: 100vh;
}

In normal markup, the ID alone is preferable because it is clear and already specific. Add a type or class condition only when you genuinely need to distinguish elements.

Selecting an ID in JavaScript

querySelector()

document.querySelector() accepts any valid CSS selector and returns the first matching element, or null when there is no match.

const panel = document.querySelector('#settings');

if (panel) {
  panel.hidden = false;
}

Because the argument is a CSS selector string, the leading # is required. A missing element is not an exception; it produces null, so check the result before reading properties or calling methods on it.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

getElementById()

document.getElementById() is the direct ID-specific alternative. Pass only the ID value, without a hash.

const panel = document.getElementById('settings');

if (panel) {
  panel.hidden = false;
}

For a normal ID, getElementById('settings') and querySelector('#settings') identify the same element. The former communicates that you are looking up an ID; the latter is useful when the selector may later become more complex.

querySelectorAll()

document.querySelectorAll() returns a collection of every match. It is useful for auditing duplicate IDs or operating on a selector that intentionally matches more than one element.

const matches = document.querySelectorAll('#legacy-widget');

matches.forEach((element) => {
  element.classList.add('found');
});

Do not use duplicate IDs as a normal design. An ID is intended to identify one element, and duplicates make CSS, scripts, fragment links and accessibility relationships ambiguous.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

CSS selector versus JavaScript lookup

Question ID selector in CSS querySelector() getElementById()
Primary purpose Style matching elements Retrieve the first element matching any CSS selector Retrieve an element by its ID value
Input #id, optionally combined with other selectors A complete CSS selector string, such as #id The ID value only, such as id
No match No rule is applied Returns null Returns null
Multiple matches Can match every element carrying that value Returns the first match in depth-first document order Designed for one ID; duplicate markup is invalid practice
Selector flexibility Supports CSS selector combinations Supports CSS selector combinations ID lookup only

IDs that contain numbers or punctuation

HTML permits ID values that are not valid CSS identifiers. An ID beginning with a number or containing punctuation can therefore work in markup but fail as an unescaped selector.

Escape a dynamic ID with CSS.escape()

When an ID comes from data, a URL, a database or user input, escape it before interpolating it into a selector.

const id = 'item:42';
const element = document.querySelector(`#${CSS.escape(id)}`);

if (element) {
  element.scrollIntoView();
}

CSS.escape() protects punctuation, whitespace and leading characters that have special meaning in CSS. It also prevents malformed input from turning into a different selector. Do not concatenate an untrusted ID directly into querySelector().

Escaping a literal CSS rule

If the value is known when you write the stylesheet, escape the invalid character or leading digit in the selector.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
/* id="item?one" */
#item\?one {
  color: crimson;
}

/* id="123item" */
#\00003123item {
  color: navy;
}

An invalid, unescaped selector is ignored by CSS. In JavaScript, passing an invalid selector to querySelector() throws a SyntaxError instead of returning null, so escaping is part of input validation.

Uniqueness, case and document scope

Keep IDs unique

Use each ID once per document. Duplicate values can cause a CSS rule to affect several elements, while querySelector() returns only the first one in document order. If you need a reusable hook for a group, use a class and select it with .class-name.

<article class="card">...</article>
<article class="card">...</article>

Match case exactly

<div id="Profile"></div>

/* Does not match: */
#profile { ... }

/* Matches: */
#Profile { ... }

Choose the correct document

document.getElementById() searches the document. If the element is inside a component’s shadow root, query that shadow root instead:

const host = document.querySelector('#widget-host');
const shadowPanel = host?.shadowRoot?.querySelector('#panel');

A selector run on the main document cannot see nodes encapsulated in a shadow root. Likewise, querying an element before its markup exists returns null; run the code after the relevant HTML has been parsed or after the component has rendered.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Reliable patterns for real code

Wait until the DOM exists

document.addEventListener('DOMContentLoaded', () => {
  const form = document.getElementById('signup-form');
  if (!form) return;

  form.addEventListener('submit', (event) => {
    event.preventDefault();
  });
});

Scripts placed at the end of <body>, or scripts marked defer, also run after the document has been parsed. Choose one loading strategy and still handle a missing element when templates can vary.

Use a selector when you need a relationship

const invalidEmail = document.querySelector(
  '#signup-form input[name="email"].invalid'
);

This is where querySelector() earns its flexibility: it can combine an ID with descendants, attributes, classes and state selectors. Use getElementById() first when the ID alone is the complete requirement.

Do not confuse an ID with a fragment URL

A link such as <a href="#details"> navigates to the element whose ID is details. The same value can be used in CSS as #details, but the contexts are different: the URL uses a fragment, while CSS and querySelector() use selector syntax.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting checklist

  • No style appears: verify that the HTML says id="...", the stylesheet uses #..., spelling and case match, and the stylesheet is loaded.
  • querySelector() returns null: confirm the code runs after the element is created, that you included the hash, and that the selector is scoped to the correct document or shadow root.
  • getElementById() returns null: pass the raw ID without #; for example, use getElementById('demo'), not getElementById('#demo').
  • querySelector() throws SyntaxError: the selector is invalid, often because an ID contains punctuation or starts with a digit. Build it with CSS.escape().
  • The wrong element is changed: inspect the document for duplicate IDs. querySelector() deliberately returns the first match.
  • A rule seems ignored: inspect the selector in developer tools. An unescaped invalid ID selector is discarded by the CSS parser; also check whether another rule wins through the cascade.
  • A dynamic component is missed: query after rendering, or use event delegation on a stable ancestor when elements are inserted later.

Performance and maintainability

For one known ID, getElementById() states intent directly. querySelector() is the better fit when you already have a compound selector or need to change the condition without rewriting the lookup API. There is no meaningful universal speed difference, so choose based on clarity and selector requirements rather than an assumed benchmark.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Cache a reference when the same element is used repeatedly, avoid querying in tight loops when a single lookup will do, and keep IDs stable when they are part of JavaScript, CSS, tests or deep links. Use classes for repeated styling and data attributes for behavior hooks when uniqueness is not required.

Or skip the browser setup

If your goal is to capture a page or a selected element rather than manipulate it in your own browser code, ScreenshotNeo provides a website screenshot API and MCP server. It can capture a full page or one CSS-selected element, load lazy images, apply custom CSS or JavaScript, wait for a selector, and return PNG, JPEG, WebP or PDF. Before capture it accepts consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled.

Use a GET request (see the ScreenshotNeo API documentation):

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://example.com 
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and response headers report the page verdict and whether it was billed (X-Page-Verdict and X-Billed). An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Free plan includes 1,000 shots per month without a card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan, and yearly billing provides two months free. Create a free ScreenshotNeo account to try it with no card.

Practical decision guide

  • Choose a CSS #id rule when you are styling one uniquely identified element.
  • Choose getElementById() when JavaScript needs one known ID.
  • Choose querySelector() when you need a compound CSS selector.
  • Choose querySelectorAll() only when multiple matches are intentional or you are diagnosing duplicate IDs.
  • Escape every dynamic ID with CSS.escape() before constructing a selector.
  • Fix duplicate IDs and timing problems instead of compensating with increasingly complex selectors.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.