---
title: "How to configure pa11y thresholds"
slug: "configure-pa11y-thresholds"
category: "howto"
tags: ["howto", "accessibility", "pa11y", "ci", "tutorial", "testing", "a11y"]
status: "stable"
last_updated: 2026-05-14
summary: "Set per-rule threshold overrides, add ignore patterns for acceptable failures, and wire the config to CI so new violations break the build."
related: ["[[frontend/accessibility]]", "[[howto/run-pa11y-locally]]", "[[tooling/github-actions]]", "[[frontend/html-aria-patterns]]", "[[frontend/html-semantic-elements]]", "[[seo/technical]]", "[[cheatsheets/bash-one-liners]]"]
---

## Overview

pa11y runs WCAG accessibility checks against a URL or a set of URLs. The default configuration fails on any error, which is too strict for codebases migrating incrementally and too loose for teams that want to catch regressions. A well-configured threshold strategy allows zero new violations while letting known legacy issues stay visible. The broader accessibility playbook lives in [[frontend/accessibility]]; running pa11y locally is covered in [[howto/run-pa11y-locally]].

## Prerequisites

- pa11y installed: `npm install -D pa11y pa11y-ci`.
- A running local or staging server, or a set of public URLs to test.
- An existing pa11y run with baseline results so you know what the current violation count is.

```bash
npx pa11y http://localhost:8080 --reporter json > baseline.json
```

## Steps

### 1. Create a `.pa11yci` configuration file

pa11y-ci reads `.pa11yci` (JSON) at the project root. Start with sensible defaults.

```json
{
  "defaults": {
    "timeout": 30000,
    "wait": 500,
    "standard": "WCAG2AA",
    "runners": ["axe", "htmlcs"],
    "ignore": [],
    "threshold": 0
  },
  "urls": [
    "http://localhost:8080",
    "http://localhost:8080/blog",
    "http://localhost:8080/contact"
  ]
}
```

`threshold: 0` means zero errors are permitted. This is the target state. On a codebase with existing violations, you will raise this number temporarily (step 3) and lower it as issues are fixed.

### 2. Add per-rule overrides with ignore patterns

Use `ignore` to exclude specific WCAG rules that are accepted as known exceptions. Document the reason in a comment file, since JSON does not support inline comments.

```json
{
  "defaults": {
    "standard": "WCAG2AA",
    "ignore": [
      "WCAG2AA.Principle1.Guideline1_4.1_4_3.G18.Fail",
      "color-contrast"
    ]
  }
}
```

Prefer ignoring specific rule codes over entire principles. Ignoring `Principle1` silences too many rules and hides real regressions.

To ignore a violation only on a specific page, put the `ignore` key inside the URL entry rather than `defaults`:

```json
{
  "defaults": { "standard": "WCAG2AA" },
  "urls": [
    {
      "url": "http://localhost:8080/legacy-form",
      "ignore": ["WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1"]
    }
  ]
}
```

This scopes the exception to one page and prevents it from silently spreading. See [[frontend/html-aria-patterns]] for the correct ARIA patterns that fix these rules permanently.

### 3. Set a realistic threshold during migration

If the baseline has 40 existing violations, set `threshold: 40` today and schedule a sprint to reduce it to zero. The threshold acts as a ratchet: never raise it, only lower it.

```json
{
  "defaults": {
    "threshold": 40
  }
}
```

Track the number in a `docs/accessibility-debt.md` file or a GitHub issue with a checkbox list of rules to fix. See [[frontend/html-semantic-elements]] for the most common fixes.

### 4. Wire pa11y-ci to GitHub Actions

Run pa11y-ci against the deployed preview URL on every pull request. Use `pa11y-ci` (not `pa11y`) to test multiple pages in one run.

```yaml
# .github/workflows/a11y.yml
name: Accessibility
on: [push, pull_request]

jobs:
  pa11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: "22" }
      - run: npm ci
      - run: npm run build && npx serve -s public -p 8080 &
      - run: sleep 3 && npx pa11y-ci
```

For server-rendered sites, start the dev server instead of serving the static build. See [[tooling/github-actions]] for caching and reuse patterns.

### 5. Tighten the threshold over time

Each sprint, reduce `threshold` by the number of violations fixed. When `threshold` reaches zero and `ignore` is empty, the accessibility baseline is clean. Remove the `threshold` key entirely; pa11y-ci defaults to zero.

Use the JSON reporter to track progress over time:

```bash
npx pa11y-ci --reporter json 2>/dev/null | jq '.totals'
# { "errors": 12, "warnings": 4, "notices": 7 }
```

## Verify it worked

```bash
# Zero errors on clean pages
npx pa11y-ci
# Expected: "Errors: 0"

# Confirm ignore patterns suppress only the intended rules
npx pa11y http://localhost:8080 --reporter json | jq '.[].code' | sort -u
# The ignored codes should not appear.

# Confirm CI fails when a new violation is introduced
# Temporarily add a <img> without alt, run pa11y-ci, expect non-zero exit.
```

## Common errors

- **pa11y times out on JavaScript-heavy pages**. Increase `timeout` to 60000 and add `wait: 1000` to let the page fully render before scanning.
- **Violations appear in CI but not locally**. The CI build uses a different base URL. Check that `urls` in `.pa11yci` match the server address in the workflow.
- **`threshold` is set but CI still passes with new violations**. `threshold` is a maximum count, not a delta. If you add two violations and remove two, the count stays the same and CI passes. To catch regressions, track per-rule counts in a separate script.
- **`ignore` array has no effect**. The rule code must match exactly, including the full dotted path. Copy the code from `pa11y --reporter json` output.
- **`axe` and `htmlcs` report contradictory results**. Both runners implement WCAG differently. Prefer `axe` for programmatic correctness; use `htmlcs` for structural checks. Remove one runner if the noise is high.

## Related

- [[frontend/accessibility]]
- [[howto/run-pa11y-locally]]
- [[tooling/github-actions]]
- [[frontend/html-aria-patterns]]
- [[frontend/html-semantic-elements]]
- [[seo/technical]]
- [[cheatsheets/bash-one-liners]]
