├── .gitignore ├── jest.config.js ├── .github ├── CODEOWNERS ├── workflows │ ├── ci.yml │ └── test-accessibility-alt-text-bot.yml └── dependabot.yaml ├── src ├── index.js ├── validate.js └── index.test.js ├── CONTRIBUTING.md ├── SUPPORT.md ├── package.json ├── LICENSE.txt ├── queries.sh ├── .all-contributorsrc ├── SECURITY.md ├── CODE_OF_CONDUCT.md ├── README.md └── action.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | export default { transform: {} } -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @github/accessibility-reviewers -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { validate } from "./validate.js"; 2 | 3 | const [content] = process.argv.slice(2); 4 | const [config] = process.argv.slice(3); 5 | 6 | 7 | const run = async () => { 8 | console.log((await validate(content, config))); 9 | }; 10 | 11 | run(); 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: ["main"] 5 | workflow_dispatch: 6 | pull_request: 7 | jobs: 8 | tests: 9 | name: Run unit tests 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repo 13 | uses: actions/checkout@v6 14 | - name: Install dependencies 15 | run: npm ci 16 | - name: Run test 17 | run: npm run test 18 | permissions: 19 | contents: read 20 | pull-requests: write 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 4 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 5 | 6 | 7 | ## Submitting a pull request 8 | 9 | 1. Clone the repository 10 | 1. Make sure the tests pass on your machine by running: `./test-flag-alt-text.sh` 11 | 1. Create a new branch: `git checkout -b my-branch-name` 12 | 1. Make your change, add tests, and make sure the tests still pass 13 | 1. Push up your changes and [submit a pull request](https://github.com/github/accessibility-alt-text-bot/compare) 14 | 1. Pat your self on the back and wait for your pull request to be reviewed and merged. 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | commit-message: 8 | prefix: "chore(deps)" 9 | groups: 10 | dependencies: 11 | applies-to: version-updates 12 | update-types: 13 | - "minor" 14 | - "patch" 15 | - package-ecosystem: 'github-actions' 16 | directory: '/' 17 | schedule: 18 | interval: 'weekly' 19 | commit-message: 20 | prefix: "chore(deps)" 21 | groups: 22 | dependencies: 23 | applies-to: version-updates 24 | update-types: 25 | - "minor" 26 | - "patch" 27 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | 2 | # Support 3 | 4 | ## How to file issues and get help 5 | 6 | This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue. 7 | 8 | For help or questions about using this project, please an issue. 9 | 10 | - **accessibility-alt-text-bot** is under active development and maintained by GitHub staff **AND THE COMMUNITY**. We will do our best to respond to support, feature requests, and community questions in a timely manner. 11 | 12 | 13 | ## GitHub Support Policy 14 | 15 | Support for this project is limited to the resources listed above. 16 | -------------------------------------------------------------------------------- /.github/workflows/test-accessibility-alt-text-bot.yml: -------------------------------------------------------------------------------- 1 | name: Test Accessibility-alt-text-bot 2 | on: 3 | issues: 4 | types: [opened, edited] 5 | pull_request: 6 | types: [opened, edited] 7 | issue_comment: 8 | types: [created, edited, deleted] 9 | discussion: 10 | types: [created, edited] 11 | discussion_comment: 12 | types: [created, edited, deleted] 13 | 14 | jobs: 15 | accessibility_alt_text_bot: 16 | name: Check alt text is set on issue or pull requests 17 | runs-on: ubuntu-latest 18 | if: ${{ !endsWith(github.actor, '[bot]') }} 19 | steps: 20 | - name: Check alt text 21 | uses: github/accessibility-alt-text-bot@main 22 | permissions: 23 | contents: read 24 | pull-requests: write 25 | discussions: write 26 | issues: write 27 | -------------------------------------------------------------------------------- /src/validate.js: -------------------------------------------------------------------------------- 1 | import markdownIt from "markdown-it"; 2 | import { lint } from "markdownlint/sync"; 3 | import githubMarkdownLint from "@github/markdownlint-github"; 4 | import yaml from "js-yaml"; 5 | const markdownItFactory = () => markdownIt({ html: true }); 6 | 7 | export const validate = (markdown, config) => { 8 | const configObject = yaml.load(config); 9 | return ( 10 | lint({ 11 | strings: { 12 | content: markdown, 13 | }, 14 | config: config 15 | ? { default: false, ...configObject } 16 | : { 17 | default: false, 18 | "no-default-alt-text": true, 19 | "no-alt-text": true, 20 | "no-empty-alt-text": true, 21 | }, 22 | handleRuleFailures: true, 23 | markdownItFactory, 24 | customRules: githubMarkdownLint, 25 | }).content?.map((error) => { 26 | return `- ${error.ruleDescription} at line ${error.lineNumber}`; 27 | }) ?? [] 28 | ).join("\n"); 29 | }; 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "accessibility-alt-text-bot", 3 | "version": "1.0.0", 4 | "description": "Reminds users to write alt texts in issues and prs.", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "node --experimental-vm-modules ./node_modules/.bin/jest" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/github/accessibility-alt-text-bot.git" 13 | }, 14 | "author": "kendallgassner", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/github/accessibility-alt-text-bot/issues" 18 | }, 19 | "homepage": "https://github.com/github/accessibility-alt-text-bot#readme", 20 | "devDependencies": { 21 | "jest": "^30.2.0" 22 | }, 23 | "dependencies": { 24 | "@github/markdownlint-github": "^0.8.0", 25 | "js-yaml": "^4.1.1", 26 | "markdown-it": "14.1.0", 27 | "markdownlint": "^0.40.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright GitHub 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. -------------------------------------------------------------------------------- /queries.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Given a node_id for a discussion comment that is a reply in thread, return the parent comment's node ID. 4 | function getDiscussionReplyToId() { 5 | local NODE_ID=$1 6 | local REPLY_TO_DATA=$(gh api graphql -f query=' 7 | query($nodeId: ID!) { 8 | node(id: $nodeId) { 9 | ... on DiscussionComment { 10 | replyTo { 11 | id 12 | } 13 | } 14 | } 15 | }' -F nodeId=$NODE_ID) 16 | echo $REPLY_TO_DATA | jq -r '.data.node.replyTo.id' 17 | } 18 | 19 | # Given a discussion node ID, a message, and an optional reply to node ID, adds a discussion comment. 20 | function addDiscussionComment() { 21 | local DISCUSSION_NODE_ID=$1 22 | local MESSAGE=$2 23 | local REPLY_TO_ID=$3 24 | 25 | if [ -n "$REPLY_TO_ID" ]; then 26 | gh api graphql -F discussionId="$DISCUSSION_NODE_ID" -F replyToId="$REPLY_TO_ID" -F body="$MESSAGE" -f query=' 27 | mutation($discussionId: ID!, $replyToId: ID, $body: String!) { 28 | addDiscussionComment(input: {discussionId: $discussionId, replyToId: $replyToId, body: $body}) { 29 | comment { 30 | id 31 | } 32 | } 33 | } 34 | ' 35 | else 36 | gh api graphql -F discussionId="$discussion_node_id" -F body="$message" -f query=' 37 | mutation($discussionId: ID!, $body: String!) { 38 | addDiscussionComment(input: {discussionId: $discussionId, body: $body}) { 39 | comment { 40 | id 41 | } 42 | } 43 | } 44 | ' 45 | fi 46 | } 47 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "accessibility-alt-text-bot", 3 | "projectOwner": "kendallgassner", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "commitConvention": "none", 12 | "contributors": [ 13 | { 14 | "login": "inkblotty", 15 | "name": "Katie Foster", 16 | "avatar_url": "https://avatars.githubusercontent.com/u/14206003?v=4", 17 | "profile": "https://github.com/inkblotty", 18 | "contributions": [ 19 | "ideas", 20 | "a11y" 21 | ] 22 | }, 23 | { 24 | "login": "kendallgassner", 25 | "name": "Kendall Gassner", 26 | "avatar_url": "https://avatars.githubusercontent.com/u/15275462?v=4", 27 | "profile": "https://github.com/kendallgassner", 28 | "contributions": [ 29 | "code", 30 | "a11y", 31 | "doc", 32 | "infra" 33 | ] 34 | }, 35 | { 36 | "login": "khiga8", 37 | "name": "Kate Higa", 38 | "avatar_url": "https://avatars.githubusercontent.com/u/16447748?v=4", 39 | "profile": "https://github.com/khiga8", 40 | "contributions": [ 41 | "code", 42 | "a11y", 43 | "doc", 44 | "infra" 45 | ] 46 | }, 47 | { 48 | "login": "HonkingGoose", 49 | "name": "HonkingGoose", 50 | "avatar_url": "https://avatars.githubusercontent.com/u/34918129?v=4", 51 | "profile": "https://github.com/HonkingGoose", 52 | "contributions": [ 53 | "doc", 54 | "a11y" 55 | ] 56 | } 57 | ], 58 | "contributorsPerLine": 7, 59 | "linkToUsage": false 60 | } 61 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Thanks for helping make GitHub safe for everyone. 2 | 3 | # Security 4 | 5 | GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). 6 | 7 | Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. 8 | 9 | ## Reporting Security Issues 10 | 11 | If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. 12 | 13 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 14 | 15 | Instead, please send an email to opensource-security[@]github.com. 16 | 17 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 18 | 19 | * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 20 | * Full paths of source file(s) related to the manifestation of the issue 21 | * The location of the affected source code (tag/branch/commit or direct URL) 22 | * Any special configuration required to reproduce the issue 23 | * Step-by-step instructions to reproduce the issue 24 | * Proof-of-concept or exploit code (if possible) 25 | * Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | ## Policy 30 | 31 | See [GitHub's Safe Harbor Policy](https://docs.github.com/en/github/site-policy/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms) 32 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource@github.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import { validate } from "./validate.js"; 2 | 3 | test("no-alt-text: should return errors", async () => { 4 | let result = await validate("![]()"); 5 | expect(result).toBe("- Images should have alternate text (alt text) at line 1"); 6 | result = await validate(''); 7 | expect(result).toBe("- Images should have alternate text (alt text) at line 1"); 8 | result = await validate(''); 9 | expect(result).toBe("- Images should have alternate text (alt text) at line 1"); 10 | result = await validate(''); 11 | expect(result).toBe("- Images should have alternate text (alt text) at line 1"); 12 | }); 13 | test("no-default-alt-text: should return errors", async () => { 14 | let result = await validate("![Cleanshot 2020-01-01 at 12.00.00.png]()"); 15 | expect(result).toBe( 16 | "- Images should have meaningful alternative text (alt text) at line 1" 17 | ); 18 | result = await validate("![Clean shot 2020-12-01 @12x]()"); 19 | expect(result).toBe( 20 | "- Images should have meaningful alternative text (alt text) at line 1" 21 | ); 22 | result = await validate("![Clean shot 2020-12-01 @12x]()"); 23 | expect(result).toBe( 24 | "- Images should have meaningful alternative text (alt text) at line 1" 25 | ); 26 | 27 | result = await validate("![Screen Shot 2020-01-01 at 12.00.00.png]()"); 28 | expect(result).toBe( 29 | "- Images should have meaningful alternative text (alt text) at line 1" 30 | ); 31 | result = await validate("![Screenshot 2020-01-01 at 12.00.00.png]()"); 32 | expect(result).toBe( 33 | "- Images should have meaningful alternative text (alt text) at line 1" 34 | ); 35 | result = await validate("![Screencast 2020-01-01 at 12.00.00.png]()"); 36 | expect(result).toBe( 37 | "- Images should have meaningful alternative text (alt text) at line 1" 38 | ); 39 | result = await validate("![image]()"); 40 | expect(result).toBe( 41 | "- Images should have meaningful alternative text (alt text) at line 1" 42 | ); 43 | result = await validate("![Image]()"); 44 | expect(result).toBe( 45 | "- Images should have meaningful alternative text (alt text) at line 1" 46 | ); 47 | result = await validate("Check this: ![Image]()"); 48 | expect(result).toBe( 49 | "- Images should have meaningful alternative text (alt text) at line 1" 50 | ); 51 | result = await validate("My awesome ![image]()"); 52 | expect(result).toBe( 53 | "- Images should have meaningful alternative text (alt text) at line 1" 54 | ); 55 | result = await validate('Check this out: image'); 56 | expect(result).toBe( 57 | "- Images should have meaningful alternative text (alt text) at line 1" 58 | ); 59 | result = await validate('image'); 60 | expect(result).toBe( 61 | "- Images should have meaningful alternative text (alt text) at line 1" 62 | ); 63 | result = await validate(''); 64 | expect(result).toBe( 65 | "- Please provide an alternative text for the image. at line 1" 66 | ); 67 | result = await validate(""); 68 | expect(result).toBe( 69 | "- Please provide an alternative text for the image. at line 1" 70 | ); 71 | result = await validate( 72 | 'Screen shot 2020-01-01 at 12.00.00.png' 73 | ); 74 | expect(result).toBe( 75 | "- Images should have meaningful alternative text (alt text) at line 1" 76 | ); 77 | result = await validate( 78 | 'Screen Shot 2020-01-01 at 12.00.00.png' 79 | ); 80 | expect(result).toBe( 81 | "- Images should have meaningful alternative text (alt text) at line 1" 82 | ); 83 | result = await validate( 84 | 'Screenshot 2020-01-01 at 12.00.00.png' 85 | ); 86 | expect(result).toBe( 87 | "- Images should have meaningful alternative text (alt text) at line 1" 88 | ); 89 | result = await validate( 90 | 'CleanShot 2020-01-01 @12x' 91 | ); 92 | result = await validate('Screencast 2020-01-01 @12x'); 93 | expect(result).toBe( 94 | "- Images should have meaningful alternative text (alt text) at line 1" 95 | ); 96 | expect(result).toBe( 97 | "- Images should have meaningful alternative text (alt text) at line 1" 98 | ); 99 | }); 100 | 101 | 102 | test("no-generic-link-text: should return errors", async () => { 103 | const config = ` 104 | no-default-alt-text: true, 105 | no-alt-text: true, 106 | no-empty-alt-text: true, 107 | no-generic-link-text: true,` 108 | 109 | // default configuration does not check for improper links 110 | const result = await validate("[Learn more](https://docs.github.com)", config); 111 | expect(result).toBe("- Avoid using generic link text like `Learn more` or `Click here` at line 1"); 112 | }); 113 | 114 | 115 | test("no-alt-text: should not return errors", async () => { 116 | let result = await validate("```![]()```"); 117 | expect(result).toBe(""); 118 | }); 119 | 120 | test("no-generic-link-text: should not return errors", async () => { 121 | // default configuration does not check for improper links 122 | const result = await validate("[Learn more](https://docs.github.com)"); 123 | expect(result).toBe(""); 124 | }); 125 | 126 | test("no-default-alt-text: should not return errors", async () => { 127 | let result = await validate("![Mona Lisa, the Octocat](cat.png)"); 128 | expect(result).toBe(""); 129 | result = await validate( 130 | "![Screen shot of Submit button with updated color contrast.]()" 131 | ); 132 | expect(result).toBe(""); 133 | result = await validate("![Image of a cat]()"); 134 | expect(result).toBe(""); 135 | result = await validate("![Screenshot of the new GitHub home page]()"); 136 | expect(result).toBe(""); 137 | result = await validate( 138 | 'Screenshot of the new danger button with a dark red shade' 139 | ); 140 | expect(result).toBe(""); 141 | result = await validate( 142 | 'Clean shot of the scenery' 143 | ); 144 | expect(result).toBe(""); 145 | result = await validate('Mona Lisa, the Octocat'); 146 | expect(result).toBe(""); 147 | result = await validate('Mona Lisa, the Octocat'); 148 | expect(result).toBe(""); 149 | result = await validate( 150 | '```CleanShot 2020-01-01 @12x```' 151 | ); 152 | expect(result).toBe(""); 153 | result = await validate("```![Image]()```"); 154 | expect(result).toBe(""); 155 | }); 156 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Accessibility-alt-text-bot 2 | 3 | This action reminds users to add a meaningful alternative text to their images. 4 | Alternative text helps users who rely on tools like screen readers, and lowers accessibility barriers. 5 | 6 | The action can check: 7 | 8 | - Issue comments 9 | - Issue descriptions 10 | - Pull Request comments 11 | - Pull Request descriptions 12 | - Discussion comments 13 | - Discussion descriptions 14 | 15 | To learn how to write good alternative text, read [Alternative text for images on Primer](https://primer.style/design/guides/accessibility/alternative-text-for-images). 16 | 17 | ## Why you may need this action 18 | 19 | Images on GitHub default to using the filename as alt text. 20 | This action flags when the alt text has not been updated from the default: 21 | 22 | Screenshot of an automated actions comment on a GitHub issue that says, 'Uh oh! @monalisa, the image you shared is missing helpful alt text...' and contains instructions for setting alt text 23 | 24 | > [!TIP] 25 | > Normally, setting `alt=""` marks images as decorative. But GitHub renders all images as a link. To avoid rendering links with no names, we recommend always setting alt text on images in GitHub. 26 | 27 | ## How to add this action to your repo 28 | 29 | Copy this workflow into any repo you want the `accessibility-alt-text-bot` to run in: 30 | 31 | ```yml 32 | name: Accessibility-alt-text-bot 33 | on: 34 | issues: 35 | types: [opened, edited] 36 | pull_request: 37 | types: [opened, edited] 38 | issue_comment: 39 | types: [created, edited, deleted] 40 | discussion: 41 | types: [created, edited] 42 | discussion_comment: 43 | types: [created, edited, deleted] 44 | 45 | permissions: 46 | issues: write 47 | pull-requests: write 48 | discussions: write 49 | 50 | jobs: 51 | accessibility_alt_text_bot: 52 | name: Check alt text is set on issue or pull requests 53 | if: ${{ !endsWith(github.actor, '[bot]') }} 54 | runs-on: ubuntu-latest 55 | steps: 56 | - name: Get action 'github/accessibility-alt-text-bot' 57 | uses: github/accessibility-alt-text-bot@v1.7.1 # Set to latest 58 | ``` 59 | 60 | ### Pin the action's version for stability 61 | 62 | We recommend you pin the action to a specific version. 63 | This makes sure you stay on a stable version of this action. 64 | 65 | ```yml 66 | uses: github/accessibility-alt-text-bot@v1.7.1 67 | ``` 68 | 69 | Replace the ref value with any commit hash. 70 | 71 | ### Experimental: Adding a custom linting configuration 72 | 73 | If you would like to include more linting rules from the [markdownlint](https://github.com/DavidAnson/markdownlint) or [markdownlint-github](https://github.com/github/markdownlint-github) rulesets, pass a `config` object to the `github/accessibility-alt-text-bot` action. 74 | 75 | 76 | ⚠ Consider adding new rules sparingly, as excessive rules could make the bot too noisy and overwhelm users. 77 | 78 | ⚠ This feature is experimental and may be removed in the future. We acknowledge that some repositories may want to implement more accessibility checks and aim to evaluate how users this feature before making it generally available. 79 | 80 | ```yml 81 | steps: 82 | - name: Check alt text 83 | uses: github/accessibility-alt-text-bot@v1.7.1 84 | with: 85 | config: | 86 | no-default-alt-text: true, 87 | no-alt-text: true, 88 | no-empty-alt-text: true, 89 | no-generic-link-text: true, 90 | ``` 91 | 92 | ## License 93 | 94 | This project is licensed under the terms of the MIT open source license. 95 | Please read [the MIT license file](./LICENSE.txt) for the full terms. 96 | 97 | ## Maintainers 98 | 99 | See [CODEOWNERS](.github/CODEOWNERS). 100 | 101 | ## Support 102 | 103 | TODO: Be explicit about support expectations. 104 | 105 | ## Acknowledgement 106 | 107 | Please read our [Contributing Guide](./CONTRIBUTING.md) for more information. 108 | 109 | ## Contributors ✨ 110 | 111 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 |
Katie Foster
Katie Foster

