diff --git a/docs/accessibility.md b/docs/accessibility.md
index 1ff100f..5c0bf21 100644
--- a/docs/accessibility.md
+++ b/docs/accessibility.md
@@ -8,24 +8,44 @@
- **Forms.** Every field has a label connected to its input, invalid fields are marked with `aria-invalid`, and help text (such as the minimum password length) is connected with `aria-describedby`. Error messages are shown with `role="alert"`, so screen readers read them out as they appear. We also set `autoComplete` on the fields, so browsers and password managers can fill them in, and the error messages tell the user what went wrong, for example that the email is already in use.
-- **Images and icons.** Book covers have an alternative text (`Book cover of …`), and books without a cover show a text instead. Decorative icons, such as the heart on the favorite button and loading spinners, are hidden from screen readers. The star rating is hidden as well, and replaced by text that reads, for example, "Rated 4.12 out of 5, 1234 ratings".
+- **Images and icons.** Book covers have an alternative text (`Book cover of ...`), and books without a cover show a text instead. Decorative icons, such as the heart on the favorite button and loading spinners, are hidden from screen readers. The star rating is hidden as well, and replaced by text that reads, for example, "Rated 4.12 out of 5, 1234 ratings".
-- **Names for icon buttons and links.** Buttons and links that only show an icon or a short text have a descriptive label, for example "Filter books", "Search for books", "View details for …" and "Go to previous book". The filter button also tells how many filters are active.
+- **Names for icon buttons and links.** Buttons and links that only show an icon or a short text have a descriptive label, for example "Filter books", "Search for books", "View details for ..." and "Go to previous book". The filter button also tells how many filters are active.
- **Communicating state.** The favorite button uses `aria-pressed`, the "Read more" button on the book page uses `aria-expanded`, and disabled pagination links use `aria-disabled` and are skipped when tabbing. Loading and empty search results are announced with `role="status"`.
- **Responsive layout.** The book grid goes from four columns to one as the screen gets narrower, and the book page stacks vertically on small screens, so the app can be used on phones.
+## Automatic testing using Playwright
+
+We use [Playwright](https://playwright.dev/) to perform automatic accessibility checks.
+
+We're glad to share that we pass the automatic Playwright checks for all of the following levels:
+
+- WCAG 2 Level A
+- WCAG 2 Level AA
+- WCAG 2 Level AAA
+ - It is very difficult to test for all requirements in Level AAA automatically. We conform to those requirements that can be checked automatically, such as 7:1 color contrast.
+- WCAG 2.1 Level A
+- WCAG 2.1 Level AA
+- WCAG 2.2 Level AA
+- General best practices
+
+**We do not claim conforming to all of these levels, especially AAA.**
+
+Testing all of the WCAG requirements is difficult, and Playwright only tests for a subset of these.
+We have rectified multiple issues highlighted by Playwright.
+
## Limitations/Future work
This section is meant as an overview of potential improvements with regard to accessibility, and also shows that we are aware of the downsides of our implementation. The table has a column for the status at delivery 3, and the same table will be used to show what work has been done from delivery 3 to 4.
-| Limitation | Possible improvement | Delivery 3 | Delivery 4 |
-| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| The heading structure is inconsistent. The login, sign-up, profile and settings pages have two `
` headings (the site name in the header and the page's own heading), the home page has no heading of its own, and the book title on the book page is not a heading. | Show the site name in the header as a link without a heading, and give each page one `
` that describes it, for example the book title on the book page and a heading for the search page (it can be visually hidden). | Not done | |
-| The favorite button changes its label ("Add …" / "Remove …") and also uses `aria-pressed`, so screen readers may read conflicting states. It is also disabled while saving, which can move the keyboard focus away from it. | Use a fixed label together with `aria-pressed`, and use `aria-disabled` instead of `disabled` while saving. | Not done | |
-| An error when loading books is announced with `role="status"`, which screen readers may read later or not at all. | Use `role="alert"` for errors, and keep `role="status"` for loading and empty results. | Not done | |
-| There is no "Skip to main content" link, so keyboard users have to tab through the header on every page. | Add a skip link as the first focusable element. | Not done | |
-| Animations, such as the cover zooming in on hover, don't respect the user's setting for reduced motion. | Turn off these animations with Tailwind's `motion-reduce:` variant. | Not done | |
-| We have not tested the app with automatic tools, and color contrast has not been measured. | Run Lighthouse and axe on all pages. | Not done | Done. Lighthouse, which uses axe for its accessibility checks, gives 100 on all pages after we made `--muted-foreground` darker (the breadcrumb links were 4.29:1). See the [Lighthouse results](/docs/sustainability.md#lighthouse). |
-| We have not tested the main flows with only a keyboard or with a screen reader. | Test the main flows with only a keyboard and with a screen reader (VoiceOver or NVDA). | Not done | |
+| Limitation | Possible improvement | Delivery 3 | Delivery 4 |
+| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| The heading structure is inconsistent. The login, sign-up, profile and settings pages have two `
` headings (the site name in the header and the page's own heading), the home page has no heading of its own, and the book title on the book page is not a heading. | Show the site name in the header as a link without a heading, and give each page one `
` that describes it, for example the book title on the book page and a heading for the search page (it can be visually hidden). | Not done | Done. The site name in the header is no longer a heading, the book title is the `
` on the book page, and the search page has a visually hidden `
`. The automatic Playwright checks now also check that each page has exactly one `
`. |
+| The favorite button changes its label ("Add ..." / "Remove ...") and also uses `aria-pressed`, so screen readers may read conflicting states. It is also disabled while saving, which can move the keyboard focus away from it. | Use a fixed label together with `aria-pressed`, and use `aria-disabled` instead of `disabled` while saving. | Not done | Done. The button is always labelled "Favorite ...", and `aria-pressed` tells whether the book is a favorite. While saving, it is marked with `aria-disabled` and keeps the keyboard focus. |
+| An error when loading books is announced with `role="status"`, which screen readers may read later or not at all. | Use `role="alert"` for errors, and keep `role="status"` for loading and empty results. | Not done | Done. Errors when loading books, genres and favorites now use `role="alert"`. |
+| There is no "Skip to main content" link, so keyboard users have to tab through the header on every page. | Add a skip link as the first focusable element. | Not done | Done. A "Skip to main content" link is the first focusable element on every page, and is shown when it gets focus. |
+| Animations, such as the cover zooming in on hover, don't respect the user's setting for reduced motion. | Turn off these animations with Tailwind's `motion-reduce:` variant. | Not done | Done. The cover zoom uses `motion-safe:`, like the animations on the home page, and changing page scrolls to the top without animation when the user prefers reduced motion. |
+| We have not tested the app with automatic tools, and color contrast has not been measured. | Run Lighthouse and axe on all pages. | Not done | Done. Lighthouse, which uses axe for its accessibility checks, gives 100 on all pages after we made `--muted-foreground` darker (the breadcrumb links were 4.29:1). See the [Lighthouse results](/docs/sustainability.md#lighthouse). |
+| We have not tested the main flows with only a keyboard or with a screen reader. | Test the main flows with only a keyboard and with a screen reader (VoiceOver or NVDA). | Not done | Done. We tabbed through every page: the order follows the layout, and every element shows a focus ring. Playwright tests search, filter, sort, opening a book, saving it as a favorite and going back using only the keyboard. Testing with a screen reader is done manually, and showed links being read correct, tabs listed (e.g. "tab 1 out of 3"), etc. |
diff --git a/docs/choices-and-reasoning.md b/docs/choices-and-reasoning.md
index 879ea02..ac78cbe 100644
--- a/docs/choices-and-reasoning.md
+++ b/docs/choices-and-reasoning.md
@@ -46,7 +46,7 @@ better-auth stores users and sessions in our own PostgreSQL database through its
## The URL holds the search state
-The search text, filters, sorting and page number are stored in the URL (`?q=…&genres=…&sort=…&page=…`) instead of in component state. This means a search can be shared as a link, survives a page reload, and the browser's back button works as expected. Together with Apollo Client's cache for data from the server, this covers our need for state management without a separate global store.
+The search text, filters, sorting and page number are stored in the URL (`?q=...&genres=...&sort=...&page=...`) instead of in component state. This means a search can be shared as a link, survives a page reload, and the browser's back button works as expected. Together with Apollo Client's cache for data from the server, this covers our need for state management without a separate global store.
## Pagination with page numbers
diff --git a/frontend/app/components/BookCard/BookCard.test.tsx b/frontend/app/components/BookCard/BookCard.test.tsx
index d286043..2f67ede 100644
--- a/frontend/app/components/BookCard/BookCard.test.tsx
+++ b/frontend/app/components/BookCard/BookCard.test.tsx
@@ -36,7 +36,7 @@ describe("BookCard", () => {
renderRoutes(routes, { initialEntries: ["/?q=fantasy"] });
expect(
- screen.getByRole("link", { name: "View details for The Blade Itself" }),
+ screen.getByRole("link", { name: /^The Blade Itself/ }),
).toHaveAttribute("href", `/book/${book.id}?q=fantasy`);
});
diff --git a/frontend/app/components/BookCard/BookCard.tsx b/frontend/app/components/BookCard/BookCard.tsx
index a8cc838..a453540 100644
--- a/frontend/app/components/BookCard/BookCard.tsx
+++ b/frontend/app/components/BookCard/BookCard.tsx
@@ -31,9 +31,8 @@ function BookCard({ book, searchListParams }: BookCardProps) {
search: nextSearchParams.toString(),
}}
className="flex h-full flex-col cursor-pointer p-6 rounded-xl"
- aria-label={`View details for ${book.title}`}
>
-
+ {book.title}
diff --git a/frontend/app/components/BookCard/__snapshots__/BookCard.test.tsx.snap b/frontend/app/components/BookCard/__snapshots__/BookCard.test.tsx.snap
index b282a01..62eede4 100644
--- a/frontend/app/components/BookCard/__snapshots__/BookCard.test.tsx.snap
+++ b/frontend/app/components/BookCard/__snapshots__/BookCard.test.tsx.snap
@@ -8,7 +8,6 @@ exports[`BookCard > matches the snapshot 1`] = `
data-slot="card"
>
matches the snapshot 1`] = `
class="block h-full overflow-hidden"
>
@@ -56,7 +55,8 @@ exports[`BookCard > matches the snapshot 1`] = `