ESLint

Plugin

We provide an ESLint plugin that enforces best-practices for accessibility and component consumption. This guide assumes you've already set up and configured ESLint. If not, we recommend starting with the configs section below.

In Development

This plugin is currently in development. New rules will be added over time. Refer to the changelog for updates.

Installation

Our configs are provided in ESM format. You will need "type": "module" in your project's package.json to consume them.

npm i --save-dev @qualcomm-ui/eslint-plugin-angular

Setup

Add the plugin to your ESLint configuration:

import angularEslint from "angular-eslint"
import {defineConfig} from "eslint/config"

import angularPlugin from "@qualcomm-ui/eslint-plugin-angular"

export default defineConfig([
  // ...the rest of your config
  {
    // your component files
    files: ["**/*.ts"],
    extends: [...angularEslint.configs.tsRecommended, angularPlugin.config],
    plugins: {"@angular-eslint": angularEslint.tsPlugin},
    processor: angularEslint.processInlineTemplates,
  },
  {
    // your component html files
    files: ["**/*.html"],
    extends: [angularEslint.configs.templateRecommended, angularPlugin.config],
  },
])

Rules

accessible-name

Enforces that certain QUI components have an aria-label or aria-labelledby attribute for accessibility.

Affected directives:

  • q-icon-button
  • q-inline-icon-button
  • q-header-bar-action-icon-button
<!-- Invalid -->
<button q-icon-button icon="close"></button>

<!-- Valid -->
<button q-icon-button icon="close" aria-label="Close dialog"></button>
<button q-icon-button icon="close" aria-labelledby="close-label"></button>
<button q-icon-button icon="close" [attr.aria-label]="closeLabel"></button>

avatar-image-alt

Enforces that q-avatar-image has an alt attribute for accessibility.

<!-- Invalid -->
<img q-avatar-image src="/user.jpg" />

<!-- Valid -->
<img q-avatar-image alt="John Doe" src="/user.jpg" />
<img q-avatar-image [alt]="userName" src="/user.jpg" />

input-label-association

Enforces that form input components have proper label association for accessibility. Supports both simple and compound component patterns.

Affected components and directives:

ComponentCompound LabelLabeled Control
q-text-inputq-text-input-labelq-text-input-input
q-number-inputq-number-input-labelq-number-input-input
q-password-inputq-password-input-labelq-password-input-input
q-comboboxq-combobox-labelq-combobox-input
q-selectq-select-labelq-select-control or q-select-hidden-select
q-switchq-switch-labelq-switch-hidden-input
q-checkboxq-checkbox-labelq-checkbox-hidden-input
q-radioq-radio-labelq-radio-hidden-input

Simple component usage:

<!-- Invalid -->
<q-text-input placeholder="Enter name"></q-text-input>

<!-- Valid -->
<q-text-input label="Full name" placeholder="Enter name"></q-text-input>
<q-text-input aria-label="Full name" placeholder="Enter name"></q-text-input>
<q-text-input [aria-label]="nameLabel" placeholder="Enter name"></q-text-input>
<q-combobox aria-label="Country"></q-combobox>
<q-combobox [aria-label]="countryLabel"></q-combobox>
<label q-switch aria-label="Enable notifications"></label>

<!-- Valid - projected label -->
<q-text-input>
  <label q-text-input-label>Full name</label>
</q-text-input>

Compound component usage:

<!-- Invalid -->
<div q-text-input-root>
  <input q-text-input-input placeholder="Enter name" />
</div>

<!-- Valid -->
<div q-text-input-root>
  <label q-text-input-label>Full name</label>
  <input q-text-input-input placeholder="Enter name" />
</div>

<label q-switch-root>
  <input q-switch-hidden-input aria-label="Enable notifications" />
  <div q-switch-control></div>
</label>

Projected controls on simple components can also provide the accessible name:

<q-select>
  <button q-select-control aria-label="Country"></button>
</q-select>

<label q-checkbox>
  <input q-checkbox-hidden-input aria-label="Accept terms" />
</label>

interactive-card-element-nesting

Disallows interactive elements inside q-card when the card itself is interactive.

<!-- Invalid -->
<button q-card interactive>
  <button q-button>Open</button>
</button>

<!-- Valid -->
<button q-card interactive>
  <div q-card-heading>Open</div>
</button>

prefer-alert-banner-button

Enforces q-alert-banner-button for alert banner actions.

