ScreenshotNeo

BlogHow-to

How to Select Elements by ID Using CSS Selectors

Learn ID selectors in CSS and JavaScript, escape unusual IDs, handle duplicates, and capture selected elements reliably.

By the ScreenshotNeo team29 September 20268 min read

How to Select Elements by ID Using CSS Selectors

Use a hash followed by the exact value of the element’s id attribute. For example, #demo selects <div id="demo"> in CSS. In JavaScript, use document.querySelector('#demo') when you need a CSS selector, or document.getElementById('demo') when you already have an ID value.

The important details are exact matching, case sensitivity, escaping values that are not valid CSS identifiers, and keeping IDs unique. This guide covers styling, JavaScript retrieval, dynamic IDs, duplicate IDs, troubleshooting, and a practical way to capture a selected element without maintaining a browser automation stack.

1. The direct answer: use #id

HTML assigns the identifier:

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

CSS selects it with a hash:

#save-button {
  background: #0b63ce;
  color: white;
  padding: 0.75rem 1rem;
  border: 0;
  border-radius: 0.4rem;
}

The CSS ID selector matches an element based on the value of its id attribute. The value must match exactly. A selector such as #Save-button does not match id="save-button", because IDs are case-sensitive.

You can also combine an ID with a type selector:

button#save-button {
  font-weight: 700;
}

p#introduction {
  max-width: 65ch;
}

A type selector comes before the ID selector in a compound selector. Use the extra type constraint only when it expresses a real requirement; #save-button is usually clearer and less brittle.

2. Selecting an ID in JavaScript

querySelector()

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

The same ID connects CSS styling and JavaScript retrieval.
The same ID connects CSS styling and JavaScript retrieval.
const saveButton = document.querySelector('#save-button');

if (saveButton) {
  saveButton.addEventListener('click', () => {
    console.log('Saved');
  });
}

Because it accepts CSS, you can extend the selector when necessary:

const enabledSaveButton = document.querySelector(
  'button#save-button:not([disabled])'
);

An invalid selector causes querySelector() to throw a SyntaxError. This is why dynamic IDs must be escaped before interpolation.

getElementById()

document.getElementById() is the direct ID-specific method. Pass the ID value without the hash:

const saveButton = document.getElementById('save-button');

For a normal ID, this is equivalent in purpose to document.querySelector('#save-button'). The APIs differ in what they accept: getElementById() takes only an ID value, while querySelector() takes a complete CSS selector.

querySelectorAll()

querySelectorAll() returns all elements matching a selector. IDs are intended to be unique, but this method is useful for diagnosing invalid markup with duplicate IDs:

const matches = document.querySelectorAll('#save-button');
console.log(matches.length);

By contrast, querySelector('#save-button') returns only the first match in depth-first document order.

3. CSS selector versus JavaScript method

Need Use Input Result
Style an element #id in CSS CSS selector All matching elements are styled
Retrieve with a CSS condition querySelector() Any valid CSS selector First element or null
Retrieve by ID only getElementById() ID value, without # The matching element or null
Inspect every match querySelectorAll() Any valid CSS selector A collection of all matches

Choose getElementById() when the ID is already a plain, trusted string and no additional selector logic is needed. Choose querySelector() when you need to combine the ID with a type, state, attribute, or descendant selector.

4. Escape IDs that contain punctuation or start with a number

HTML permits ID values that are not valid CSS identifiers. For example:

<div id="item:42">Details</div>

This is not safe to interpolate directly into a CSS selector:

// Can throw SyntaxError because ':' has selector meaning.
const item = document.querySelector('#item:42');

Use CSS.escape() for dynamic or unusual values:

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

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

A literal CSS rule needs an escaped character. For example, an ID containing a question mark can be written as #item\\?one. An ID beginning with digits can be escaped as #\\00003123item. In JavaScript string literals, remember that the backslash itself must be escaped when needed.

const numericId = '123item';
const element = document.querySelector(`#${CSS.escape(numericId)}`);

When you control the markup, prefer simple IDs made from letters, digits, hyphens, and underscores. When you do not control it, always escape the value before building a selector.

5. Exact matching, case, and duplicate IDs

Exact values

#profile matches id="profile", not id="profile " with a trailing space and not id="Profile". Check the rendered DOM in browser developer tools rather than relying on a template variable that may have been transformed.

IDs should be unique

An ID is intended to identify one element in a document. Duplicate IDs make CSS and JavaScript behavior ambiguous. CSS can match every element carrying the same value, while querySelector() returns the first match in document order.

<div id="card">First</div>
<div id="card">Second</div>
document.querySelectorAll('#card').length; // 2
document.querySelector('#card').textContent; // "First"

Fix the markup by assigning unique IDs or use a class when multiple elements intentionally share a style or behavior.

6. Practical patterns

Toggle an element by ID

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

toggle?.addEventListener('click', () => {
  if (panel) panel.hidden = !panel.hidden;
});

Style a selected element with a class

