npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

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-vibes

You 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.