🤔 ️️️️♿️
Kendall Gassner
Kendall Gassner

💻 ️️️️♿️ 📖 🚇
Kate Higa
Kate Higa

💻 ️️️️♿️ 📖 🚇
HonkingGoose
HonkingGoose

📖
JoshuaKGoldberg
JoshuaKGoldberg

💻
127 | 128 | 129 | 130 | 131 | 132 | 133 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. 134 | Contributions of any kind welcome! 135 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Accessibility alt text bot 2 | description: "This action will check a repos issue, discussion, or PR for correct alt text usage." 3 | branding: 4 | icon: "eye" 5 | color: "purple" 6 | inputs: 7 | config: 8 | description: "A custom linting configuration" 9 | required: false 10 | runs: 11 | using: "composite" 12 | steps: 13 | - name: Runs alt text check and adds comment 14 | run: | 15 | source ${{ github.action_path }}/queries.sh 16 | 17 | if [ ${{ github.event.comment }} ]; then 18 | content=$COMMENT 19 | user=${{ github.event.comment.user.login }} 20 | target_id=${{ github.event.comment.id }} 21 | if ${{ github.event.issue.pull_request.url != '' }}; then 22 | type=pr_comment 23 | issue_url=${{ github.event.issue.html_url }} 24 | bot_comment_id=$(gh api repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments | jq -r '.[] | select(.user.login == "github-actions[bot]") | select(.body | test("
72 | 73 | Uh oh! @$user, your markdown has a few linting errors. Check $target to fix the following violations: 74 | 75 | $flag 76 | 77 | > 🤖 Beep boop! This comment was added automatically by [github/accessibility-alt-text-bot](https://github.com/github/accessibility-alt-text-bot). 78 | " 79 | 80 | message="
81 | 82 | Uh oh! @$user, at least one image you shared is missing helpful alt text. Check $target to fix the following violations: 83 | 84 | $flag 85 | 86 | Alt text is an invisible description that helps screen readers describe images to blind or low-vision users. If you are using markdown to display images, add your alt text inside the brackets of the markdown image. 87 | 88 | Learn more about alt text at [Basic writing and formatting syntax: images on GitHub Docs](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#images). 89 | 90 | > 🤖 Beep boop! This comment was added automatically by [github/accessibility-alt-text-bot](https://github.com/github/accessibility-alt-text-bot). 91 | " 92 | 93 | echo "Config: $CONFIG" 94 | if [ "$CONFIG" ]; then 95 | message=$custom_config_message 96 | fi 97 | 98 | echo "Detected errors: ${flag}" 99 | echo "Event type: $type" 100 | if [[ $flag && ${{ github.event.action }} != 'deleted' ]]; then 101 | if [ $bot_comment_id ]; then 102 | if [[ $type = pr_comment ]] || [[ $type = pr_description ]]; then 103 | gh api repos/${{ github.repository }}/issues/comments/$bot_comment_id -X PATCH -f body="$message" 104 | elif [[ $type = issue_comment ]] || [[ $type = issue_description ]]; then 105 | gh api repos/${{ github.repository }}/issues/comments/$bot_comment_id -X PATCH -f body="$message" 106 | elif [[ $type = discussion_description ]] || [[ $type = discussion_comment ]]; then 107 | gh api graphql -f query='mutation($commentId: ID!, $body: String!) { updateDiscussionComment(input: {commentId: $commentId, body: $body}) { comment { id body }}}' -f commentId=$bot_comment_id -f body="$message" 108 | fi 109 | else 110 | if [[ $type = pr_comment ]] || [[ $type = pr_description ]]; then 111 | gh pr comment $issue_url --body "$message" 112 | elif [[ $type = issue_comment ]] || [[ $type = issue_description ]]; then 113 | gh issue comment $issue_url --body "$message" 114 | elif [[ $type = discussion_description ]]; then 115 | addDiscussionComment $discussion_node_id "$message" 116 | elif [[ $type = discussion_comment ]]; then 117 | addDiscussionComment $discussion_node_id "$message" $reply_to_id 118 | fi 119 | fi 120 | else 121 | echo "bot_comment_id: $bot_comment_id" 122 | if [ $bot_comment_id ]; then 123 | echo "Deleting bot comment..." 124 | if [[ $type = pr_comment ]] || [[ $type = pr_description ]]; then 125 | gh api -X DELETE /repos/${{ github.repository }}/issues/comments/$bot_comment_id 126 | elif [[ $type = issue_comment ]] || [[ $type = issue_description ]]; then 127 | gh api -X DELETE /repos/${{ github.repository }}/issues/comments/$bot_comment_id 128 | elif [[ $type = discussion_description ]] || [[ $type = discussion_comment ]]; then 129 | gh api graphql -f query='mutation($id: ID!) { deleteDiscussionComment(input: {id: $id}) { clientMutationId } }' -f id=$bot_comment_id 130 | fi 131 | fi 132 | fi 133 | shell: bash 134 | env: 135 | GITHUB_TOKEN: ${{ github.token }} 136 | COMMENT: ${{ github.event.comment.body }} 137 | ISSUE_BODY: ${{ github.event.issue.body }} 138 | PR_BODY: ${{ github.event.pull_request.body }} 139 | DISCUSSION_BODY: ${{ github.event.discussion.body }} 140 | CONFIG: ${{ inputs.config }} 141 | --------------------------------------------------------------------------------