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

@pragmatiks/lint

v0.7.0

Published

Pragmatiks engineering principles eslint config and bundled semgrep rules.

Readme

@pragmatiks/lint

Library-only eslint flat config and bundled semgrep rules for Pragmatiks engineering principles.

This package has no bin entry. Command-line integration belongs in pragma-cli.

Peer dependencies

Requirements: Node ^22.13.0 || >=24.

Package-wide (the root entry and /eslint-config/next):

| Package | Range | | ----------------------- | ---------- | | eslint | ^10.11.0 | | typescript-eslint | ^8.70.1 | | eslint-plugin-sonarjs | ^4.0.0 | | eslint-plugin-unicorn | ^64.0.0 |

/eslint-config/next only (optional peers; install them when you use @pragmatiks/lint/eslint-config/next):

| Package | Range | Why | | ----------------------- | --------- | -------------------------------------------------------------------- | | @tailwindcss/node | ~4.3.3 | Compiles class strings with the project's own Tailwind design system | | tailwindcss | ~4.3.3 | Version check against the project | | @tailwindcss/postcss | ~4.3.3 | Version check against the project | | @eslint/css | ^2.0.0 | Lints .css files | | eslint-plugin-react-x | ^5.20.8 | Leaked conditional rendering (typed) |

The project's tailwindcss, its @tailwindcss/postcss and the @tailwindcss/node this package imports must be the same version, or loading the config throws. @eslint/css-tree is a regular dependency.

@pragmatiks/lint/eslint-config

The base config for JavaScript and TypeScript, also exported from the package root:

import { PRAGMATIKS_LINT_FILES, pragmatiksConfig } from "@pragmatiks/lint";

export default pragmatiksConfig();

Every object except the global ignores, the sonarjs and unicorn recommended presets included, applies only to PRAGMATIKS_LINT_FILES (**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}), so the config composes with configs for other languages such as CSS.

@pragmatiks/lint/eslint-config/next

In the app's eslint.config.mjs:

import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
import { pragmatiksNextConfig } from "@pragmatiks/lint/eslint-config/next";

export default await pragmatiksNextConfig([...nextVitals, ...nextTs], {
  rootDir: import.meta.dirname,
  tailwindStylesheet: "app/globals.css",
});

Run it with eslint . --max-warnings 0; the Next presets report some rules as warnings.

Composition, in order:

  1. The Next configs. Objects without files get the glob of the one object that registers jsx-a11y; the config throws unless there is exactly one. Ignore-only objects stay global.
  2. pragmatiksConfig() on **/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}. Its typescript-eslint parser replaces Next's Babel parser on JavaScript files too.
  3. The frontend rules on the jsx-a11y glob.
  4. react-x/no-leaked-conditional-rendering on **/*.{ts,tsx,mts} with typed linting (projectService, tsconfigRootDir: rootDir).
  5. The CSS rules on **/*.css (css/css, tolerant parsing).

Every config object you add after the result must set files, for example files: PRAGMATIKS_LINT_FILES from @pragmatiks/lint. An object without files also applies to .css files, and JavaScript rules crash on them.

Operating notes

  • Not --cache-safe across stylesheet edits. Class checks depend on the stylesheet, which ESLint's cache does not track.
  • Restart after editing a stylesheet. The design system loads when the config is built, so persistent ESLint processes and editor servers keep the old theme until restarted.
  • Tailwind bumps. Rerun the class-token corpus (tests/next-class-tokens.test.ts, AC-1) at the new version before widening the ~4.3.3 peer range. The config uses Tailwind's __unstable__loadDesignSystem; a load canary and the version check fail fast when it changes shape.

