Find hardcoded colors that should be design tokens — the raw #hex / rgb() that escaped your token migration. Comment- and definition-aware, zero dependencies.
Project description
hexsweep
Find the hardcoded colors that should be design tokens. After a token
migration, components still have raw background: #3f82f0 buried in them instead
of var(--primary-blue) — and you don't find out until dark mode or a rebrand
breaks. hexsweep scans your source and flags every raw color literal that
escaped, with zero config and zero dependencies.
pipx run hexsweep src/
# src/components/Button.tsx (2)
# 14:18 #3f82f0 [hex6] background
# 27:10 rgb(255, 0, 0) [rgb] color
#
# ✖ 2 hardcoded colors in 1 file (23 files clean)
Exits non-zero when it finds colors, so it drops straight into CI. Pure standard
library. Also on npm (npx hexsweep) — the two builds produce byte-for-byte
identical output.
Why not grep or stylelint?
grep '#[0-9a-f]{6}' is what people fall back to, and it's noisy: it hits
#header id-selectors, url(#gradient) references, colors in comments, and it
can't see rgb()/hsl(), 3/4/8-digit hex, or tell a leftover from a token
definition.
stylelint can do it, but needs Node + a config + postcss/custom-syntax, it
can't see hex inside TSX/CSS-in-JS string literals, and — critically — its
color-no-hex flags your tokens.css (the one file that's supposed to hold
raw colors) exactly as loudly as a stray hex in a component. The request to
exempt token definitions has sat unimplemented for years.
hexsweep sits in between. It's a one-shot, zero-config scanner that is:
- comment-aware — colors in
//,/* */,<!-- -->are skipped (but strings are kept, so it does catch CSS-in-JS likestyled.div`color:#3f82f0`); - definition-aware —
--primary: #3f82f0,$brand: …,@brand: …are allowed by default; only usages are flagged (use--strictto flag definitions too); - structural —
#fff {id-selectors andurl(#a1b2c3)are not colors; - precise — matches hex (3/4/6/8) +
rgb()/rgba()/hsl()/hsla(), never a 5- or 7-digit run, with an--allowlist for the colors you keep on purpose.
Usage
hexsweep # scan the current directory
hexsweep src/ styles/ # scan specific paths
hexsweep --allow "#000,#fff" src # allowlist colors that are OK to hardcode
hexsweep --strict # also flag token definitions (--x/$x/@x)
hexsweep --ext css,scss,vue # override the scanned extensions
hexsweep --json # machine output (byte-identical both builds)
node_modules, .git, dist, build, coverage (and more) are skipped by
default; add others with --exclude. The default extensions are css, scss,
sass, less, vue, svelte, jsx, tsx, js, ts, html, htm, astro.
Exit codes: 0 clean · 1 hardcoded colors found · 2 error.
In CI
- run: pipx run hexsweep src/ # fails the build on a hardcoded color
The --json shape is sorted by (file, line, column) so it's deterministic
regardless of filesystem order:
{
"version": 1,
"summary": { "filesScanned": 24, "filesWithFindings": 1, "findingCount": 2 },
"findings": [
{ "file": "src/components/Button.tsx", "line": 14, "column": 18,
"literal": "#3f82f0", "category": "hex6", "property": "background", "isDefinition": false }
]
}
How it works
It's a line scanner, not a CSS/JS parser — that's what keeps it zero-dependency and lets the Node and Python builds stay byte-identical. It blanks comment spans (keeping string contents and every column position intact), then matches color literals with explicit-ASCII regexes, and applies the id-selector / url() / definition / allowlist gates. Columns are counted in UTF-8 bytes and files are visited in a fixed byte-sorted order, so output never depends on the OS or filesystem.
Scope
MVP matches #hex and rgb()/hsl() functional notation. Named colors (red),
modern oklch()/lab(), autofix, and a config file are intentionally left out —
the goal is a high-signal, zero-config CI gate, not a parser.
Install
pip install hexsweep # or pipx run hexsweep
npm i -g hexsweep # Node build, identical behaviour
Python ≥ 3.8 or Node ≥ 18. No dependencies.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hexsweep-0.1.0.tar.gz.
File metadata
- Download URL: hexsweep-0.1.0.tar.gz
- Upload date:
- Size: 13.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
717e7ad880094be2eba073512fbbd022f9d22a9d8e76fd05f77d878105b66fad
|
|
| MD5 |
fce1a0e2146f6d69bd37490520d22ec3
|
|
| BLAKE2b-256 |
2e45028d5e199d720f2d0a318d7213ae4b0b707d8bb8e7a22708fa6871515a7b
|
File details
Details for the file hexsweep-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hexsweep-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46e19971b2ef50c5cf156e541597cbafc24f27a4c65cfe722b48dd102237d3e2
|
|
| MD5 |
4072a631d5886e8666d4be2a59f06436
|
|
| BLAKE2b-256 |
2ecfeded61d1c5984f794890902978e591007c9ddb23cb426dfd32506a36dfe7
|