╔════════════════════════════════════════════════════════════════════════════════════════════╗
║ ║
║ ██████╗ █████╗ ████████╗███████╗██╗ ██╗██╗████████╗ ║
║ ██╔════╝ ██╔══██╗╚══██╔══╝██╔════╝██║ ██╔╝██║╚══██╔══╝ ║
║ ██║ ███╗███████║ ██║ █████╗ █████╔╝ ██║ ██║ ║
║ ██║ ██║██╔══██║ ██║ ██╔══╝ ██╔═██╗ ██║ ██║ ║
║ ╚██████╔╝██║ ██║ ██║ ███████╗██║ ██╗██║ ██║ ║
║ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ║
║ ║
║ three gates a linter will not give you ║
║ ║
╚════════════════════════════════════════════════════════════════════════════════════════════╝
Three gates for defects a linter does not have an opinion about, because none of them is a syntax error. Each one exists because a real person read something and could not.
| Gate | Fails the build when |
|---|---|
typefloor |
Type is declared below a readable floor (13px by default) |
contrast-gate |
A declared colour cannot reach 4.5:1 on any surface the file also declares |
deadname-gate |
A retired name reaches anything a human reads |
Install
pip install legible
Use
typefloor ./src # every CSS and inline declaration
contrast-gate ./src # WCAG 4.5:1 against declared surfaces
deadname-gate ./src # a name that should be gone
Each exits non-zero on a finding and prints the file and line. --list
shows every offender rather than the summary.
Why these three
typefloor. Six-point type is not a style choice. This was written after the same reader reported the same thing more than once, and the first time it was pointed at a folder it found every violation immediately — what was missing was never the tool, it was anything that pulled it.
contrast-gate. It reads CSS custom properties — --ink: #hex against
--bg: #hex — and reports the best surface each ink can reach. Dark grey on
navy measured 2.64:1 where body text needs 4.5, and a person found that, not
a build.
It does not read literal color: declarations. A stylesheet using
neither has nothing to compare, and it says so and exits 0 — which in CI
reads as a pass on a site nobody checked. Pass --require-pairs to fail
instead when nothing was found. The Action sets it.
deadname-gate. Fails a build when a name that should be gone reaches a title, a footer, a byline, alt text or a comment — while leaving machine identifiers alone, because renaming a repository path or a published artifact breaks working links or contradicts a deposited record. Both halves matter: a gate that cannot tell those apart gets switched off.
Written for a company rename. It is the same shape as the problem of a person changing their name and finding it in three hundred places, which is the harder version and the one worth getting right.
Exit codes
| Code | Means |
|---|---|
0 |
Clean |
1 |
It found something. The file and line are printed. |
2 |
Misconfigured — it could not check, and will not report clean |
The third one matters. deadname-gate forbids nothing out of the box, so a
fresh install exits 2 with an explanation rather than 0. A gate that
reports a pass while checking nothing is worse than no gate, and every one of
these three shipped a version that did exactly that before it was caught.
Configuration
Each gate reads a JSON file of exceptions, so a deliberate choice is recorded with a reason rather than silently re-flagged.
typefloor ./src --config typefloor.json
contrast-gate ./src --config contrast.json
deadname-gate ./src --config deadname_gate.json
TYPEFLOOR_CONFIG, CONTRAST_CONFIG and DEADNAME_CONFIG do the same.
In CI
- run: pip install legible
- run: typefloor ./src
- run: contrast-gate ./src --require-pairs
- run: deadname-gate ./src
There are Actions too, in .github/actions/ of the source repository.
A note on the argument
An earlier version ignored a bare path and scanned its own directory
instead, then printed OK. A gate that reports a pass on a tree it never
opened is worse than no gate, so a positional path is now the root. If you
pinned an earlier build, check what it is actually reading.
Licence
MIT.
Metadata
Release files for legible 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| legible-0.1.0.tar.gz | 16.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| legible-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.7 kB
Release files / legible-0.1.0.tar.gz
| Download URL | legible-0.1.0.tar.gz |
|---|---|
| Size | 16.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b29774f2edc547dbb54c9f67bcd4061b3deb45b69ec75f284333b4a3d05068b
|
|
BLAKE2b-256 checksum How to use checksums |
2f6afef8b1d692dd00c1632cbd1304fd9f922ccdcfe3874a041e5f3a374d164d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|
Release files / legible-0.1.0-py3-none-any.whl
| Download URL | legible-0.1.0-py3-none-any.whl |
|---|---|
| Size | 16.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
37beb99f788884d8fb5b24507f7acc93d68cbc31660f414d2106871f79c11b89
|
|
BLAKE2b-256 checksum How to use checksums |
43cbbb42811847d8704175c802cf0719c4e4b8cdc54dcad0fd6a17d533480a20
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.14
|