sourcecodestack Team
Tools, guides & how-tos
Invalid code is one of the most insidious problems in web development. Unlike a JavaScript runtime error that throws an exception with a line number, invalid HTML and CSS fail silently. Your page still loads. It looks mostly fine. And somewhere, in some browser, on some device, the broken markup causes a layout to collapse, a screen reader to lose its place, or a search engine to misparse your content.
Code validation and linting are the tools that surface these problems before they reach users. They are not just pedantic quality gates — they are practical defenses against bugs that would otherwise be nearly impossible to trace. This guide explains what validation and linting are, how they work across HTML, CSS, and JavaScript, and how to integrate them into a workflow that catches problems early and keeps your codebase healthy.
Code validation is the process of checking your code against a formal specification — a set of rules defined by a standards body — to confirm that it is syntactically correct and follows the established standard.
For HTML, the specification is maintained by the W3C (World Wide Web Consortium). For CSS, also the W3C. For JavaScript, it is the ECMAScript specification maintained by ECMA International.
Validation answers the question: Does this code conform to the official standard?
Code linting is the automated analysis of code for programmatic and stylistic errors. Unlike validation, which checks against a formal standard, linting checks against a configurable set of rules — rules about code style, potential bugs, anti-patterns, and best practices.
Linting answers the question: Is this code likely to cause problems, and does it follow our team’s agreed conventions?
Code formatting is the automated restructuring of code to follow a consistent visual style — indentation, line length, quote style, semicolons. Formatting makes no judgments about logic or correctness; it only cares about appearance.
| Concept | Checks Against | Catches | Examples of Tools |
|---|---|---|---|
| Validation | Official language specification | Syntax errors, invalid elements, undefined properties | W3C Validator, CSS Validator |
| Linting | Configurable rule sets | Anti-patterns, potential bugs, style violations | ESLint, Stylelint, HTMLHint |
| Formatting | Style configuration | Inconsistent whitespace, quote style, line endings | Prettier, EditorConfig |
These three practices are complementary, not competing. Validation ensures correctness. Linting ensures quality. Formatting ensures consistency. A mature codebase uses all three.
Pro tip: Run validation and linting in sequence, not as a choice. Formatting first (so the code looks consistent), then linting (to catch logic and style issues), then validation (to confirm standards compliance). Each layer catches different categories of problems.
HTML validation checks your markup against the HTML5 specification. The W3C’s validator is the authoritative tool for this, and it catches errors that browsers silently paper over.
Modern browsers implement error recovery — when they encounter invalid HTML, they attempt to interpret what the author probably meant and render something reasonable. This is a great feature for end users. It is a terrible feature for developers, because it means your broken code appears to work.
Different browsers implement error recovery differently. A missing closing tag that renders fine in Chrome might cause a layout collapse in an older browser or a parsing failure in a web crawler.
Unclosed elements:
<!-- Invalid: <div> not closed -->
<div class="container">
<p>Some content
<!-- Valid -->
<div class="container">
<p>Some content</p>
</div>
Nesting violations:
<!-- Invalid: <p> cannot contain block elements -->
<p>
<div>This is wrong</div>
</p>
<!-- Valid -->
<div>
<p>Paragraph content</p>
</div>
Duplicate IDs:
<!-- Invalid: IDs must be unique per page -->
<div id="header">First</div>
<div id="header">Second</div> <!-- Duplicate! -->
Missing required attributes:
<!-- Invalid: <img> without alt attribute -->
<img src="photo.jpg">
<!-- Valid -->
<img src="photo.jpg" alt="A photo of the team at our annual retreat">
Obsolete elements:
<!-- Invalid: <center>, <font>, <b> as a style element are obsolete in HTML5 -->
<center><font color="red">Old-style markup</font></center>
<!-- Valid -->
<p style="text-align: center; color: red;">Modern approach</p>
<!-- Or better yet, use CSS classes -->
Pro tip: The most dangerous HTML validation error is the unclosed element. A missing
</div>does not just break that element — it can swallow all following content into the unclosed element, causing cascading layout failures that are extremely difficult to debug by eye.
CSS validation checks your stylesheets against the CSS specification. Invalid CSS is silently ignored by browsers — another case where code appears to work while actually failing.
Unknown properties:
/* Invalid: 'colour' is not a CSS property */
.button {
colour: red; /* Typo: ignored by all browsers */
color: red; /* Valid */
}
Invalid values:
/* Invalid: percentage not valid for font-weight */
.heading {
font-weight: 150%; /* Invalid — use 100-900 numeric values */
font-weight: 700; /* Valid */
}
/* Invalid: missing unit */
.box {
margin-top: 20; /* Invalid in CSS (valid in SVG) */
margin-top: 20px; /* Valid */
}
Browser-vendor prefix issues:
/* Incomplete: vendor prefix without standard property */
.element {
-webkit-transform: rotate(45deg); /* Vendor prefix only */
transform: rotate(45deg); /* Standard property — required */
}
Beyond spec validation, CSS tools can check browser support — alerting you when a property has limited browser coverage. Properties like container-type, @layer, and color-mix() are modern CSS features not universally supported.
/* Modern CSS with limited support — needs fallback */
.sidebar {
container-type: inline-size; /* Not supported in older browsers */
}
/* Better: provide a fallback */
.grid {
display: flex; /* Fallback for very old browsers */
display: grid; /* Override with modern layout */
}
ESLint is the dominant JavaScript linting tool. It analyzes your JavaScript code for potential errors, enforces coding conventions, and catches patterns that cause bugs in subtle ways.
ESLint parses your JavaScript into an Abstract Syntax Tree (AST) — a structured representation of your code’s logic — and runs configured rules against it. Each rule inspects specific patterns and reports violations.
Configuration lives in .eslintrc.json (or .eslintrc.js, .eslintrc.yaml):
{
"env": {
"browser": true,
"es2021": true
},
"extends": "eslint:recommended",
"rules": {
"no-unused-vars": "error",
"no-console": "warn",
"eqeqeq": "error",
"no-var": "error",
"prefer-const": "error"
}
}
no-unused-vars — Catches variables declared but never used. A strong signal of dead code or a forgotten cleanup.
// ESLint error: 'userName' is defined but never used
function greetUser() {
const userName = 'Alice'; // Never used below
return 'Hello, stranger!';
}
eqeqeq — Enforces === over ==. JavaScript’s == operator performs type coercion, producing famously surprising results:
// Surprising == behavior
console.log(0 == false); // true
console.log('' == false); // true
console.log(null == undefined); // true
// Predictable === behavior
console.log(0 === false); // false
console.log('' === false); // false
no-var + prefer-const — Enforces modern variable declarations. var has function scope and hoisting behavior that causes subtle bugs. let and const have block scope and are predictable.
// Anti-pattern: var hoisting causing a bug
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Prints: 3, 3, 3
}
// Correct: let has block scope
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // Prints: 0, 1, 2
}
no-console — Warns when console.log is left in production code. Debug logging in production is a minor security risk and a sign of incomplete cleanup.
| Level | Value | Effect |
|---|---|---|
"off" |
0 | Rule disabled |
"warn" |
1 | Violation reported but does not fail CI |
"error" |
2 | Violation reported and fails CI |
The silent failure of invalid code is worth examining in depth, because it creates a false sense of security.
When a browser encounters an HTML parsing error, it does not stop. It follows a specified error recovery algorithm that defines exactly how to handle every possible invalid input. The result is usually “good enough” — but “good enough” is not the same as “correct”.
The specific consequences of invalid HTML depend on the browser, the version, and the exact type of error:
<tbody> in a table is auto-inserted by some browsers, which changes the DOM structure your CSS and JavaScript target<div />) are valid in XML but not HTML — browsers parse them differentlyHTML errors compound. A single unclosed element can cause every element that follows it to be nested incorrectly, which in turn causes CSS selectors to fail, which causes JavaScript queries to return null, which causes runtime errors.
<!-- One missing </div> can cascade badly -->
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<!-- Missing </ul> and </nav> here -->
<main>
<!-- This content is now inside <nav> in the parsed DOM -->
<!-- CSS targeting main > .content will fail -->
<div class="content">...</div>
</main>
Code quality is not just about developer experience — it directly impacts accessibility and search engine performance.
Screen readers and assistive technologies parse the DOM. Invalid HTML can cause:
<!-- Invalid: label not associated with input -->
<label>Email</label>
<input type="email" name="email">
<!-- Valid: for attribute matches input id -->
<label for="email-input">Email</label>
<input type="email" id="email-input" name="email">
Search engine crawlers are not as forgiving as modern browsers. Invalid HTML can cause:
Valid, semantic HTML helps search engines understand your content hierarchy, which is a foundational SEO factor.
Validation and linting are most powerful when they run automatically — not just locally when a developer remembers to run them.
# .github/workflows/lint.yml
name: Code Quality
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
- name: Run Stylelint
run: npm run lint:css
With this configuration, every pull request is automatically checked. Code that fails linting cannot be merged. This enforces standards at the team level without requiring individual discipline.
For even earlier feedback, pre-commit hooks run linting before a commit is allowed:
// package.json
{
"lint-staged": {
"*.js": ["eslint --fix", "prettier --write"],
"*.css": ["stylelint --fix"],
"*.html": ["htmlhint"]
}
}
This catches errors before they even enter the repository.
For quick, one-off validation without configuring a local tool, our browser-based Code Validator lets you paste HTML, CSS, or JavaScript and receive instant validation feedback.
Use cases for the online validator:
<nav>, <main>, <article>, <section>, <aside>) improve both validation outcomes and accessibility. They carry meaning that generic <div> elements do not.--cache flags, run only on changed files in CI, and parallelize where possible.Clean code is not a luxury or an expression of perfectionism. It is a practical investment. Every hour spent writing valid, well-linted code saves multiple hours of debugging mysterious failures that only appear in production, on specific devices, in edge-case browser environments. Validation and linting are the closest thing software development has to a guarantee.
sourcecodestack Team
We build free, privacy-first browser tools and write practical guides on how to use them. Everything runs on your device — no uploads, no sign-ups.
A site will not load. Before you clear your cache, reboot the router, or file an angry support ticket, answer …
You have two JSON payloads — maybe a staging API response and a production one, or a config file before and af…
API Client Guide: Test APIs Online Without Postman Testing an API should be fast and frictionless. You have an…