const status = document.querySelector('#status');
status?.classList.add('is-ready');

Select an ID inside a component root

const dialog = document.querySelector('#account-dialog');
const closeButton = dialog?.querySelector('#close-dialog');

Use a scoped query when the component structure is known. If IDs are globally unique, querying the document directly is simpler.

Read and update content safely

const heading = document.getElementById('page-heading');
if (heading) {
  heading.textContent = 'Updated heading';
}

textContent treats the value as text. Use innerHTML only when you intentionally need to insert trusted markup.

7. Capturing an element selected by ID

If your goal is a screenshot of one element, the same selector can be passed to a browser automation tool that supports element capture. A typical workflow is:

  1. Load the page and wait until the target element exists.
  2. Use #id as the CSS selector.
  3. Capture the element rather than the entire viewport.
  4. Escape the ID when it is dynamic or contains punctuation.

For a page containing <section id="pricing">, the selector is simply #pricing. If the ID is plan:pro, construct it with CSS.escape('plan:pro') before passing it to the capture call.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Its element capture option accepts a CSS selector, so you can request the element selected by ID without installing or managing a browser.

A clean capture removes common overlays before selecting the target element.
A clean capture removes common overlays before selecting the target element.

See the ScreenshotNeo API documentation for the complete parameter list and response details.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  --data-urlencode selector='#pricing' \
  -o pricing.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://stripe.com",
        "selector": "#pricing",
    },
    timeout=90,
)
r.raise_for_status()
open("pricing.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com',
  selector: '#pricing',
});

const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('pricing.webp', image));

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan. Create a free ScreenshotNeo account.

9. Troubleshooting

Symptom Likely cause Fix
No CSS styles apply The selector does not exactly match the ID, or the stylesheet is not loaded. Inspect the rendered id, check capitalization and whitespace, and verify the stylesheet request.
querySelector() throws SyntaxError The ID contains punctuation or starts with a value that is invalid in a CSS identifier. Use CSS.escape(id) before interpolation.
JavaScript returns null The element is not in the current document yet, the ID is misspelled, or the element is inside another document. Run the query after the markup exists and inspect the live DOM. For an iframe, query its document after it loads.
The wrong element is returned Duplicate IDs exist. Make IDs unique or use a class and a more specific selector.
An element screenshot is blank The selector did not resolve before capture, or the page had not finished rendering. Wait for the selector, add a short delay or network-idle wait, and confirm the selector in the browser.
Dynamic IDs work sometimes The ID changes between renders or contains unescaped characters. Build the selector from the current value and call CSS.escape(); prefer a stable data attribute when you control the page.

10. Performance, reliability, and cost notes

For a single known ID, getElementById() communicates the intent directly. The practical performance difference from querySelector('#id') is usually less important than querying at the correct time and avoiding repeated work in hot event loops. Cache a reference when the same element is used repeatedly.

const status = document.getElementById('status');

for (const message of messages) {
  if (status) status.textContent = message;
}

Reliability comes from stable identifiers, unique markup, escaping untrusted values, and waiting for the element’s lifecycle. In server-rendered or client-rendered pages, attach behavior after the relevant DOM has been created.

For automated screenshots, full-page captures may require lazy images to load and can take longer than an element capture. A selector wait is more deterministic than an arbitrary long delay. ScreenshotNeo also supports custom CSS and JavaScript, request blocking, headers, cookies, user agents, timezone, geolocation, caching with a chosen TTL, retries through asynchronous jobs, signed webhooks, and bulk capture of up to 100 URLs per call. Use only the options your page needs so requests remain easy to diagnose.

ScreenshotNeo charges only for clean shots. Cache hits and unsuccessful page outcomes such as bot checks, blank pages, timeouts, and failed loads cost nothing. The response headers let you record the verdict and billing result alongside your asset pipeline.

11. A short checklist

  • Write the selector as #exact-id.
  • Keep every ID unique and case-sensitive.
  • Use getElementById('exact-id') for direct lookup.
  • Use querySelector('#exact-id') when combining CSS conditions.
  • Use querySelectorAll() to detect duplicate matches.
  • Escape dynamic values with CSS.escape().
  • Wait until the target element exists before querying or capturing it.

12. FAQ

Should I include the hash in getElementById()?

No. Pass only the ID value: getElementById('demo'). The hash is part of the CSS selector syntax used by querySelector() and stylesheets.

Can an ID selector match more than one element?

It can if the document contains duplicate IDs. That markup violates the intended uniqueness of IDs. CSS may style all matches, while querySelector() returns the first.

Why does #123item fail?

A leading number can make the value invalid as an unescaped CSS identifier. Use CSS.escape('123item') when constructing the selector.

When should I use a class instead?

Use a class when several elements share the same style or behavior. Reserve IDs for unique elements that need a stable reference, fragment target, label association, or script hook.

Does querySelector() return a live collection?

No. It returns one element or null. Use querySelectorAll() when you need all current matches.