Check CSS custom property scope between selectors
A CSS custom property is available on the element where it is declared and, unless inheritance is interrupted, on descendants of that element.
Run — free
This checker compares the selector that defines a variable with the selector that uses it. It validates both selector strings, handles selector lists, and reports whether every usage branch is structurally covered by at least one definition branch. The result helps you catch a common source of missing design tokens before debugging computed styles in a browser.
Describe the definition and usage points
Enter the selector for the rule that declares the custom property in the definition field, then enter the selector for the rule that calls <code>var()</code> in the usage field. For example, a token defined on <code>.theme-dark .card</code> is available to a usage on <code>.theme-dark .card > .title</code>, because the title is selected beneath the card that receives the declaration. A definition on <code>:root</code> is treated as global because the root element is an ancestor of the document content. Both fields may contain comma-separated selector lists. The checker evaluates each usage branch independently and requires every branch to be covered for the overall answer to be true. It reports which definition branch matched each covered usage branch, making a long list easier to audit. This is a selector relationship check, so you do not need to paste a stylesheet, a declaration block, the property name, or its value. Supplying only the two relevant selectors keeps the result focused on cascade scope rather than unrelated source order or value syntax.
Understand the structural scope decision
The checker models the part of custom property availability that can be determined from selectors alone. It asks whether the definition selector can identify the same element as, or an ancestor of, the element identified by the usage selector. Compound requirements are respected: a definition on <code>.card.featured</code> is not assumed to cover a usage that mentions only <code>.card</code>. Child combinators must remain child combinators, while a descendant relationship may span additional compounds in the usage selector. Selector lists work as alternatives on the definition side and as obligations on the usage side. This deliberately conservative approach avoids claiming that a variable is available when the relationship is not visible in the selector text. Runtime facts can still change the real cascade. Source order, conditional rules, shadow boundaries, inline styles, layers, specificity, explicit resets, and the actual document tree are outside the two-selector input. Treat a true result as confirmation of structural containment, and use browser computed styles when you need to prove the final value in a particular rendered document.
Use validation errors to fix ambiguous input
Each selector is parsed before comparison. Empty selector-list branches, unmatched brackets or parentheses, unfinished combinators, declaration punctuation, incomplete class, ID, or pseudo tokens, and other malformed forms return an input error instead of a guessed result. That distinction matters in automation: false means the supplied selectors are valid but the requested usage is not structurally covered, whereas an error means no scope conclusion was made. Keep the fields as selectors only. Do not include braces, semicolons, a custom property declaration, or an entire CSS rule. Escaped characters, quoted attribute values, attribute selectors, and functional pseudo-classes remain grouped during tokenization so commas and combinators inside them are not mistaken for top-level syntax. The algorithm is deterministic, makes no network request, and has a fixed input-length limit. You can therefore use it in a linting step, a pull-request check, or a design-token migration script and receive the same result for the same pair every time. If advanced relational selectors encode facts that only a live DOM can settle, interpret an uncovered result conservatively and verify it against the target markup.
What you can do with it
Audit theme tokens
Confirm that component selectors using theme variables remain beneath the selector that activates the theme.
Review component refactors
Detect when a renamed or moved component selector no longer carries the structural prefix that owns its custom properties.
Validate token documentation
Check selector examples in a design system so documented usages are consistent with the stated definition scope.
FAQ
What does a true result mean?
Every valid usage selector branch is structurally the same as or below at least one definition selector branch.
Does this inspect my HTML or computed styles?
No. It compares selectors only, so document state, source order, layers, shadow DOM, and explicit value overrides are outside the result.
How are selector lists handled?
Definition branches are alternatives. Every comma-separated usage branch must match at least one definition branch for the overall result to be true.
Why did I receive an input error instead of false?
At least one selector was syntactically malformed. False is reserved for valid selectors that do not show the required scope relationship.
Does a definition on :root cover all usages?
Yes. The checker treats :root, html, and the universal selector as global definition scopes.
What does the API request cost?
Each API request costs $0.002. The browser version runs locally without an API request.
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-custom-property-scope-check \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"definition_selector":".theme-dark .card","usage_selector":".theme-dark .card > .title"}'const res = await fetch("https://api.kit.forhosting.com/web/css-custom-property-scope-check", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"definition_selector": ".theme-dark .card",
"usage_selector": ".theme-dark .card > .title"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/web/css-custom-property-scope-check",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"definition_selector": ".theme-dark .card",
"usage_selector": ".theme-dark .card > .title"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/web/css-custom-property-scope-check", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"definition_selector":".theme-dark .card","usage_selector":".theme-dark .card > .title"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"definition_selector":".theme-dark .card","usage_selector":".theme-dark .card > .title"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/web/css-custom-property-scope-check", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"definition_selector": ".theme-dark .card",
"usage_selector": ".theme-dark .card > .title"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "web.css_custom_property_scope_check",
"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
timeout_sec | 30 |
max_crawl_pages | 25 |
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. |