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

v0.1.0

Published

Deterministic guardrails for AI-generated TypeScript: comment discipline, strict-tsconfig enforcement, mobile overflow patterns, interactive states, theme-aware colors, and static a11y as one drop-in ESLint plugin, plus a runtime overflow/contrast checker

Downloads

28

Readme

eslint-plugin-vibesafe

Deterministic guardrails for AI-generated TypeScript. One drop-in ESLint plugin that catches the mistakes coding agents actually make: leftover note-taking comments, quietly relaxed compiler strictness, layouts that overflow on phones, interactive elements with no hover or focus states, colors that disappear in dark mode, and inaccessible markup. Plus a runtime checker that proves what static analysis cannot.

Every rule is deterministic. Same input, same findings, no heuristics, no AI.

Install

pnpm add -D eslint-plugin-vibesafe

Quick start

For a Next.js app (assumes eslint-config-next is present, which create-next-app sets up):

// eslint.config.mjs
import vibesafe from 'eslint-plugin-vibesafe';

export default [...vibesafe.configs.next];

For any other TypeScript project:

import vibesafe from 'eslint-plugin-vibesafe';

export default [...vibesafe.configs.core];

Run with warnings blocking, so the comment rules gate CI like everything else:

eslint --max-warnings=0

The three layers

| Layer | Catches | When | |---|---|---| | ESLint rules (configs.core / configs.next) | comments, type safety, ASCII, overflow patterns, missing interactive states, non-token colors, static a11y | edit time and CI | | vibesafe/strict-tsconfig | someone quietly turning off strict or noUncheckedIndexedAccess | CI | | vibesafe-ui (Playwright + axe) | real horizontal overflow at 375px and real contrast failures in light and dark mode | pre-ship |

Custom rules

vibesafe/no-multiline-comments

Any comment block longer than one line gets asked: are these comments necessary, or note-taking left over from implementing or debugging? Delete stale ones; if the code truly needs that much explanation, refactor the code.

The one allowed multi-line form is an interface doc directly above a declaration, one line per label, each label at most once:

// description: parses a class string into variant-aware tokens
// input: raw className string
// output: ClassToken list
// side effects: none
export function tokenize(value: string): ClassToken[] { ... }

A label line that wraps to a second line fails the grammar, so the one-line-per-label limit enforces itself. Tool directives (eslint-disable, @ts-expect-error, prettier-ignore, triple-slash references, shebangs) are exempt.

vibesafe/comment-max-length

Comment lines longer than max characters (default 100) are flagged. Options: { max?: number }.

vibesafe/ascii-only

Non-ASCII anywhere in the file: comments, identifiers, and literals in one rule. Em-dash becomes -, smart quotes become ' or ", ellipsis becomes ....

vibesafe/strict-tsconfig

Reads the resolved compiler options through the type-aware parser and errors if strict or noUncheckedIndexedAccess is off. ESLint cannot set compiler flags, but it can refuse to pass while they are relaxed.

vibesafe/no-viewport-width-overflow

Bans w-screen, w-dvw, w-[100vw] and friends. 100vw includes the scrollbar width, which is the classic cause of a page-wide horizontal scrollbar. Use w-full.

vibesafe/no-unresponsive-fixed-width

Fixed widths at or above maxPx (default 360) must carry a responsive prefix: w-[600px] fails, md:w-[600px] passes. Covers Tailwind scale values (w-96 is 384px), arbitrary px and rem values, size-, basis-, min-w-, and inline style widths. max-w-* is exempt because it constrains rather than overflows. Options: { maxPx?: number }.

vibesafe/wide-content-needs-scroll-container

<table> and <pre> must sit inside (or carry) overflow-x-auto or an equivalent scroll container; <pre> may instead wrap with whitespace-pre-wrap. Skips when the class list cannot be statically determined.

vibesafe/no-bare-nowrap

whitespace-nowrap without truncate, an overflow utility, or an ancestor scroll container is a horizontal-overflow bug waiting for long content.

vibesafe/responsive-grid-columns (advisory)

Bare grid-cols-3 and up should usually be grid-cols-1 md:grid-cols-3. This is the one heuristic-adjacent rule, so it ships in a separate configs.advisory pass that nags without failing CI. Options: { minColumns?: number }.

vibesafe/interactive-states

Raw interactive HTML elements (button, a with href, anything with onClick or role="button") must style their states: hover:, active:, focus-visible:, and for buttons disabled:. Capitalized components are exempt since a component library carries its states internally; the rule targets exactly the hand-rolled elements where states get skipped. Skips when the class list cannot be statically determined.

vibesafe/no-unfocusable-outline

outline-none without a focus-visible: replacement removes keyboard users' only affordance. Add focus-visible:ring-2 or similar, or do not remove the outline.

vibesafe/theme-aware-colors

The root cause of "black text on a dark background, can't see anything" is a color-bearing utility that only defines one theme.

  • mode: 'tokens' (default): raw palette colors (text-gray-900, bg-white, text-[#111]) are banned in favor of semantic tokens (text-foreground, bg-background), which are theme-aware by construction. This is the shadcn/ui model.
  • mode: 'paired': raw colors are allowed only when the element also styles the same property under dark:.

Inline style colors are flagged in both modes since they cannot respond to theme switching at all. Options: { mode?: 'tokens' | 'paired', allow?: string[] }.

Inherited baseline

configs.core also bundles the strict baseline so you do not assemble it per project: typescript-eslint strict plus the type-aware set (floating promises, misused promises, switch exhaustiveness, strict boolean expressions, explicit module boundary types, described-only @ts-expect-error, no non-null assertions), size caps (50 lines per function, 300 per file, complexity 10, depth 4, params 4), hygiene rules, no console.*, no raw process.env outside an env module, no throw new Error() in favor of typed errors, and the full static accessibility set for .tsx files. The accessibility rules come from eslint-plugin-jsx-a11y but are registered under the vibesafe-a11y namespace (vibesafe-a11y/alt-text and so on), so they never collide with eslint-config-next's own jsx-a11y registration. Tests, scripts, and config files get the standard relaxations.

configs.next layers on i18n literal-string enforcement and exemptions for components/ui, Drizzle schemas, and migrations.

Advisory pass

eslint --config eslint.advisory.config.mjs .
// eslint.advisory.config.mjs
import vibesafe from 'eslint-plugin-vibesafe';

export default [...vibesafe.configs.advisory];

Run it without --max-warnings=0; it nags, it never blocks.

Runtime checker

Static rules catch overflow and contrast risk patterns; the runtime checker proves the outcome. It loads each route at mobile and desktop widths, in light and dark mode, and fails on real horizontal overflow or axe-core contrast violations.

pnpm add -D playwright @axe-core/playwright
pnpm exec playwright install chromium

vibesafe-ui / /pricing /docs --base http://localhost:3000

Flags: --viewports 375,1280, --themes light,dark, --dark-class dark (for class-strategy dark mode), --full-axe (fail on all serious/critical axe violations, not just contrast), --timeout 30000.

Philosophy

If a guardrail depends on judgment, an agent will argue its way past it. Every check here is a yes/no question a machine answers the same way every time. The comment rules do not measure comment quality; they measure comment shape, and shape is enough: code that needs paragraphs of explanation needs refactoring, and comments that were scaffolding for a thinking process need deleting.

License

MIT