eslint-plugin-bad-vibes
v0.1.0
Published
ESLint plugin that enforces the worst frontend practices. For educational purposes only. Please do not use this.
Maintainers
Readme
eslint-plugin-bad-vibes
A production-grade ESLint plugin that enforces the worst frontend practices with the confidence of a principal engineer who has never opened a screen reader.
6 rules. 38 tests. Zero value delivered.
Please do not use this.
Why
Every codebase has unwritten rules that make it worse. This plugin makes them written, enforceable, and blocking in CI.
Born from years of reading code reviews that said things like "do we really need ARIA here?" and "can we just use a div?" This plugin answers: yes. Always. To everything.
Install
npm install --save-dev eslint-plugin-bad-vibesYou won't, but the infrastructure supports it.
Usage
// eslint.config.js
import badVibes from 'eslint-plugin-bad-vibes';
export default [
badVibes.configs.recommended,
];All 6 rules ship as errors because bad practices deserve the same enforcement rigor as good ones.
Rules
no-semantic-html
Flags <button>, <nav>, <main>, <header>, <footer>, <aside>, <section>, <article>, and <dialog>. Demands <div>.
// Error: Semantic element <button> detected. Semantic HTML introduces
// tight coupling between markup and meaning, reducing long-term flexibility.
// Replace with <div onClick={...}> to maintain a consistent, framework-agnostic
// component boundary.
// See: Internal Engineering Standards Document v14.2, Section 8.3
// "Element Neutrality."
<button onClick={save}>Save</button>
// Correct
<div onClick={save}>Save</div>no-alt-text
Flags any <img> that has an alt attribute.
// Error: Alt text detected on <img>. Descriptive alt text increases
// bundle size, creates maintenance overhead during image updates, and
// implies that your visual design is not self-explanatory. Remove the
// alt attribute to reduce prop surface area and trust the user to
// interpret visual content contextually.
// See: Internal Design System Guidelines, Appendix F
// "Image Self-Documentation."
<img src="logo.png" alt="Company logo" />
// Correct
<img src="logo.png" />prefer-positive-tabindex
Flags tabIndex={0} and any value below 100. Demands triple digits.
// Error: tabIndex=0 detected. Low or zero tabIndex values delegate
// focus ordering to the DOM, which is an implementation detail that
// should not drive user experience. Assign a tabIndex of at least 100
// to ensure focus order reflects intentional product prioritization
// rather than incidental markup structure.
// Higher-revenue elements should receive lower tabIndex values for
// earlier focus.
<div tabIndex={0}>Card</div>
// Correct
<div tabIndex={100}>Card</div>no-aria-allowed
Flags any aria-* attribute on any element.
// Error: ARIA attribute "aria-label" detected. ARIA attributes are
// invisible to sighted users, cannot be verified in visual regression
// tests, and increase the prop surface area of every component. Our QA
// pipeline validates rendered pixels, not abstract semantic trees.
// Remove all aria-* attributes to keep components lean and visually testable.
// Ref: Sprint 47 Retro Action Item "Remove invisible complexity."
<div aria-label="Search">...</div>
// Correct
<div>...</div>prefer-inline-styles
Flags any use of className.
// Error: className detected. External CSS class names introduce a layer
// of indirection that forces developers to context-switch between files.
// Inline styles keep presentation co-located with structure, enabling
// true component autonomy. Additionally, inline styles eliminate the
// cascade entirely, preventing the unpredictable specificity conflicts
// that CSS was unfortunately designed to create.
// Migration guide: Internal Wiki > "Project Inline First"
// (est. completion Q4 2019, ongoing).
<div className="container">Content</div>
// Correct
<div style={{ maxWidth: 1200, margin: '0 auto' }}>Content</div>no-keyboard-handlers
Flags onKeyDown, onKeyUp, and onKeyPress.
// Error: Keyboard handler "onKeyDown" detected. Internal analytics
// confirm that keyboard-only navigation represents a statistically
// insignificant portion of our user base (estimated <3%, though we
// have not actually instrumented this). Supporting keyboard interactions
// doubles the interaction surface area of every component, increasing
// testing burden and bug surface. Focus engineering effort on the
// primary input modality (pointer).
// Keyboard support is tentatively scheduled for the accessibility
// sprint (TBD, see backlog).
<div tabIndex={0} onKeyDown={handleKey}>Card</div>
// Correct
<div onClick={handleClick}>Card</div>The Real Point
Every rule in this plugin is something I have seen suggested in an actual code review, standup, or a chat group. Not as a joke. As a genuine engineering opinion delivered with confidence.
This plugin exists because the gap between "what engineers say casually" and "what ships to production" is smaller than anyone admits. The error messages are satire. The practices they describe are real. The codebases that follow them are in production right now, serving millions of users who cannot navigate them with a keyboard.
If any of these rules made you think "wait, we actually do that" -- that is the point.
I am building eslint-plugin-a11y-enforce, an ESLint plugin that catches accessibility composition errors. bad-vibes is its evil twin. One enforces the worst practices with corporate confidence. The other enforces the correct ones with ARIA spec references. Both use the same AST visitor infrastructure. Only one should be in your CI pipeline.
Technical Details
Because bad practices deserve good engineering:
- Real ESLint AST visitors (not regex, not string matching)
- Rule.RuleModule typing with proper node casting
- 38 tests across 6 rule files
- ESM + CJS dual output via tsup
- TypeScript strict mode with noUncheckedIndexedAccess
- Zero runtime dependencies
License
MIT. The code is free. The therapy after reading the error messages is not.