<!-- Invalid -->
<div q-alert-banner>
  <button q-button>Retry</button>
</div>

<!-- Valid -->
<div q-alert-banner>
  <button q-alert-banner-button>Retry</button>
</div>

prefer-card-actions

Enforces q-card-button and q-card-link for direct QUI actions rendered inside q-card.

<!-- Invalid -->
<div q-card>
  <button q-button>Confirm</button>
  <a q-link href="/details">Details</a>
</div>

<!-- Valid -->
<div q-card>
  <button q-card-button>Confirm</button>
  <a q-card-link href="/details">Details</a>
</div>

prefer-header-bar-actions

Enforces q-header-bar-action-button and q-header-bar-action-icon-button inside q-header-bar-action-bar.

<!-- Invalid -->
<div q-header-bar-action-bar>
  <button q-button>Apps</button>
  <button q-icon-button aria-label="Settings"></button>
</div>

<!-- Valid -->
<div q-header-bar-action-bar>
  <button q-header-bar-action-button>Apps</button>
  <button q-header-bar-action-icon-button aria-label="Settings"></button>
</div>

prefer-menu-trigger-buttons

Enforces menu-specific trigger button components inside q-menu-trigger.

<!-- Invalid -->
<div q-menu-trigger>
  <button q-button>Open</button>
</div>

<!-- Valid -->
<div q-menu-trigger>
  <button q-menu-button>Open</button>
</div>

prefer-select-item-checkbox

Enforces q-select-item-checkbox when q-select-root statically sets selectionIndicator="checkbox".

<!-- Invalid -->
<div q-select-root selectionIndicator="checkbox">
  <span q-select-item-indicator></span>
</div>

<!-- Valid -->
<div q-select-root selectionIndicator="checkbox">
  <span q-select-item-checkbox></span>
</div>

These contextual component usage rules target direct QUI Angular selectors. Custom wrapper components are ignored.

Configs

We provide shared ESLint configurations to enforce consistent code style and quality across our internal projects. These packages wrap popular open source plugins and provide a common baseline that makes it easier for developers to move between codebases.

Overview

Two packages are available for Angular projects:

  • @qualcomm-ui/eslint-config-typescript - TypeScript rules
  • @qualcomm-ui/eslint-config-angular - Angular-specific rules

Both packages use ESLint's flat config format.

Installation

npm i --save-dev @qualcomm-ui/eslint-config-typescript @qualcomm-ui/eslint-config-angular eslint globals typescript-eslint

An example project featuring the complete configuration is available at https://github.com/qualcomm/qualcomm-ui-templates/tree/main/templates/angular-ssr

Skip ahead to the Example Configuration section for a complete setup, or continue reading to learn more about the individual configs.

Migrate from v2 to v3

Version 3 removes public base and split Angular configs. Each public config now includes the plugin, parser, processor, or formatting setup it needs.

  • Replace split Angular TypeScript configs with typescriptRecommended, and remove the array spread from @qualcomm-ui/eslint-config-typescript.
  • Replace split Angular template configs with templateRecommended:
import quiEslintAngular from "@qualcomm-ui/eslint-config-angular"
import quiEslintTs from "@qualcomm-ui/eslint-config-typescript"

export default defineConfig(
  // typescript
  {
    extends: [
      ...quiEslintTs.configs.recommended, 
      quiEslintAngular.configs.baseTypescript, 
      quiEslintAngular.configs.typescript, 
      quiEslintTs.configs.recommended, 
      quiEslintAngular.configs.typescriptRecommended, 
    ],
    // ...
  },

  // template
  {
    extends: [
      quiEslintAngular.configs.baseTemplate, 
      quiEslintAngular.configs.templatePrettier, 
      quiEslintAngular.configs.templateAttributeOrder, 
      quiEslintAngular.configs.templateSelfClosingTags, 
      quiEslintAngular.configs.templateRecommended, 
    ],
    // ...
  },
)

Config Deep Dive

TypeScript Configs

The @qualcomm-ui/eslint-config-typescript package exports the following configurations:

  • recommended: Recommended TypeScript rules. Composes each of the following:
    • styleGuide: Code style enforcement
    • sortKeys: Object key ordering
    • typeChecks: Type-aware linting rules
    • namingConventions: Identifier naming patterns
  • strictExports: Export restrictions
  • jsdoc: JSDoc comment validation