Focus patterns

  • P1, the only class pattern: focus-visible:outline-hidden focus-visible:ring-2. Optional extras: one project ring color (a theme color that is not from Tailwind's default palette, @theme inline colors included, ring-(--x) or ring-[var(--x)], with no / modifier), one ring-offset-<n> and one project offset color; a ring-offset-<n> above 0 needs the offset color, and ring-offset-0 allows it but needs none. Every in-scope token has exactly the focus-visible variant and no !.
  • C1, the only CSS pattern: every selector in the list ends in :focus-visible (an @variant focus-visible block counts as one), and the declarations are exactly outline: <non-zero width> solid var(--token) plus an optional outline-offset, without !important.

Rules

| Principle | Rule ID | Supported syntax | Fix | Gaps | | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | Form controls have an accessible name | pragmatiks/form-control-has-label | JSX input, textarea, select. Passes with a non-absent aria-label/aria-labelledby, a wrapping <label> with a matching htmlFor, a wrapping <label> without htmlFor (it labels only the first control inside it, as in HTML; controls on opposite branches of a ternary each count as the first), or a <label htmlFor> in the same component (the nearest enclosing function declaration or variable initialiser named with a capital letter, by its own or its variable's name, else the outermost enclosing function, so callbacks such as .map(), named ones included, count as their component) whose id is the same static string, the same variable (a const, prop, parameter or useId() result), or the same member expression or template literal over the same variables. undefined, null, "" and valueless attributes count as absent. Skips spread props and input types hidden, submit, reset, button, image. Known false positives: a component defined inside another component is a separate component, so it does not pair with a label the enclosing component renders; separate callbacks over the same data (a label list and an input list mapping one array) do not pair. Known false negative: E25. | Wrap in <label>, pair htmlFor/id, or add aria-label or aria-labelledby. A placeholder is not a label. | E9, E16, E25 | | Focus indicators use P1 | pragmatiks/focus-styling | Every string and template literal, compiled by the project's Tailwind, except values of JSX attributes that are not class attributes (class, className, *ClassName, *Classes); string keys of an object passed straight to a call (cn({ "outline-none": x })). Flags tokens whose selector contains :focus, that set outline*, --tw-outline-style, --tw-ring-*, --tw-inset-ring-*, forced-color-adjust or all, or whose shadow cannot compose the ring ([box-shadow:none], shadow-[none], [--tw-shadow:none]). Inline style objects and motion props (also behind as, satisfies, !, ternaries and &&): outline*, forcedColorAdjust, all, --tw-ring-* and --tw-inset-ring-* keys whatever their value, --tw-shadow/--tw-inset-shadow resets, every key in whileFocus except transition (including the variants entries a static whileFocus label or label array names; not followed: variants held in a variable, variants written as functions, labels inherited from a parent motion element, see E8 and E26), and boxShadow (vendor aliases included) on focus-styled elements. .style.<key> writes of the same keys. onFocus, onBlur, onFocusCapture and onBlurCapture handlers writing .style.*, and any handler writing .style.boxShadow on a focus-styled element; handlers inline, by name or wrapped in useCallback; a handler whose body is a single call to a function of the same module also counts that function's writes, followed transitively. | Use P1. Move decorative shadows to shadow-* classes. | E7, E8, E11, E20, E22, E26 | | Stylesheet focus uses C1 | pragmatiks/css-focus-styling | Rules whose selector contains :focus, and @variant focus, focus-visible and focus-within blocks. Outside them: outline*, forced-color-adjust, all, --tw-ring-*, --tw-inset-ring-*, --tw-shadow/--tw-inset-shadow resets, !important box-shadow, and box-shadow (vendor aliases included) not composing both var(--tw-ring-offset-shadow) and var(--tw-ring-shadow) that is unlayered, in a layer other than theme/base/components, or inside @keyframes. | Use C1. Put decorative shadows in @layer components, or use filter: drop-shadow(...) instead. | E7, E21, E24 | | Animate paint properties only | pragmatiks/animated-properties, pragmatiks/css-animated-properties | transition, transition-property, animation, animation-name in compiled class tokens and inline styles (vendor prefixes included); theme keyframes named by animate-* or by a stylesheet animation/animation-name (quoted names included); motion props initial, animate, exit, while* and every state of variants, also behind ternaries and &&; stylesheet transitions and @keyframes. Flags all, a layer without a property, layout properties, and layers that cannot be verified (var() only). none, CSS-wide keywords and custom properties pass. | Name the paint properties: transition-[opacity,transform], transition-colors. Animate transform, opacity or filter. | E2, E8, E10, E11, E12, E18 | | No vh | pragmatiks/viewport-units, pragmatiks/css-viewport-units | Any vh dimension in class tokens, inline styles and stylesheets, including var() fallbacks, custom property values and @property initial values. | dvh, svh or lvh, such as h-dvh or min-h-svh. | E8, E11 | | z-index at most 50 | pragmatiks/z-index, pragmatiks/css-z-index | z-index in class tokens, inline styles and stylesheets. Numbers ≤ 50, auto and calc(a * b) ≤ 50 pass; a var() passes when its theme value, or else its fallback, passes; anything else, CSS-wide keywords included, fails. | Use the scale up to 50 or a named layer such as z-(--layer). | E1, E8, E11, E23 | | Colors come from project tokens | pragmatiks/color-tokens, pragmatiks/css-color-tokens | literal: arbitrary values, properties and modifiers of class tokens that set a property able to hold a color (a var() with a fallback is judged by its fallback), with a hex color, a named color, or a color function with a literal channel or alpha, var() fallbacks included (relative color passes with a var() origin and keyword or var() channels; color-mix passes with var(), transparent, currentcolor operands; strings are ignored). defaultPalette: any Tailwind default-palette --color-* named by var(), theme() or --theme(), fallbacks included, in class tokens, inline styles and stylesheets; custom properties defined inside @theme may alias the palette, and a class token that names such an @theme inline alias, such as bg-danger, passes for the palette colors the alias references (other palette colors the token emits are still flagged). | A project theme color or bg-(--token). | E4, E8, E11 | | CSS lives in stylesheets | pragmatiks/no-embedded-style | JSX <style>. | Move the CSS to a project stylesheet. | — | | No @apply | pragmatiks/css-no-apply | @apply at-rules. | Write the classes in markup or plain declarations. | — | | Stylesheets parse fully | pragmatiks/css-parsed-fully | Blocks with unparsed (raw) children under tolerant parsing, such as an @utility with declarations after a nested rule, and nested rules whose selector swallowed the declarations before them. | Wrap declarations that sit before or between nested rules in & { … } where they stand, so the cascade order stays the same, or move @keyframes into an @theme block of their own. | — | | Accessibility | jsx-a11y recommended, jsx-a11y/control-has-associated-label (error, preset options plus select in ignoreElements, which pragmatiks/form-control-has-label checks), jsx-a11y/no-autofocus (ignoreNonDOM) | JSX. | Per rule. | — | | No leaked conditional render | react-x/no-leaked-conditional-rendering | **/*.{ts,tsx,mts}, typed. | count > 0 && … or a ternary. | E14 | | Copy and platform | no-restricted-syntax | ... after a letter in JSX text, strings and templates (not in href/src/action or import/export sources); onPaste calling preventDefault; userScalable: false, maximumScale: 1 (quoted keys included); <link href> to Google Fonts; "lorem ipsum" and "John Doe" in JSX text. | The … glyph; allow paste and zoom; next/font; real copy. | — | | React 19 and one icon family | no-restricted-imports, no-restricted-properties | forwardRef from react, React.forwardRef; imports from lucide-react, react-icons, @heroicons/*, @tabler/icons-react, @radix-ui/react-icons, react-feather, @fortawesome/*, @mui/icons-material. | Pass ref as a prop; use @phosphor-icons/react, the one icon family. | — | | No blocking dialogs | no-alert | alert, confirm, prompt. | An in-app dialog or toast. | — | | File names and undefined | unicorn/filename-case (kebab, Pascal, camel), unicorn/no-useless-undefined (checkArguments: false) | Files on the jsx-a11y glob. | Rename; undefined arguments are allowed. | E13 |

Exclusions

What the rules deliberately do not enforce. Reviewers cite these rows by number ("lint exclusion E7").

| # | Not enforced | Reason | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | E1 | z-index scale for values ≤ 50 | Local stacking contexts | | E2 | Paint-property transitions | Normal state feedback | | E3 | Size transitions (only if chosen in Q4) | Not applicable: Q4 chose to enforce | | E4 | Color and token literals in inline styles and CSS; non-color arbitrary tokens | PRA-526 | | E5 / E6 | Leaked render / CSS (only if Q2c / Q3c) | Not applicable: Q2a and Q3a were chosen | | E7 | Focus-indicator contrast; per-site clipping or obscuring | Browser proof is per pattern; reviewers check sites | | E8 | Style values held in variables or returned by calls | Needs data flow | | E9 | Form controls with spread props | Labelling is delegated to the caller | | E10 | Motion values not written inline | No reliable signature | | E11 | Class tokens next to ${} | Fragments are ambiguous | | E12 | Custom properties in transition lists | The consumer isn't traced | | E13 | Frontend rules on .cjs | The Next glob excludes it | | E14 | Leaked render outside ts/tsx/mts | Web's tsconfig scope | | E16 | Label values other than static-absent expressions | Treated as labelled | | E18 | JS animation names defined nowhere | Checked where the keyframes are defined | | E19 | --cache runs, and persistent ESLint processes or editors after a stylesheet edit | The design system loads when the config is built | | E20 | Focus appearance driven by React state; imperative styling other than .style.<prop>; handlers imported from other modules; handler wrappers other than useCallback | Not resolvable statically; removing the native indicator is still flagged | | E21 | Package CSS in node_modules | Not project source. The known @xyflow/react node and edge outline:none is neutralised by a C1 override in the app. | | E22 | Class-helper object keys written as identifiers (cn({ hidden: x })) and class objects not passed straight to a call | An identifier key cannot be told apart from an ordinary property name | | E23 | z-index var() that the theme does not define and that has no fallback | The value is set outside the theme, where the rule cannot read it | | E24 | @custom-variant aliases that resolve to a focus selector | Resolving custom variants needs the design system's variant graph, not the stylesheet alone | | E25 | Form-control components nested in a factory function and wrapped in a call such as forwardRef or memo, written as object property values, or written as classes | Only function declarations and variable initialisers are component boundaries, so these components pair with each other's labels | | E26 | whileFocus labels an element inherits from a parent motion element, and variants entries written as functions | Label propagation and variant resolution happen at runtime |

Known false positives

Fix these by rewriting the code:

  • In-scope styling outside P1/C1: decorative rings, !important shadows, split patterns, and decorative box-shadow in unlayered CSS or @keyframes.
  • Class-like words in prose strings.
  • a...b in URLs outside href/src/action.
  • Decorative box-shadow in a stylesheet that its importer layers (@import "./x.css" layer(components)): layers are judged per file, so it counts as unlayered.
  • Controls paired with a design-system label component (<Label htmlFor>): only a lowercase <label> pairs with a control. Use <label> or aria-labelledby.

Development

| Command | Purpose | | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | task js:check / task js:test / task js:build | Lint and type check / tests (Node >=22.15 for module.registerHooks; mise.toml pins Node 26) / build | | task js:pack | Install, build and npm pack the committed HEAD with CI's toolchain (Node 26, pnpm 12.6.0, latest npm) in a throwaway worktree, printing all three versions; the tarball lands in js/ |