Repository navigation
Accessibility
Contrack works with a keyboard alone, with a screen reader, on a phone, and in the light and the dark theme. The first half of this page says what Contrack supports. The second half is for contributors: what the suite checks on every pull request, and what a person still checks by hand.
Every page works with no pointer. See Keyboard shortcuts for the keys.
- The first
Tabon a page shows Skip to main content. It moves focus to the contact's name on a contact page, to the current row on the Network page, past the page list in Settings, and to the main part of every other page. - A ring marks the control that has keyboard focus, in both themes. A click does not show it, but a text field shows it on any focus.
- The Network list and its letter rail are one
Tabstop each. The arrow keys,Home,Endand the letters move inside them. - Opening a contact moves focus to its name. On a phone, Back puts focus on the row you opened.
- A dialog opens with focus in its first field, or on the dialog itself when
it has no field, never on Close. It keeps focus inside while it is
open.
Esccloses it, and focus returns to the control that opened it. A menu, the right-click menu too, takes the arrow keys,Home,Endand a letter, andEscreturns focus to its button. While a dialog or a menu is open, the page's own keys wait. - After a click on blank space,
Space,Page Downand the arrow keys scroll the part of the page you clicked. -
Alt T(⌥ Ton a Mac) moves focus to the notifications. After Undo, focus goes back to where it was. - Speech input or a stray press can set off a shortcut that is one key, such
as
N. Turn off Single-key shortcuts in Settings → Keyboard to stop all of them but?and the Network list's letters. See Turn off single-key shortcuts.
- Each page has one main landmark and one level-one heading. On a wide screen, the Network list is a landmark named "Contacts" beside the contact, named "Contact".
- Every control has a name. Help text opens from a button, with a key, a click or a tap, not on hover alone.
- Search progress and results are spoken without moving focus, such as "12 matches for …". A list that AI did not check ends with "Not verified by AI." The Network search, a bulk bar's count and a moved email, phone or address are spoken too, such as "Moved to position 2 of 3".
- An error interrupts, a field's error is read with the field, and a score is said in words, such as "Score 72, strong".
- The map is a region named "Contact map". Each pin is a button named for the person and the company. Each cluster says how many people it holds and what a press does, such as "12 contacts, zoom in".
- Motion, in Settings → Appearance: System follows the reduce motion setting of your device, and Reduced keeps animation to a minimum for your account.
-
Corvid motion: Full, Subtle or Off, for the bird in the
sidebar. Reduced motion holds the bird still whatever you choose. The bird
is never announced, never takes a click, and
Esclands it. - Text size: Large makes all text one step larger.
- Theme: Light, Dark or System. Every Accent color you can pick is adjusted to meet WCAG 2.2 AA contrast in both themes.
Every control has a touch target of at least 44 by 44 pixels, and no text is smaller than 11 pixels. On a touch screen, form fields and the note editor are 16 pixels or larger, so the browser does not zoom in when a field takes focus. The tab bar marks the current page.
- Dialogs are sheets that rise from the bottom of the screen. Drag one down
by its handle or its title to close it. On Android, Back closes an open
sheet or menu instead of leaving the page, in browsers that have
CloseWatcher(Chrome, Edge and Samsung Internet). While a sheet is open, notifications show at the top of the screen, clear of its buttons. - On a touch screen, a dialog takes focus itself, not its first field. Ask Contrack and the sign-in screens put no cursor in a field either. The on-screen keyboard opens only when you tap a field.
- On a touch screen, controls keep their 44 px size at every width, a tablet too, and no control waits for a hover. A long press on an icon button shows its name.
- While you type, the tab bar steps aside, and the note's Save bar and a sheet's buttons stay above the keyboard.
- The app keeps clear of the status bar, the home indicator and, on a phone on its side, the camera cutout. On its side the rail packs tight, so Settings stays in reach.
- Added to the Home Screen, Contrack opens as an app, with shortcuts to Pulse, Ask Contrack and the map. The browser bar takes the theme you chose in the app.
This half is for contributors.
The Browser accessibility jobs in .github/workflows/ci.yml build the
production bundle and run npx playwright test in headless Chromium, split
across four runners with --shard. Each worker starts its own production
server (NODE_ENV=production, dist/, the CSP on) on a free port, with a new
temporary DATA_DIR. The suite runs with reduced motion, the en-US locale
and the America/Los_Angeles time zone. Each shard uploads its report on
every run.
| Spec | What it holds |
|---|---|
axe.spec.ts |
Eight screens against WCAG 2.2 AA, four of them in dark too. Landmark and heading rules on six screens and two phone screens |
keyboard.spec.ts |
The skip link, the sidebar in order with a visible ring in both themes, /, the list's arrow keys, the Ctrl Alt keys and the Tab budget |
contact.spec.ts |
Focus when a contact opens, the list keys, the header menu, tracking, the timeline and the composer. On a phone: Back, the call and mail links, Share and a long press |
dialogs.spec.ts |
Five dialogs: focus in, Tab kept inside, Esc (a list inside a dialog closes first), focus back, and a scan while open |
search-announcements.spec.ts |
The status messages and alerts of People and Notes searches |
palette-keys.spec.ts |
The command palette: the arrows, Esc one layer at a time, aria-activedescendant in every list it shows, focus kept and given back |
mobile-forms.spec.ts |
A Pixel 7: the tab bar, the new contact sheet with 16 px fields, and setup errors tied to their fields |
metrics.spec.ts |
A 390 px phone: 44 px targets and 11 px text on seven screens |
phone-shell.spec.ts |
Typing hides the tab bar and keeps Save in view, a sheet drags closed, the browser bar's theme, asset caching, the rail on its side |
account-transitions.spec.ts |
A gated instance: setup, sign out, sign in, an expired session and a forced password change |
Most other specs, such as Pulse, the map and Settings, also scan the screens
they reach. In the unit suite, tests/unit/frontend/style/stylesFloor.test.ts
fails on text under 11 px and on a focus ring drawn by a component.
themeContrast.test.ts beside it checks both palettes and every accent
against WCAG AA.
npm run build # the suite serves dist/
npx playwright install chromium # first run only
npm run test:e2e # every journey, headless
npm run test:e2e:ui # pick a test and watch it
npx playwright test -g "skip link" --headedEvery run writes playwright-report/. Open it with
npx playwright show-report. It holds each axe scan as JSON and, for a
failed test, its screenshot and the server's log as server.log. Specs that
take docs screenshots save them in test-results/docs-screenshots/, or in
docs/screenshots/ when DOCS_SCREENSHOTS=1 is set.
-
Status: results and progress go to a polite region,
LiveStatusinsrc/components/ui/, that is in the page before it speaks. The search words are insrc/lib/searchAnnouncements.ts. An error takesrole="alert", and a field's error setsaria-invalidandaria-describedby. -
Dialogs: build on
Modalinsrc/components/ui/. It names the dialog, moves focus in, keepsTabinside, closes onEscand returns focus. -
Structure: one named
mainper route, oneh1, headings in order, and nothing outside a landmark. Destination names come fromsrc/lib/names.ts. -
Names and focus: name a control by its text or
aria-label, never bytitlealone, and put an explanation inInfoTip. One:focus-visiblerule insrc/index.cssdraws every ring, and.focus-framedraws it on a composite field. -
Tab budget: from the top of the page, a contact's name is within 21
presses of
Tab, and the first Network row within 18. A new stop in front of the content raises the budget inkeyboard.spec.ts, with the reason in the pull request. -
Phones: 44 px targets on every touch screen (
sm:pointer-fine:drops them only for a mouse), withhit-areafor a small control, and 16 px fields on a touch screen, whichsrc/index.csssets. See.agent/STYLE.md.
Automation checks the markup a screen reader reads and the focus a keyboard user lands on. It cannot hear a screen reader. Before a release, and after a change to a live region, a dialog or the sign-in screens, one person does this pass. Record the date and the screen reader and browser in the release notes.
- Load
/and pressTab, thenEnteron Skip to main content. Focus is on the list's current row.↓,↑and a letter move focus without opening anyone, andEnteropens the contact with focus on its name. - Tab through the sidebar. Every stop shows a ring, in light and in dark.
- Press
/, type a name and pressEsc: the list filters, then clears. PressN, move through the form withTab, and close it withEsc. - Open the shortcuts dialog from the sidebar's Keyboard shortcuts
button. Focus stays inside on
Tab, andEscputs it back on the button. - On Ask Contrack, open a result with
Enter,Tabinside the card, and pressEsc. Focus is back on the result. - Sign out and sign in again with
TabandEnteronly.
Use VoiceOver with Safari on macOS and NVDA with Firefox or Chrome on Windows. Do steps 1 to 4 with both.
- On Ask Contrack, ask a question. You hear "Searching your network for …", then "N matches for …" or "No matches for …". Nothing is read twice, and nothing is read while you type.
- Switch to Notes with the arrow keys, type a word and press
Enter. You hear "N notes for …". Choose Last 30 days, and you hear the new count once. - Stop the server and ask again. You hear "Could not search" as an interruption, and nothing from the status region.
- Leave the page and come back. The results show, and nothing is read.
- Open the shortcuts dialog. You hear "Keyboard shortcuts, dialog".
- Open a contact. You hear its name as heading level 1. The landmarks list shows "Contacts" and "Contact" on a wide screen, and one main on a phone.
- Submit a wrong password on the sign-in screen. You hear "Incorrect
username or password." On the setup screen, type an email that is not
valid and press
Tab. You hear that the field is not valid, and why.
- At 200% browser zoom,
/,/searchand/settingscut nothing off and need no sideways scrolling. - With reduce motion on in the operating system, dialogs and result cards appear with no animation, and the corvid holds still.
- On a phone, or in device mode at 412 px wide, the five tabs are easy to hit. New contact rises from the bottom, no field zooms the page, and Save contact stays above the keyboard. The account row at the top of Settings signs out. With VoiceOver on, Back from a contact puts focus on the row you opened.
Write the spec against test from tests/e2e/fixtures/test.ts for an open
instance, or against gatedTest when the journey starts before sign-in. Go
to a page with page.goto("/…"), because each worker sets baseURL. Find
elements by role and name, and assert what the next screen says, not the URL.
Scan every screen the journey reaches with expectPageAccessible, a new page
with expectPageStructured too, and an open dialog with
b.include('[role="dialog"]'). Script a People search with
answerPeopleSearch from fixtures/search.ts, and answer the basemap with
stubBasemap from fixtures/map.ts.
These pages are written from docs/ in arvarik/contrack. Change the source, then run npm run docs:wiki again.
Start here
Use Contrack
- Contacts
- Pulse and tracking
- Search and Ask Contrack
- Map
- Duplicates
- Import, sync, and export
- AI
- Keyboard shortcuts
- Accessibility
Run Contrack
Comparisons
- Contrack vs Monica
- Contrack vs Clay
- Contrack vs Folk
- Contrack vs Twenty
- Contrack vs Huly
- Contrack vs EspoCRM
- Contrack vs Nextcloud Contacts
Build on Contrack