Sort CSS selectors by specificity
CSS specificity decides which competing declaration can win before source order and importance are considered, but comparing a long set of selectors by inspection is slow and error-prone.
Run — free
This tool accepts a list of individual CSS selectors, validates their syntax, calculates each three-part specificity value, and returns the entire list ordered from lowest to highest. It understands modern functional pseudo-classes such as :is(), :not(), :has(), :where(), and :nth-child(), while preserving the original order when two selectors have equal weight.
Read the three-part specificity score
Each result uses the familiar ID, class, and type structure. The first number counts ID selectors such as <code>#checkout</code>. The second counts classes, attribute selectors, and pseudo-classes such as <code>.active</code>, <code>[disabled]</code>, and <code>:hover</code>. The third counts type selectors and pseudo-elements such as <code>button</code> and <code>::before</code>. Universal selectors and combinators add nothing. Comparison is lexicographic, so one ID outweighs any number of entries in the other columns, and one class outweighs any number of type selectors when the ID counts match. The returned <code>specificity</code> array is convenient for software, while <code>specificity_text</code> gives the same value in a compact, readable form. Results are sorted ascending, which places broadly reusable rules first and the hardest selectors to override last. When two scores match exactly, the tool keeps their input order instead of inventing a secondary alphabetical ranking. That stable behavior makes repeated runs predictable and preserves useful context from a stylesheet or review checklist.
Handle modern pseudo-classes correctly
Functional pseudo-classes are where manual calculations most often go wrong. <code>:where()</code> always contributes zero specificity, including the selectors inside it, so authors can add structural precision without making a rule harder to override. By contrast, <code>:is()</code>, <code>:not()</code>, and <code>:has()</code> contribute the specificity of their most specific argument rather than adding a pseudo-class point of their own. The <code>:nth-child()</code> and <code>:nth-last-child()</code> forms contribute one pseudo-class point, plus the highest specificity in an optional <code>of</code> selector list. Shadow-tree forms such as <code>:host()</code> and <code>::slotted()</code> are also accounted for. The parser walks nested parentheses, brackets, quoted strings, and escapes, so commas inside a functional selector are not confused with commas separating top-level selectors. Supply each list item as one selector. A top-level comma would represent several selectors with potentially different scores, so it is rejected rather than collapsed into a misleading single value. This explicit rule keeps every input item mapped to exactly one output score.
Use the sorted list to simplify a cascade
A specificity report is most useful as a refactoring aid, not as encouragement to build stronger selectors. Paste representative selectors from a component, design system, or legacy stylesheet and inspect the high end of the result. Large jumps often reveal IDs, deeply qualified states, or a powerful argument hidden inside <code>:is()</code> or <code>:not()</code>. Those selectors may force later code to repeat structural details merely to override a declaration. Consider replacing them with a single-purpose class, lowering optional context with <code>:where()</code>, or organizing layers so precedence does not depend on escalating scores. The low end is useful too: element rules and lightweight utilities are easier to reuse when their intended role is clear. Automated pipelines can call the API for $0.002 to flag newly introduced selectors above a team-defined threshold, record specificity alongside generated CSS, or present ordered diagnostics during review. Invalid input fails the whole request, ensuring a partial report never hides a malformed selector that should be fixed first. The calculation is deterministic and requires no network access, so identical input always produces identical ordering and values.
What you can do with it
Audit a legacy stylesheet
Order selectors by weight to find IDs and heavily qualified rules that make routine overrides difficult.
Review component CSS
Compare new selectors with established component conventions before merging a change into a shared design system.
Enforce a specificity budget
Calculate scores in an automated check and flag selectors that exceed the maximum your team has chosen.
FAQ
What does each specificity number mean?
The three values count ID selectors, class-like selectors, and type-like selectors respectively, and they are compared from left to right.
How is :where() counted?
:where() and everything inside its argument contribute zero specificity, although its selector syntax is still validated.
How are :is(), :not(), and :has() counted?
Each contributes the specificity of the most specific selector in its argument list, without an additional pseudo-class point.
Can one input item contain commas?
Not at the top level. Submit each selector separately because members of a selector list can have different specificity. Commas nested inside supported functional pseudo-classes are accepted.
What happens when two selectors have equal specificity?
Their original input order is retained, making the sort stable and deterministic.
What does the API request cost?
Each request costs $0.002. The browser version runs locally for free.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/web/css-specificity-sort \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"selectors":["button",".toolbar button:hover","#app .toolbar button"]}'const res = await fetch("https://api.kit.forhosting.com/web/css-specificity-sort", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"selectors": [
"button",
".toolbar button:hover",
"#app .toolbar button"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/web/css-specificity-sort",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"selectors": [
"button",
".toolbar button:hover",
"#app .toolbar button"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/web/css-specificity-sort", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"selectors":["button",".toolbar button:hover","#app .toolbar button"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"selectors":["button",".toolbar button:hover","#app .toolbar button"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/web/css-specificity-sort", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"selectors": [
"button",
".toolbar button:hover",
"#app .toolbar button"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "web.css_specificity_sort",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_items | 1000 |
max_selector_length | 4096 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |