What does this CSS check actually test?
This tool is a quick structural sanity check for CSS, meant to catch the mistakes that break a whole stylesheet: a missing or extra brace, a rule with no selector, or a declaration typed without its colon. It doesn't parse CSS the way a browser does, and it isn't a substitute for the W3C CSS Validation Service.
A missing brace is worth catching early because of how CSS error recovery works. Per CSS Syntax Level 3, a browser that hits an unclosed block keeps consuming until it finds the matching brace or the end of the file, so one stray { can silently swallow every rule after it.
How the check works
The JavaScript does four things, in order, and stops at the first problem:
- Removes
/* ... */comments, reporting a comment that's never closed. - Walks through the text, skipping over quoted strings (an unclosed string is an error) and tracking braces. A
}with no open block is reported; any{left open at the end is reported with a count, for example "1 unclosed opening brace". - For each innermost
selector { ... }block, rejects one whose selector is empty. - Splits each block's body on semicolons that sit outside strings and parentheses, and rejects any non-empty piece without a colon, quoting it back to you.
If no rule block was found at all, that's an error too. Success returns "Valid CSS (best-effort check: brace balance and basic declaration syntax only, not a full CSS spec validator)".
Results on real input
| Input | Result |
|---|---|
.btn { color: #fff; padding: 4px 8px; } |
Valid |
.btn { color: red; then .card { margin: 0; } |
1 unclosed opening brace |
.btn { color: red; } } |
unmatched closing brace |
.btn { color red; } |
malformed declaration "color red" |
{ color: red } |
rule block with an empty selector |
@import url("a.css"); on its own |
no rule blocks were found |
.btn { colr: blu; width: -5banana; } |
Valid (names and values aren't checked) |
.a { color: red background: blue } |
Valid (the missing semicolon isn't noticed) |
@media (max-width: 600px) { .btn { color: red } } |
Valid |
.a::after { content: "}"; } |
Valid (the brace is inside a string) |
.a { background: url(data:image/png;base64,AAA) } |
Valid (the semicolon is inside url()) |
.a { content: "oops } |
unclosed string starting on line 1 |
Read the table as two lists. The first five rows and the last one are what it's good at. Rows six and seven show what slips through, and the content and url() rows show that braces and semicolons inside values don't cause false alarms.
What a pass does not tell you
- Property names and values are never checked.
colr: blupasses. The browser will drop that declaration without complaint, which is why typos like this survive for months. Your browser's developer tools show such declarations struck through or marked invalid, which is the quickest way to find them. - A missing semicolon between two declarations passes. The browser treats
red background: blueas one invalid value forcolor, and both declarations are lost. - At-rules aren't understood.
@media,@supportsand@layerblocks pass as long as their braces balance, but their conditions aren't checked. - Selectors aren't checked beyond being non-empty.
- Nesting syntax (native CSS nesting, or Sass and Less) isn't modelled; braces just have to balance.
When to use which checker
Use this tool when a stylesheet stops working after an edit and you suspect a brace, or as a quick pass before minifying. The CSS minifier will happily compress broken CSS, so checking first is cheap insurance. For anything you'd ship, run the W3C service too; it parses against the CSS specifications and reports unknown properties and bad values with line numbers.
If the CSS is hard to read, run it through the code beautifier first so each declaration sits on its own line, then edit it in the online CSS editor. The CSS lessons cover the syntax rules that this check is built around.
Last updated