Each config can be consumed individually. The following example targets JS files.

import quiEslintTs from "@qualcomm-ui/eslint-config-typescript"

export default defineConfig({
  extends: [
    quiEslintTs.configs.styleGuide,
    quiEslintTs.configs.sortKeys,
    quiEslintTs.configs.namingConventions,
  ],
  files: ["**/*.{js,cjs,mjs}"],
  // ...
})

Angular Configs

The @qualcomm-ui/eslint-config-angular package exports the following configurations:

ConfigDescription
typescriptRecommendedAngular TypeScript rules with inline templates
templateRecommendedTemplate formatting, ordering, and closing rules

Type-Aware Rules

The TypeScript configs and Angular TypeScript config include type-aware rules that require type information. Files matched by your ESLint config's files patterns must be included in the nearest tsconfig.json (or referenced project if you're using project references).

If ESLint errors with "file not found in any configured project," ensure the file is included in your tsconfig's files or include patterns.

TypeScript Rules

The typescriptRecommended config enforces these conventions:

  • @angular-eslint/prefer-signals - Require Angular signals for reactive state
  • @angular-eslint/no-input-rename - Warn when renaming inputs
  • no-restricted-globals - Bans browser globals (window, document, navigator, location, localStorage, sessionStorage) for SSR compatibility

The following rules are disabled to allow flexibility:

  • @angular-eslint/component-class-suffix - Component class naming not enforced
  • @angular-eslint/directive-class-suffix - Directive class naming not enforced
  • @angular-eslint/no-host-metadata-property - Host bindings in metadata allowed
Template Attribute Order

The templateRecommended config enforces this order for template attributes:

<!-- 1. Structural directives -->
*ngIf="condition"

<!-- 2. Template references -->
#templateRef

<!-- 3. Attribute bindings -->
[attr.role]="role"

<!-- 4. Input bindings -->
[value]="value"

<!-- 5. Two-way bindings -->
[(ngModel)]="model"

<!-- 6. Output bindings -->
(click)="onClick()"

Attributes within each group are sorted alphabetically.

Self-Closing Tags

The templateRecommended config requires self-closing syntax for components without content:

<!-- Disallowed -->
<q-text-input></q-text-input>

<!-- Required -->
<q-text-input />

Example Configuration

Type-aware rules

This configuration uses type-aware linting. Learn more here about potential pitfalls.

Create an eslint.config.js file in your project root:

import {defineConfig} from "eslint/config"
import globals from "globals"
import * as tseslint from "typescript-eslint"

import quiEslintAngular from "@qualcomm-ui/eslint-config-angular"
import quiEslintTs from "@qualcomm-ui/eslint-config-typescript"
import quiEslintPluginAngular from "@qualcomm-ui/eslint-plugin-angular"

const tsLanguageOptions = {
  globals: globals.browser,
  parser: tseslint.parser,
  parserOptions: {
    projectService: true,
  },
}

export default defineConfig([
  {
    ignores: [
      "**/.angular/",
      "**/dist/",
      "**/node_modules/",
      "**/build/",
      "**/coverage/",
      "**/.turbo/",
      "**/out/",
      "**/out-tsc/",
      "**/temp/",
      "**/vite.config.ts.timestamp*",
    ],
  },
  {
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
    },
  },
  // JS
  {
    extends: [quiEslintTs.configs.sortKeys, quiEslintTs.configs.styleGuide],
    files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
    languageOptions: {globals: globals.browser},
  },
  // TS
  {
    extends: [
      quiEslintTs.configs.recommended,
      quiEslintTs.configs.strictExports,
    ],
    // recommendation: scope these to your source files in your package(s).
    files: ["**/*.{ts,tsx}"],
    languageOptions: tsLanguageOptions,
  },
  // Angular
  {
    extends: [
      quiEslintTs.configs.recommended,
      quiEslintAngular.configs.typescriptRecommended,
      // optional: include the plugin as well
      quiEslintPluginAngular.config,
    ],
    // recommendation: scope these to your source files in your package(s).
    files: ["**/*.ts"],
    languageOptions: tsLanguageOptions,
  },
  {
    extends: [
      quiEslintAngular.configs.templateRecommended,
      // optional: include the plugin as well
      quiEslintPluginAngular.config,
    ],
    // recommendation: scope these to your source files in your package(s).
    files: ["**/*.html"],
  },
])
Last updated on by Ryan Bower