b2KIT

Unused CSS Finder

Detect unused CSS selectors by comparing stylesheet rules against HTML markup for code cleanup.

Tested tool guide Tested browser tools Checked August 16, 2026

What Unused CSS Finder does, with a checked example

Paste your HTML in one box, your CSS in the other, and this tool checks every selector in the stylesheet against the markup, listing the ones no element matches. Selector lists are split on commas and each part is tested independently, and matching obeys the same rules a browser applies: class tokens and ids must match exactly, letter for letter, and compound selectors only match real element nesting. The usual surprise: the tool sees only the markup you paste. Classes your scripts add at runtime, or selectors used on other pages, come back as unused even when the page needs them.

Worked example

A concrete input and expected output from the current implementation.

Input

HTML box: <div id="app" class="card"><p class="note">Hello</p></div>

CSS box:
.card { padding: 16px; }
.note { color: #555; }
#app { max-width: 640px; }
p { line-height: 1.5; }
.hero { display: grid; }
.alert, .warning { color: red; }

Expected output

Used: 4 of 7 (.card, .note, #app, p). Unused: 3 (.hero, .alert, .warning). The rule .alert, .warning is reported as two separate unused selectors, since each selector in a comma list is judged on its own.

.card, .note, #app, and p each have a matching element in the pasted markup, while no element carries the class hero. The comma group is split, so .alert and .warning are checked independently: a matching .alert element would not keep .warning off the unused list.

How the result is produced

1

Selector extraction

Each ruleset is reduced to its selector list, and lists are split on commas so a rule like .alert, .warning is checked as two selectors that succeed or fail independently. Matching is exact, never by substring: .btn-primary does not count as used just because a class attribute contains the text btn.

2

Matching against the markup

The HTML is parsed into a document tree and every selector is tested against it with the same semantics a browser uses for matching: element selectors match tags, class selectors match whole tokens, id selectors match the id attribute, and combinators such as .parent > .child hold only when the elements are nested exactly as the selector requires. Selectors that match nothing are reported unused.

Good uses

  • Stylesheet cleanup after a layout change: you remove a page section or drop a framework override, then paste the remaining markup and the full stylesheet to see which selectors lost their last matching element and can be deleted.
  • Auditing a legacy stylesheet: paste a CSS file that has grown over years - old grid helpers, unused button variants, abandoned utility classes - together with the pages you actually ship, and get a shortlist of rules that nothing on those pages uses.
  • Verifying a class-rename refactor: run the old stylesheet against the new markup; any selector that still fails to match is a stale rule the rename missed and should be removed or updated.

Limits and checks

  • JavaScript is invisible to it: the tool compares against the markup you paste, not the rendered page. A class a script toggles or injects - modal-open, is-active - is reported unused even when the page depends on it. Paste markup captured after scripts run, or add the dynamic classes yourself.
  • One page at a time: the report covers only the markup supplied. A selector unused here may be used on another page or in a template partial, so paste every distinct page and template the stylesheet serves before trusting the unused list.
  • It judges selectors, not properties: a used selector can still carry declarations that do nothing, and a selector matched by a single element is reported used even when its other usages are gone. A selector on the unused list cannot affect the markup you pasted, so deleting it is safe for that page - but only for that page.

Common questions

Why does a class I can see on a button come back unused?

Two common causes: the class is added by JavaScript after the page loads, so it is not in the markup you pasted, or the class token differs in spelling or case, because matching is exact and case-sensitive. Inspect the element in the browser, then paste the markup as it looks after scripts have run.

Should I delete everything on the unused list?

Treat it as a shortlist, not an automatic delete list. The comparison runs entirely in your browser, so nothing is uploaded, but before removing anything, search your codebase for that selector in JavaScript, templates, and other pages, since the tool sees only the CSS and markup you entered. Keep a backup and re-run the check after each round of deletions.

References and verification

The example and behavioral notes were checked against the browser implementation. Standards and primary references below define the relevant format, formula, or platform behavior.

Related Tools