Skip to content

ESLint Plugin

@airframeui/eslint-plugin works in JSX and Angular 20+ templates. Same seven rules, same version as @airframeui/core: leftover Tailwind/Bootstrap, unknown af-* names, structure classes, redundant defaults, and field-error ARIA.

  • JSX — string literals on class / className in .js / .jsx / .ts / .tsx
  • Angular — class="…", [class]="'…'", [class.token], and [ngClass] in *.html, including @if / @for / @switch / @defer. Inline template strings on @Component work when angular-eslint extracts them.

Install angular-eslint for templates. The plugin does not parse .html as JavaScript. Vue bindings, class:list, and [class]="someVar" are out of scope — use MCP validate_markup for those hosts.

npm install -D @airframeui/eslint-plugin
// eslint.config.js
import airframe from '@airframeui/eslint-plugin';

export default [
  airframe.configs['flat/recommended'],
];

JSX. flat/recommended turns on the rules and JSX parsing for .js / .jsx / .ts / .tsx. Drop it into an existing export default [ … ] array.

Angular (v20+). Use flat/angular with angular-eslint. That preset matches *.html and does not set a parser — templateRecommended (or languageOptions.parser from @angular-eslint/template-parser) must already apply. processInlineTemplates on *.ts covers inline template: strings. Full CSS and template setup: Angular.

An app that has both JSX and Angular can spread flat/recommended and flat/angular in the same config.

JSX only. Angular templates need flat config (angular-eslint no longer supports eslintrc).

// .eslintrc.cjs
module.exports = {
  plugins: ['@airframeui'],
  extends: ['plugin:@airframeui/recommended'],
};

Rules with a fix rewrite the class string. In Angular that includes static class="…" and [class]="'…'" / [ngClass]="'…'" string literals. [class.token] and [ngClass] object maps are reported without a rewrite. Run npx eslint . --fix or the editor Fix all auto-fixable problems action.

Autofix is only attached when the replacement is deterministic: known aliases, catalog heuristics, structure classes, redundant defaults, and redundant width. Unknown names with only a fuzzy guess, and field-error ARIA, are reported without a fix.

All seven ship in flat/recommended (JSX) and flat/angular (templates).

RuleRecommendedAutofixWhat it does
@airframeui/no-bootstrap-classeswarnnoLeftover Bootstrap tokens in JSX class / className and Angular class / [class] / [ngClass]
@airframeui/no-redundant-defaultswarnyesRestated defaults: af-card af-stack, af-btn af-is-primary, af-container af-container-xl, matching af-text-h*
@airframeui/no-redundant-widthwarnyesaf-w-full on form controls; af-block on af-btn
@airframeui/no-tailwind-classeswarnnoLeftover Tailwind utilities in JSX class / className and Angular class / [class] / [ngClass]
@airframeui/no-unknown-airframe-classerroryes*Invented af-* names. Autofix for aliases (af-btn-danger → af-is-danger)
@airframeui/prefer-airframe-layout-recipeswarnyesRewrites flex flex-col, grid grid-cols-*, row, etc. to structure classes
@airframeui/require-field-error-ariaerrornoaf-field__error needs id; error inputs need aria-describedby

Before and after for @airframeui/prefer-airframe-layout-recipes

BeforeAfter
flex flex-col gap-4af-stack af-gap-lg
flex flex-wrap gap-2af-inline af-gap-sm
grid grid-cols-3 gap-6af-grid af-grid-3 af-gap-xl
d-flex flex-columnaf-stack
containeraf-container

Gap numbers map to the Airframe named scale (0 · xs · sm · md · lg · xl · 2xl · 3xl · 4xl). Same mapping as the Tailwind and Bootstrap cheat sheets.

  • Not Vue/Svelte bindings, class:list, template literals, Astro expression attributes, or Angular [class]="variable" (non-literal). For other HTML hosts, use MCP validate_markup.
  • @airframeui/no-unknown-airframe-class prefers the project’s @airframeui/core/classes.json, then the snapshot generated from core at plugin build time (same classes.json + token catalog as IntelliSense for Airframe). The plugin shares a version with core / tokens.
  • Tailwind/Bootstrap detectors ignore existing af-* tokens so legitimate Airframe markup is not flagged.