├── .markdownlintignore ├── .gitignore ├── .husky └── pre-push ├── .markdownlint.json ├── .prettierignore ├── HOW_TO_PUBLISH_ON_NPM.md ├── lib ├── applicableComponents │ ├── dropdownBasedComponents.ts │ ├── linkBasedComponents.js │ ├── buttonBasedComponents.js │ ├── imageBasedComponents.js │ ├── inputBasedComponents.js │ ├── labelBasedComponents.ts │ ├── ariaLabelBasedComponent.js │ └── disabledFocusableComponents.ts ├── util │ ├── hasTriggerProp.ts │ ├── flattenChildren.ts │ ├── hasTooltipParent.ts │ ├── hasValidNestedProp.ts │ ├── hasFieldParent.ts │ ├── hasLoadingState.ts │ ├── hasTextContentChild.ts │ ├── hasDefinedProp.ts │ └── hasNonEmptyProp.ts └── rules │ ├── tag-needs-labelling.ts │ ├── swatchpicker-needs-labelling.ts │ ├── card-needs-accessible-name.ts │ ├── colorswatch-needs-labelling.ts │ ├── emptyswatch-needs-labelling.ts │ ├── image-needs-alt.ts │ ├── imageswatch-needs-labelling.ts │ ├── infolabel-needs-labelling.ts │ ├── buttons │ └── menu-button-needs-labelling.ts │ ├── field-needs-labelling.ts │ ├── no-empty-components.ts │ ├── tooltip-not-recommended.ts │ ├── toolbar-missing-aria.ts │ ├── visual-label-better-than-aria-suggestion.ts │ ├── rating-needs-name.ts │ ├── spin-button-unrecommended-labelling.ts │ ├── breadcrumb-needs-labelling.ts │ ├── spinner-needs-labelling.ts │ ├── avatar-needs-name.ts │ ├── accordion-item-needs-header-and-panel.ts │ ├── spin-button-needs-labelling.ts │ └── switch-needs-labelling.ts ├── .prettierrc.json ├── .vscode ├── settings.json └── extensions.json ├── scripts ├── utils │ └── kebabToKamelCase.js ├── boilerplate │ ├── util.js │ ├── doc.js │ ├── test.js │ └── rule.js ├── create-rule.md └── addRuleToIndex.js ├── tests └── lib │ └── rules │ ├── helper │ └── ruleTester.ts │ ├── spin-button-unrecommended-labelling.test.ts │ ├── utils │ ├── hasLabeledChild.ts │ ├── hasValidNestedProp.test.ts │ ├── hasDefinedProp.test.ts │ ├── flattenChildren.test.ts │ └── hasTooltipParent.test.ts │ ├── tooltip-not-recommended.test.ts │ ├── image-needs-alt.test.ts │ ├── field-needs-labelling.test.ts │ ├── rating-needs-name.test.ts │ ├── card-needs-accessible-name.test.ts │ ├── breadcrumb-needs-labelling.test.ts │ ├── buttons │ ├── menu-button-needs-labelling.test.ts │ └── compound-button-needs-labelling.test.ts │ ├── tag-needs-labelling.test.ts │ ├── counter-badge-needs-count.test.ts │ ├── avatar-needs-name.test.ts │ ├── menu-item-needs-labelling.test.ts │ ├── avoid-using-aria-describedby-for-primary-labelling.test.ts │ ├── accordion-item-needs-header-and-panel.test.ts │ ├── visual-label-better-than-aria-suggestion.test.ts │ ├── toolbar-missing-aria.test.ts │ ├── tablist-and-tabs-need-labelling.test.ts │ ├── prefer-aria-over-title-attribute.test.ts │ ├── spinner-needs-labelling.test.ts │ ├── dialogbody-needs-title-content-and-actions.test.ts │ ├── infolabel-needs-labelling.test.ts │ ├── switch-needs-labelling.test.ts │ ├── radio-button-missing-label.test.ts │ ├── radioGroup-missing-label.test.ts │ ├── badge-needs-accessible-name.test.ts │ ├── colorswatch-needs-labelling.test.ts │ ├── spin-button-needs-labelling.test.ts │ ├── checkbox-needs-labelling.test.ts │ └── tag-dismissible-needs-labelling.test.ts ├── docs └── rules │ ├── colorswatch-needs-labelling.md │ ├── visual-label-better-than-aria-suggestion.md │ ├── infolabel-needs-labelling.md │ ├── menu-button-needs-labelling.md │ ├── avatar-needs-name.md │ ├── no-empty-buttons.md │ ├── no-empty-components.md │ ├── image-needs-alt.md │ ├── breadcrumb-needs-labelling.md │ ├── rating-needs-name.md │ ├── menu-item-needs-labelling.md │ ├── counter-badge-needs-count.md │ ├── tooltip-not-recommended.md │ ├── toolbar-missing-aria.md │ ├── combobox-needs-labelling.md │ ├── card-needs-accessible-name.md │ ├── switch-needs-labelling.md │ ├── radio-button-missing-label.md │ ├── field-needs-labelling.md │ ├── tag-needs-name.md │ ├── dropdown-needs-labelling.md │ ├── image-button-missing-aria.md │ ├── checkbox-needs-labelling.md │ ├── spin-button-needs-labelling.md │ ├── spin-button-unrecommended-labelling.md │ ├── radiogroup-missing-label.md │ ├── tag-dismissible-needs-labelling.md │ ├── input-components-require-accessible-name.md │ ├── compound-button-needs-labelling.md │ ├── dialogbody-needs-title-content-and-actions.md │ ├── link-missing-labelling.md │ ├── swatchpicker-needs-labelling.md │ ├── spinner-needs-labelling.md │ ├── accordion-header-needs-labelling.md │ ├── tablist-and-tabs-need-labelling.md │ ├── badge-needs-accessible-name.md │ ├── accordion-item-needs-header-and-panel.md │ ├── avoid-using-aria-describedby-for-primary-labelling.md │ └── prefer-aria-over-title-attribute.md ├── CODE_OF_CONDUCT.md ├── jest.config.js ├── KNOWN_ISSUES.md ├── SUPPORT.md ├── tsconfig.json ├── .github └── workflows │ ├── code-coverage.yml │ ├── node.js.yml │ └── release-package.yml ├── LICENSE ├── .eslintrc.js ├── COVERAGE.md └── SECURITY.md /.markdownlintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | npm run build 2 | npm run lint 3 | npm test 4 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "markdownlint/style/prettier", 3 | "line-length": false 4 | } -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | *.txt 3 | *.zip 4 | .gitignore 5 | .npmrc 6 | .prettierignore 7 | node_modules/ 8 | package-lock.json 9 | yarn.lock -------------------------------------------------------------------------------- /HOW_TO_PUBLISH_ON_NPM.md: -------------------------------------------------------------------------------- 1 | # How to Publish on NPM 2 | 3 | ## Commands 4 | 5 | ```sh 6 | npm publish --scope @microsoft --access=public 7 | npm login --scope @microsoft 8 | ``` 9 | -------------------------------------------------------------------------------- /lib/applicableComponents/dropdownBasedComponents.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const dropdownBasedComponents = ["Dropdown"]; 5 | 6 | export { dropdownBasedComponents }; 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSameLine": true, 4 | "bracketSpacing": true, 5 | "endOfLine": "auto", 6 | "printWidth": 140, 7 | "semi": true, 8 | "tabWidth": 4, 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /lib/applicableComponents/linkBasedComponents.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const linkBasedComponents = ["Link", "a"]; // , "BreadcrumbButton" 5 | 6 | module.exports = { 7 | linkBasedComponents 8 | }; 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.updateImportsOnFileMove.enabled": "always", 3 | "editor.formatOnSave": true, 4 | "markdown.validate.enabled": true, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "prettier.requireConfig": true 7 | } 8 | -------------------------------------------------------------------------------- /scripts/utils/kebabToKamelCase.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | export const kebabToCamelCase = str => { 5 | return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase()).replace(/^(.)/, (_, char) => char.toLowerCase()); 6 | }; 7 | -------------------------------------------------------------------------------- /tests/lib/rules/helper/ruleTester.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import { RuleTester } from "eslint"; 5 | const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }); 6 | 7 | export default ruleTester; 8 | -------------------------------------------------------------------------------- /lib/applicableComponents/buttonBasedComponents.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const applicableComponents = ["Button", "ToggleButton", "CompoundButton", "MenuButton", "SplitButton"]; 5 | 6 | module.exports = { 7 | applicableComponents 8 | }; 9 | -------------------------------------------------------------------------------- /lib/applicableComponents/imageBasedComponents.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const fluentImageComponents = ["Image"]; 5 | const imageDomNodes = ["img"]; 6 | 7 | module.exports = { 8 | fluentImageComponents, 9 | imageDomNodes 10 | }; 11 | -------------------------------------------------------------------------------- /docs/rules/colorswatch-needs-labelling.md: -------------------------------------------------------------------------------- 1 | # Accessibility: ColorSwatch must have an accessible name via aria-label, Tooltip, aria-labelledby, etc. (`@microsoft/fluentui-jsx-a11y/colorswatch-needs-labelling`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | -------------------------------------------------------------------------------- /lib/applicableComponents/inputBasedComponents.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const applicableComponents = ["Input", "Slider", "DatePicker", "Textarea", "TextField", "TimePicker", "SearchBox", "Select"]; 5 | 6 | module.exports = { 7 | applicableComponents 8 | }; 9 | -------------------------------------------------------------------------------- /lib/applicableComponents/labelBasedComponents.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const labelBasedComponents = ["Label", "label"]; 5 | const elementsUsedAsLabels = ["div", "span", "p", "h1", "h2", "h3", "h4", "h5", "h6"]; 6 | 7 | export { labelBasedComponents, elementsUsedAsLabels }; 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "firsttris.vscode-jest-runner", 5 | "esbenp.prettier-vscode", 6 | "rvest.vs-code-prettier-eslint", 7 | "DavidAnson.vscode-markdownlint", 8 | "streetsidesoftware.code-spell-checker-cspell-bundled-dictionaries" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /scripts/boilerplate/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper method to convert LF line endings to CRLF line endings 3 | * @remarks This is needed to avoid prettier formatting issues on generation of the files 4 | * @param text The text to convert 5 | * @returns The converted text 6 | */ 7 | const withCRLF = text => text.replace(/\n/g, "\r\n"); 8 | 9 | module.exports = { withCRLF }; 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | module.exports = { 5 | preset: "ts-jest", 6 | testEnvironment: "node", 7 | testMatch: ["**/tests/**/*.test.ts"], 8 | verbose: true, 9 | collectCoverage: true, 10 | coverageThreshold: { 11 | global: { 12 | branches: 80, 13 | functions: 80, 14 | lines: 80, 15 | statements: 80 16 | } 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /lib/applicableComponents/ariaLabelBasedComponent.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | const { applicableComponents: ButtonBasedComponents } = require("./buttonBasedComponents"); 5 | const { applicableComponents: InputBasedComponents } = require("./inputBasedComponents"); 6 | 7 | const applicableComponents = ["SpinButton", ...ButtonBasedComponents, ...InputBasedComponents]; 8 | 9 | module.exports = { 10 | applicableComponents 11 | }; 12 | -------------------------------------------------------------------------------- /KNOWN_ISSUES.md: -------------------------------------------------------------------------------- 1 | # Known Issues 2 | 3 | ## No object props deconstruction 4 | 5 | We currently do not support object props deconstruction. 6 | 7 | e.g. 8 | 9 | ```tsx 10 | const buttonProps = { 11 | icon: , 12 | aria-label: 'start date' 13 | }; 14 | 15 | 19 | ``` 20 | 21 | ```jsx 22 | 27 | ``` 28 | 29 | ```jsx 30 | 31 | ``` 32 | 33 | Examples of **correct** code for this rule: 34 | 35 | ```jsx 36 | 37 | ``` 38 | 39 | ```jsx 40 | 41 | ``` 42 | 43 | ```jsx 44 | 49 | ``` 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /docs/rules/no-empty-components.md: -------------------------------------------------------------------------------- 1 | # FluentUI components should not be empty (`@microsoft/fluentui-jsx-a11y/no-empty-components`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | Provide labels to identify all form controls, including text fields, checkboxes, radio buttons, and drop-down menus. In most cases, this is done by using the label element. 8 | 9 | 10 | 11 | ## Rule Details 12 | 13 | This rule aims to prevent missing text and info for users. 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```jsx 18 | 19 | ``` 20 | 21 | ```jsx 22 | 23 | ``` 24 | 25 | ```jsx 26 | 27 | ``` 28 | 29 | Examples of **correct** code for this rule: 30 | 31 | ```jsx 32 | 33 | ``` 34 | 35 | ```jsx 36 | 37 | ``` 38 | 39 | ```jsx 40 | This is an example of the Text component's usage. 41 | ``` 42 | 43 | ```jsx 44 | 45 | 46 | Item 1 47 | 48 | 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/rules/image-needs-alt.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Image must have alt attribute with a meaningful description of the image. If the image is decorative, use alt="" (`@microsoft/fluentui-jsx-a11y/image-needs-alt`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | ## Rule details 8 | 9 | This rule requires all `` components have non-empty alternative text. The `alt` attribute should provide a clear and concise text replacement for the image's content. It should *not* describe the presence of the image itself or the file name of the image. Purely decorative images should have empty `alt` text (`alt=""`). 10 | 11 | 12 | Examples of **incorrect** code for this rule: 13 | 14 | ```jsx 15 | 16 | ``` 17 | 18 | ```jsx 19 | {null} 20 | ``` 21 | 22 | Examples of **correct** code for this rule: 23 | 24 | ```jsx 25 | A dog playing in a park. 26 | ``` 27 | 28 | ```jsx 29 | 30 | ``` 31 | 32 | ## Further Reading 33 | 34 | - [`` Accessibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#accessibility) 35 | -------------------------------------------------------------------------------- /docs/rules/breadcrumb-needs-labelling.md: -------------------------------------------------------------------------------- 1 | # All interactive elements must have an accessible name (`@microsoft/fluentui-jsx-a11y/breadcrumb-needs-labelling`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | Provide labels to identify all form controls, including text fields, checkboxes, radio buttons, and drop-down menus. In most cases, this is done by using the label element. 8 | 9 | 10 | 11 | All interactive elements must have an accessible name. 12 | 13 | ## Rule Details 14 | 15 | This rule aims to... 16 | 17 | Examples of **incorrect** code for this rule: 18 | 19 | ```jsx 20 |
21 |
24 | ``` 25 | 26 | ```jsx 27 | 28 | 29 | ``` 30 | 31 | Examples of **correct** code for this rule: 32 | 33 | ```jsx 34 | 35 | ``` 36 | 37 | ```jsx 38 |
39 |
42 | ``` 43 | -------------------------------------------------------------------------------- /docs/rules/rating-needs-name.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Ratings must have accessible labelling: name, aria-label, aria-labelledby or itemLabel which generates aria-label (`@microsoft/fluentui-jsx-a11y/rating-needs-name`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | All interactive elements must have an accessible name. 8 | 9 | ## Rule Details 10 | 11 | This rule aims to enforce that a Rating element must have an accessible label associated with it. 12 | 13 | Examples of **incorrect** code for this rule: 14 | 15 | ```js 16 | 17 | ``` 18 | 19 | Examples of **correct** code for this rule: 20 | 21 | ```js 22 | `Rating of ${number} starts`} /> 23 | ``` 24 | 25 | FluentUI supports receiving a function that will add the aria-label to the element with the number. This prop is called itemLabel. 26 | If this is not the desired route, a name, aria-label or aria-labelledby can be added instead. 27 | 28 | ## When Not To Use It 29 | 30 | You might want to turn this rule off if you don't intend for this component to be read by screen readers. 31 | 32 | ## Further Reading 33 | 34 | - [ARIA in HTML](https://www.w3.org/TR/html-aria/) 35 | -------------------------------------------------------------------------------- /docs/rules/menu-item-needs-labelling.md: -------------------------------------------------------------------------------- 1 | # Accessibility: MenuItem without label must have an accessible and visual label: aria-labelledby (`@microsoft/fluentui-jsx-a11y/menu-item-needs-labelling`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | Accessibility: MenuItem must have a visual label and it needs to be linked via aria-labelledby 8 | 9 | 10 | 11 | ## Ways to fix 12 | 13 | - Add a label with an id, add the aria-labelledby having same value as id to MenuItem. 14 | 15 | ## Rule Details 16 | 17 | This rule aims to make MenuItem accessible 18 | 19 | Examples of **incorrect** code for this rule: 20 | 21 | ```jsx 22 | 23 | ``` 24 | 25 | ```jsx 26 | } onClick={handleClick}> 27 | ``` 28 | 29 | ```jsx 30 | 31 | ``` 32 | 33 | Examples of **correct** code for this rule: 34 | 35 | ```jsx 36 | <> 37 | 38 | } onClick={handleClick}> 39 | Settings 40 | 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/rules/counter-badge-needs-count.md: -------------------------------------------------------------------------------- 1 | # @microsoft/fluentui-jsx-a11y/counter-badge-needs-count 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). 6 | 7 | 8 | 9 | A counter badge is a badge that displays a numerical count. 10 | 11 | ## Rule Details 12 | 13 | Ensure that the `CounterBadge` component is accessible. 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```jsx 18 | 19 | ``` 20 | 21 | ```jsx 22 | } /> 23 | ``` 24 | 25 | 26 | Examples of **correct** code for this rule: 27 | 28 | If the badge contains a custom icon, that icon must be given alternative text with aria-label, unless it is purely presentational: 29 | 30 | ```jsx 31 | } count={5} /> 32 | ``` 33 | 34 | ```jsx 35 | 36 | ``` 37 | 38 | ```jsx 39 | 5 40 | ``` 41 | 42 | ## Further Reading 43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/util/hasLoadingState.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import { TSESTree } from "@typescript-eslint/utils"; 5 | import { hasNonEmptyProp } from "./hasNonEmptyProp"; 6 | 7 | /** 8 | * Common prop names that indicate a loading state in FluentUI components 9 | */ 10 | const LOADING_STATE_PROPS = ["loading", "isLoading", "pending", "isPending", "busy", "isBusy"] as const; 11 | 12 | /** 13 | * Determines if the component has any loading state indicator prop 14 | * @param attributes - JSX attributes array 15 | * @returns boolean indicating if component has loading state 16 | */ 17 | export const hasLoadingState = (attributes: TSESTree.JSXOpeningElement["attributes"]): boolean => { 18 | return LOADING_STATE_PROPS.some(prop => hasNonEmptyProp(attributes, prop)); 19 | }; 20 | 21 | /** 22 | * Gets the specific loading prop that is present (for better error messages) 23 | * @param attributes - JSX attributes array 24 | * @returns string name of the loading prop found, or null if none 25 | */ 26 | export const getLoadingStateProp = (attributes: TSESTree.JSXOpeningElement["attributes"]): string | null => { 27 | return LOADING_STATE_PROPS.find(prop => hasNonEmptyProp(attributes, prop)) ?? null; 28 | }; 29 | -------------------------------------------------------------------------------- /docs/rules/tooltip-not-recommended.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Prefer text content or aria over a tooltip for these components MenuItem, SpinButton (`@microsoft/fluentui-jsx-a11y/tooltip-not-recommended`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | All interactive elements must have an accessible name. 8 | 9 | Tooltip not recommended for these components: MenuItem, SpinButton, etc. 10 | 11 | Prefer text content or aria over a tooltip for these components. 12 | 13 | 14 | 15 | ## Rule Details 16 | 17 | This rule aims to prevent the usage of Tooltip. 18 | 19 | Examples of **incorrect** code for this rule: 20 | 21 | ```jsx 22 | 23 | 24 | 25 | ``` 26 | 27 | ```jsx 28 | 29 | 30 | 31 | ``` 32 | 33 | Examples of **correct** code for this rule: 34 | 35 | ```jsx 36 |
37 |
40 | ``` 41 | 42 | ```jsx 43 |
44 |
47 | ``` 48 | -------------------------------------------------------------------------------- /docs/rules/toolbar-missing-aria.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Toolbars need accessible labelling: aria-label or aria-labelledby (`@microsoft/fluentui-jsx-a11y/toolbar-missing-aria`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | 8 | 9 | The toolbar role needs an accessible name, especially if there are multiple toolbars on a screen. 10 | 11 | ## Rule Details 12 | 13 | This rule aims to prevent a confusing user experience for tool bars. 14 | 15 | Examples of **incorrect** code for this rule: 16 | 17 | ```jsx 18 | 19 | } name="textOptions" value="bold" /> 20 | 21 | ``` 22 | 23 | Examples of **correct** code for this rule: 24 | 25 | ```jsx 26 | 27 | } name="textOptions" value="bold" /> 28 | 29 | ``` 30 | 31 | ```jsx 32 | 33 | 34 | } name="textOptions" value="bold" /> 35 | 36 | ``` 37 | -------------------------------------------------------------------------------- /tests/lib/rules/spin-button-unrecommended-labelling.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | //------------------------------------------------------------------------------ 5 | // Requirements 6 | //------------------------------------------------------------------------------ 7 | 8 | import { Rule } from "eslint"; 9 | import ruleTester from "./helper/ruleTester"; 10 | import rule from "../../../lib/rules/spin-button-unrecommended-labelling"; 11 | 12 | //------------------------------------------------------------------------------ 13 | // Tests 14 | //------------------------------------------------------------------------------ 15 | 16 | ruleTester.run("spin-button-unrecommended-labelling", rule as unknown as Rule.RuleModule, { 17 | valid: [], 18 | invalid: [ 19 | { 20 | code: ``, 21 | errors: [{ messageId: "unRecommendedlabellingSpinButton" }] 22 | }, 23 | { 24 | code: `<>This is a spin button`, 25 | errors: [{ messageId: "unRecommendedlabellingSpinButton" }] 26 | } 27 | ] 28 | }); 29 | -------------------------------------------------------------------------------- /docs/rules/combobox-needs-labelling.md: -------------------------------------------------------------------------------- 1 | # All interactive elements must have an accessible name (`@microsoft/fluentui-jsx-a11y/combobox-needs-labelling`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | Provide labels to identify all form controls, including text fields, checkboxes, radio buttons, and drop-down menus. In most cases, this is done by using the label element. 8 | 9 | 10 | 11 | All interactive elements must have an accessible name. 12 | 13 | ## Rule Details 14 | 15 | This rule aims to... 16 | 17 | Examples of **incorrect** code for this rule: 18 | 19 | ```jsx 20 | 21 | 22 | ``` 23 | 24 | Examples of **correct** code for this rule: 25 | 26 | ```jsx 27 | 28 | 29 | 30 | 31 | ``` 32 | 33 | ```jsx 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/rules/card-needs-accessible-name.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Interactive Card must have an accessible name via aria-label, aria-labelledby, etc (`@microsoft/fluentui-jsx-a11y/card-needs-accessible-name`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | Interactive Card components must have an accessible name for screen readers. 8 | 9 | ## Rule Details 10 | 11 | This rule enforces that Card components have proper accessible names when they are interactive (clickable). 12 | 13 | ### Noncompliant 14 | 15 | ```jsx 16 | 17 | 18 | Card title 19 | 20 | 21 | ``` 22 | 23 | ### Compliant 24 | 25 | ```jsx 26 | 27 | 28 | Card title 29 | 30 | 31 | 32 | 33 | 34 | Card title 35 | 36 | 37 | ``` 38 | 39 | ## When Not To Use 40 | 41 | If the Card is purely decorative and not interactive, this rule is not necessary. 42 | 43 | ## Accessibility guidelines 44 | 45 | - [WCAG 4.1.2](https://www.w3.org/WAI/WCAG21/Understanding/name-role-value.html) 46 | -------------------------------------------------------------------------------- /docs/rules/switch-needs-labelling.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Switch must have an accessible label (`@microsoft/fluentui-jsx-a11y/switch-needs-labelling`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | All interactive elements must have an accessible name. 8 | 9 | Switch components need a visual label. 10 | 11 | Please add label, or aria-labelledby. 12 | 13 | 14 | 15 | ## Rule Details 16 | 17 | This rule aims to... 18 | 19 | Examples of **incorrect** code for this rule: 20 | 21 | ```jsx 22 | 23 | ``` 24 | 25 | ```jsx 26 | 27 | 28 | 32 | 33 | ``` 34 | 35 | Examples of **correct** code for this rule: 36 | 37 | ```jsx 38 | 39 | ``` 40 | 41 | ```jsx 42 | 43 | 44 | 49 | 50 | ``` 51 | 52 | ````jsx 53 | 54 | 55 | 56 | ``` 57 | ```` 58 | -------------------------------------------------------------------------------- /docs/rules/radio-button-missing-label.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Radio button without label must have an accessible and visual label: aria-labelledby (`@microsoft/fluentui-jsx-a11y/radio-button-missing-label`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | All interactive elements must have an accessible name. 8 | 9 | Radio button without a label or accessible labeling lack an accessible name. 10 | 11 | 12 | 13 | ## Ways to fix 14 | 15 | - Add a label, aria-label or aria-labelledby attribute to the Radio tag. 16 | 17 | ## Rule Details 18 | 19 | This rule aims to make Radioes accessible. 20 | 21 | Examples of **incorrect** code for this rule: 22 | 23 | ```jsx 24 | 25 | 26 | ``` 27 | 28 | ```jsx 29 | 30 | 34 | ``` 35 | 36 | Examples of **correct** code for this rule: 37 | 38 | ```jsx 39 | 40 | ``` 41 | 42 | ```jsx 43 | 44 | 49 | ``` 50 | -------------------------------------------------------------------------------- /tests/lib/rules/utils/hasLabeledChild.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/types"; 5 | import { TSESLint } from "@typescript-eslint/utils"; 6 | import { hasLabeledChild } from "../../../../lib/util/hasLabeledChild"; 7 | 8 | // helper for loc/range 9 | const mockLocRange = () => ({ 10 | loc: { 11 | start: { line: 0, column: 0 }, 12 | end: { line: 0, column: 0 } 13 | }, 14 | range: [0, 0] as [number, number] 15 | }); 16 | 17 | // minimal JSXOpeningElement: 18 | const openingEl: TSESTree.JSXOpeningElement = { 19 | type: AST_NODE_TYPES.JSXOpeningElement, 20 | name: { 21 | type: AST_NODE_TYPES.JSXIdentifier, 22 | name: "img", 23 | ...mockLocRange() 24 | }, 25 | attributes: [], // no props 26 | selfClosing: true, // 27 | ...mockLocRange() 28 | }; 29 | 30 | // minimal RuleContext mock 31 | const mockContext = { 32 | report: jest.fn(), 33 | getSourceCode: jest.fn() 34 | } as unknown as TSESLint.RuleContext; 35 | 36 | describe("hasLabeledChild", () => { 37 | it("returns false for a self-closing with no labeled children", () => { 38 | expect(hasLabeledChild(openingEl, mockContext)).toBe(false); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /docs/rules/field-needs-labelling.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Field must have label (`@microsoft/fluentui-jsx-a11y/field-needs-labelling`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | Field must have `label` prop. 8 | 9 | 10 | 11 | ## Ways to fix 12 | 13 | - Make sure that Field component has following props: 14 | - `label` 15 | 16 | ## Rule Details 17 | 18 | This rule aims to make Field component accessible. 19 | 20 | Examples of **incorrect** code for this rule: 21 | 22 | ```jsx 23 | 24 | 25 | 26 | ``` 27 | 28 | ```jsx 29 | 30 | 31 | 32 | ``` 33 | 34 | Examples of **correct** code for this rule: 35 | 36 | ```jsx 37 | 38 | 39 | 40 | ``` 41 | 42 | ```jsx 43 | 44 | 45 | 46 | ``` 47 | 48 | ```jsx 49 | 50 | 51 | 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/rules/tag-needs-name.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Tag must have an accessible name (`@microsoft/fluentui-jsx-a11y/tag-needs-name`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | All interactive elements must have an accessible name. 8 | 9 | Tag components need an accessible name for screen reader users. 10 | 11 | Please provide text content, aria-label, or aria-labelledby. 12 | 13 | 14 | 15 | ## Rule Details 16 | 17 | This rule aims to ensure that Tag components have an accessible name via text content, aria-label, or aria-labelledby. 18 | 19 | Examples of **incorrect** code for this rule: 20 | 21 | ```jsx 22 | 23 | ``` 24 | 25 | ```jsx 26 | 27 | ``` 28 | 29 | ```jsx 30 | 31 | ``` 32 | 33 | ```jsx 34 | }> 35 | ``` 36 | 37 | ```jsx 38 | } /> 39 | ``` 40 | 41 | Examples of **correct** code for this rule: 42 | 43 | ```jsx 44 | Tag with some text 45 | ``` 46 | 47 | ```jsx 48 | 49 | ``` 50 | 51 | ```jsx 52 | Some text 53 | ``` 54 | 55 | ```jsx 56 | }>Tag with icon and text 57 | ``` 58 | 59 | ```jsx 60 | } aria-label="Settings tag"> 61 | ``` 62 | -------------------------------------------------------------------------------- /tests/lib/rules/tooltip-not-recommended.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | //------------------------------------------------------------------------------ 5 | // Requirements 6 | //------------------------------------------------------------------------------ 7 | 8 | import { Rule } from "eslint"; 9 | import ruleTester from "./helper/ruleTester"; 10 | import rule from "../../../lib/rules/tooltip-not-recommended"; 11 | 12 | //------------------------------------------------------------------------------ 13 | // Tests 14 | //------------------------------------------------------------------------------ 15 | 16 | ruleTester.run("tooltip-not-recommended", rule as unknown as Rule.RuleModule, { 17 | valid: [ 18 | // Valid cases 19 | '
', 20 | '
' 21 | ], 22 | invalid: [ 23 | // Invalid cases 24 | { 25 | code: '', 26 | errors: [{ messageId: "tooltipNotRecommended" }] 27 | }, 28 | { 29 | code: '', 30 | errors: [{ messageId: "tooltipNotRecommended" }] 31 | } 32 | ] 33 | }); 34 | -------------------------------------------------------------------------------- /docs/rules/dropdown-needs-labelling.md: -------------------------------------------------------------------------------- 1 | # Accessibility: Dropdown menu must have an id and it needs to be linked via htmlFor of a Label (`@microsoft/fluentui-jsx-a11y/dropdown-needs-labelling`) 2 | 3 | 💼 This rule is enabled in the ✅ `recommended` config. 4 | 5 | 6 | 7 | Accessibility: Dropdown menu must have a visual label and it needs to be linked via htmlFor aria-labelledby of Label Or Dropdown mush have aria-label 8 | Dropdown having label linked via htmlFor in Label is recommended 9 | 10 | 11 | 12 | ## Ways to fix 13 | 14 | - Add a label with htmlFor, add the id having same value as htmlFor to dropdown. 15 | - Add a label with id, add the aria-labelledby having same value as id to dropdown. 16 | - Add a aria-label to dropdown 17 | 18 | ## Rule Details 19 | 20 | This rule aims to make dropdown accessible 21 | 22 | Examples of **incorrect** code for this rule: 23 | 24 | ```jsx 25 | 26 | 27 | <> 28 |