Accessibility Conformance Report (WCAG 2.2 and 2.1, Level AA)
Based on the VPAT 2.5 WCAG edition. Groundskeeping and Floorkeeping share one codebase, so this report covers both.
- Product: Groundskeeping (groundskeeping.app) and Floorkeeping, progressive web app, version 3.189.3
- Report date: October 28, 2026
- Prepared by: Robert Spencer (author)
- Standards: WCAG 2.2 Level AA (W3C Recommendation, 12 December 2024). WCAG 2.1 Level AA is the standard named in the U.S. Department of Justice ADA Title II rule for public universities; every 2.1 criterion is also a 2.2 criterion, so a 2.2 AA claim covers it. Section 508 (Revised) incorporates WCAG 2.0 AA and is covered as well.
- Evaluation methods:
- Automated, on every version:
dev/axe-check.jsruns axe-core with the WCAG 2.0, 2.1 and 2.2 A and AA rules on 37 screens, dialogs and drawers, as a crew member on a 390 px phone and as a manager at 1280 px. A version does not ship with a violation. Version 3.162.0: 0 violations. - Automated, on every version:
dev/a11y-check.js(names, roles, focus and live regions),dev/mobile-check.js(reflow at 320 px),dev/spacing-check.jsand the other checks listed indev/README.txt. - Manual: code review of every control and dialog; keyboard-only walkthrough; contrast calculations on the design tokens.
- Testing with assistive technology on devices follows
docs/AT-TEST-PLAN.md(VoiceOver on iPhone and Mac, TalkBack, NVDA, JAWS, keyboard only, zoom and Voice Control; 24 tasks). A full pass runs every 6 months and before each contract, and results are logged indocs/AT-RESULTS.md. The build fails when the last pass is more than 6 months old or a blocker is open. The first full pass is scheduled before sales begin (docs/COMPLIANCE.md). - Automated, on every version:
dev/signin-check.jsruns axe on the sign-in screens and the authenticator dialog. - Automated, on every version (v3.188.15):
dev/parts-check.jsruns axe on Mark as fixed, M3's full-screen dialog at 320 px and a basic dialog at 1280 px, on an item's count with its Recent use and on Add sprinkler parts; it checks that focus starts on the first part's count, stays on a count button after it is pressed, and goes back to what opened the problem after Cancel and after Mark fixed (2.4.3), and that leaving with something entered asks first. - Automated, on every version (v3.189.3, issue #199):
dev/editdot-check.jschecks the sprinkler heads in Edit this zone, which now draw as the same small dot the crew places and the zone pages show (the earlier size choices, Sprinkler marker size and Bed dot size, are retired, along with the checks for them). A head's state is told by its shape as well as its color (1.4.1) and its dot's edge keeps its contrast against the map (1.4.11), because it is drawn by the routine the zone pages use, whichdev/homedot-check.jsanddev/headzoom-check.jstest;dev/editdot-check.jschecks that Edit this zone draws that same dot at the same 10 px. The head being edited wears the app's yellow selection ring, wider than the dot's own casing so it shows on a 10 px dot, and drags by a tap circle 24 px across with a mouse and 48 px with a finger (2.4.7, 2.5.8). While a drawing tool is on, a head takes no taps, so a station or bed corner is placed on or beside it with no head selected and no drawing lost; with the head tool on, a tap on or beside a head selects it instead of putting a second head on top, as Add sprinklers does; with Select area on, a head is tapped as ever. Bigger and Smaller leave a head as it is, and are not offered for a selection of heads alone. Axe runs on the Edit this zone bar in light and dark at 1280 px, and a phone at 360 px selects and drags a head by touch with nothing off the screen (1.4.10). - Automated, on every version (v3.189, issue #188): each marker and mark is a document of its own, so a map has no size limit, and the place bar's storage warning, the stops that came with it and the "storage is full" messages are gone. Nothing new is on screen and no control changed.
dev/sprinklerbed-check.jschecks that with 4,600 detailed markers on the phone, more than the one shared document could hold before, Add sprinklers shows its ordinary hint with no warning and a tap still places a head. The hint was a polite live region for the warning; with its words never changing it is no longer one, and Done, which takes focus as placing starts, is described by it, so a screen reader hears the instructions there (1.3.1, 3.3.2), which the check confirms;dev/station-check.jsthat drawing a station still adds it and opens its Details, and that a marker tapped in, Duplicate and Copy to another kind still add. A refused write still says it was not saved, in a message that stays until read with Send feedback (2.2.1, 4.1.3), now without naming the storage (dev/reorder-check.js). Help says a map never fills up. - Automated, on every version (v3.188.50, issue #177):
dev/headzoom-check.jschecks the sprinkler heads by zoom and the Stations list. Zoomed out, and as a page opens, no head is drawn, only the stations' outlines and labels; heads appear once zoomed in well past the opening view, and a screen reader hears "Sprinkler heads shown" or "Sprinkler heads hidden. Zoom in to see them" as the zoom crosses that point, never on opening a page (4.1.3). A head the app is pointing at shows at any zoom, and the Stations dialog lists every head with its state at any zoom, so nothing is out of reach of a keyboard or a screen reader (2.1.1, 1.3.1). On the zone pages each head is now the home map's dot, its shape telling its state as well as its color (1.4.1), with its white edge over an ink casing (1.4.11); a dot beside a close neighbour draws smaller, never under 6 px across, so no two dots touch at the zoom they appear at or deeper; a tap that reaches several heads on a zone page opens the same list of them, with rows at least 48 px tall, as on the home map (2.5.8), while a tap or a press on a head's number opens that head. A window resized draws the map again once it settles, with focus kept on the marker that had it (2.4.3). Two stations' labels that would touch show only the larger station's until zoomed in; each station's label stays at the start of its outline's name, so a screen reader still hears every one (1.1.1). Resting the pointer, or a finger, on a dot shows its number, its state, and when and by whom it was checked, in the pill the bush beds use, which Escape dismisses (1.4.13). Sprinklers in the left bar opens its list of the page's stations: Hide all or Show all, then each station as a toggle button named "Station 38-3, shown" or "hidden" witharia-pressed(4.1.2), a hidden one in readable contrast, never by color alone (1.4.1); axe runs on the list. - Automated, on every version (v3.188.48, issue #179):
dev/reorder-check.jschecks Move up and Move down in a station's list of heads on a shared map, against the real rules, at 1280 px with the mouse and with a crew phone at 360 px: the list shows the new order at once and keeps it, offline too, and the crew phone shows the new number. A change the shared map refuses is never said as saved: the list goes back to the order the map holds, and the message saying it was not saved, read out in the polite live region as every snackbar is (4.1.3), stays until it is read or dismissed, with a Send feedback button, rather than passing in two seconds (2.2.1). Help, under Where your work and photos are kept, says what the message means. - Automated, on every version (v3.188.46, issue #110, the manager's tools):
dev/leadtoolsui-check.jsruns axe at 320 px and 1280 px on Manager settings as a lead holding the manager's tools, with every drawer open, in Groundskeeping and Floorkeeping, and on the manager's grant dialog, as it opens and with an error shown, and checks that nothing runs off the screen at 320 px outside a region that scrolls sideways on its own, and that a lead's Manager settings do not scroll sideways themselves (the Left bar drawer, whose rows already did on main, closed for that measure; 1.4.10); the manager's crew list, with a lead holding the tools, must not make Manager settings scroll sideways at 320 px or 1280 px: the line under a name runs to a second line instead. As a lead it measures every gap between neighbours against the spacing system, with the rows the tools hide gone, and the first row shown in each stack, which has no gap above it however many rows before it are hidden; the first row shown in a drawer has no rule above it either (Crew and access opens at the crew's hours for a lead); the last group keeps its rule, and text after a hidden field is never drawn as that field's supporting text (1.3.1). The rows the tools hide in the Site drawer are wrapped in groups that change nothing the manager sees (each heading and help text stays outside its group, and a group's buttons are drawn as the drawer's own), and the check holds Add a webcam and Add a 3D model at full width, and the Webcams heading styled as the 3D models one, for the manager. The manager gives the tools from an item on a lead's existing overflow menu in Crew and access, whose supporting text says what it will do ("Let Ann run the work"); a lead holding them has "manager's tools until" on their row, kept current as the Phones list arrives and changes while Manager settings stay open. It opens a basic dialog with a headline, supporting text saying what the tools open and what they never open and how many phones it covers (all read with the dialog as its description), and an outlined select named How long (1.3.1, 4.1.2). Until a chosen day shows a date field named Last day with its supporting text; an empty day, a day already gone or one past 90 days is said in place of that text, in an alert tied to the field, the dialog stays open and focus goes to the field (3.3.1, 3.3.3). While the server is asked, Give the tools keeps focus (it is marked busy, not disabled, which would drop focus out of the dialog); a refusal is said inside the dialog in an alert, the dialog stays open, and Escape still closes it; a grant that lands after Cancel is said in a snackbar, focus stays on the lead's row as the list is drawn again, and its answer never closes a dialog opened again (2.4.3, 4.1.3). After Give the tools or Cancel, and after taking the tools back however slowly the server answers, focus goes back to the lead's row, found again after the crew list is drawn again (2.4.3). A lead with no phone on the leads' phones list is told so in a short dialog, with no grant to fill in. A lead holding them sees, at the top of Manager settings, All manager settings, which asks for the PIN as Manager settings does for anyone (so a phone whose manager role ended while it holds the tools unlocks the rest the usual way), and a status line saying until when (4.1.3), written only when its words change, in a translation too (the check runs it in Spanish, and in Floorkeeping, whose own words and the translation no longer rewrite each other), so it is not read out again on every redraw; the words the tools change are shown translated from the start, never in English first, and in English again after switching back (3.1.2); the start is said once for each grant in a snackbar in the polite live region. The drawers and rows the tools never open are not shown, rather than shown and refused, so nothing on screen offers what it cannot do; a group whose drawers are all hidden goes with them (and in Progress (manager), By person and Hours, which hold each person's work records, are not shown either; the closing line says exactly what is the manager's alone, in place of the crew version's line), and the settings search finds nothing hidden, even when the server's copy arrives while a search is typed. When the tools end, every editing mode they opened closes with them (Assign work's bar, Set home position, Floorkeeping's Place the building), Manager settings changes even with a colour field focused, and a snackbar says so. Focus that was on a control drawn again stays on it (a chip in the Library, a line's I'll take it); focus on something taken away moves to the first control still shown in a full-screen view or dialog that is open (the Library), to Unlock or the first drawer heading still shown in Settings, to the first control still shown in an open side panel (the Schedule, Instructions, Progress), or to the left bar, never to the page itself or under a sheet (2.4.3, 2.4.11); focus that was on nothing stays there as the tools start or end. - Automated, on every version (v3.188.35, issue #131):
dev/meetings-check.js's Who's in section checks the leads' meetings on the Today page's calendar. A date with a meeting carries a badge on its corner in the Week and Month views, apart from the time-off dots and bars under the date, and the day's name says the meetings in words, as "2 meetings, the first at 10:00 AM" ("2 leads meetings" until v3.188.45) (1.4.1, 1.1.1); an opened day lists its meetings under a Meetings heading and the people under a Crew heading, each heading over a list of its own, and a day with nobody on it is a note rather than an empty list (1.3.1); each meeting is a button whose name starts with the title it shows (2.5.3, 4.1.2), the full width of its list and at least 56 px tall (2.5.8); it opens the meeting in the leads calendar, on top of the full-screen calendar on a phone, never behind it (2.4.11), and Escape there brings focus back to the meeting (2.4.3); a new copy of the meetings arriving keeps focus on the meeting or the day it was on (2.4.3); with the meeting it came from gone, closing the leads calendar puts focus on the day in the full-screen calendar, never on the app behind it, and a side panel opened meanwhile closes the full-screen calendar rather than open under it (2.4.3, 2.4.11); axe runs on the card with a meeting at 320, 390 and 1280 px, and nothing spills sideways (1.4.10). The badge is the ink token with a ring of the page's surface, a pair well over 3:1 (1.4.11). - Automated, on every version (v3.188.34, issue #132):
dev/headno-check.jschecks that every sprinkler head is called by its number. A round head outside a bush bed carries its number in its zone on a badge at its top-right edge, ink on white with an ink ring (about 11:1), 18 px tall on screen with a 12 px number, so a number never relies on the state's color (1.4.1, 1.4.3, 1.4.11). It sits just past the marker's edge, clear of the state's glyph at every marker size, and the badges are drawn under every round marker, so where heads are close together no neighbour's number covers a head's glyph (1.4.1); a finger on a marker as drawn opens that marker, and a finger on a number opens its own head, even inside a neighbour's larger unseen tap circle, zoomed in and while placing too, and never places a head (2.5.8); Enter and the menu key act on the focused object itself, an area or a bed under a number included (2.1.1); people's circles around a head keep clear of its number, which moves below or to the start side at the picture's edge so it is never cut off. Badges are hidden from screen readers, which hear the same number in the head's name, "Sprinkler head 3 in Zone 1" or "Sprinkler head 1 of Bush bed in Zone 1" (1.1.1, 2.5.3, 4.1.2). The head's choices are titled with its number, with the bed or zone it belongs to on the line under it (1.3.1, 2.4.6), and Needs repair, Details and Today's Your work say the same; axe runs on the choices, and the zone page with its badges fits at 320 px (1.4.10). - Automated, on every version (v3.188.45, issue #136):
dev/meetings-check.js(nameddev/leadscal-check.jsuntil this version) section 8 checks meetings for the whole crew. Add a meeting's For is a fieldset with the legend For (1.3.1); its segmented button, Crew or Leads, is a radio group named by that legend, and Which leads (All leads or Chosen leads) a second one named Which leads, each one tab stop with the arrow keys moving inside it (2.1.1, 4.1.2). A choice with nobody behind it is left out: on an area with no lead, For holds one line of text, Everyone on the crew, and focus starts on What the meeting is rather than on a choice that does not exist (2.4.3). The line under For says, for the choice made, who is told and who can see the meeting and its notes, and each choice is described by its own words, so a screen reader moving onto Crew or Leads hears what that choice would mean before it is made (3.3.2, 4.1.2). Chosen leads with no lead left to tick says so under Which leads, with focus there (3.3.1). While Meetings is open and its meetings are asked for again (a lead's phone let in, or a person no longer a lead), focus on a meeting waits on Back, never on the page behind, and goes back to that meeting when they come, unless the person has moved it meanwhile (2.4.3). Each meeting's line says who it is for in words, Crew, All leads or the leads' names, and a day's name says "crew meeting at 8:00 AM", or "2 meetings, the first at 10:00 AM", never by color alone (1.4.1). axe runs on the dialog with Crew chosen, with Leads, and with the one line, at 320 and 1280 px, and on a crew phone's Meetings; nothing runs off the screen at 320 px (1.4.10). - Automated, on every version (v3.188.44, issue #162):
dev/jobs-check.jschecks What the crew can do in Manager settings > Crew and access. Each crew job is an M3 list item whose whole row is the label of its switch (role switch, at least 48 px tall), named by the job alone (2.5.3) and described by its supporting text, with "Open to everyone on the crew" in words when it is on, so the state is not told by the switch's look alone (1.4.1, 4.1.2). The list is a group named by its heading (1.3.1). Turning one on or off shows a message with a Tell the crew button; the check hears the message in the page's live region (4.1.3), and like every waiting message it stays while focus is on it (2.2.1). A redraw keeps focus on the switch that had it (2.4.3). On a crew phone, when the manager closes a job mid-task, placing ends with a message saying why and focus goes to the left bar, never the page, as it does when Add sprinklers goes from under focus; Keep in Needs repair follows the job while the dialog is open, appears unchecked, and hands focus to its field when it goes (2.4.3, 3.2.2). Axe runs on the section at 320 px and 1280 px, and nothing in it spills sideways at 320 px (1.4.10). - Automated, on every version (v3.188.43, issue #151):
dev/homedot-check.jschecks the sprinkler heads on the home map. Each head is one small dot whose shape tells its state as well as its color (not checked a ring, working a dot, fixed a rounded square, needs repair a larger dot with its exclamation mark and halo), so no state relies on color (1.4.1); a white edge over an ink casing keeps each apart from grass and a pale roof (1.4.11); the dots keep their size on screen at every zoom. Each is a focusable button named in full, with its number, place, station and state, that says it opens a menu (1.1.1, 2.5.3, 4.1.2). A tap reaches 48 px across with a finger and 24 px with a mouse; a tap that reaches several heads opens a list of exactly those heads, each a row at least 48 px tall with its place, its state in words and its swatch, then what a tap there would open beneath them, by its title (a bush bed, a station, a lawn), and Zoom in here (2.5.8); choosing one opens its own choices, and Escape gives focus back to that head (2.4.3); Zoom in here spreads them at least 48 px apart and puts focus back on the head pressed. A key acts on the focused head alone, never the list (2.1.1). Keyboard focus draws the 8 px yellow ring on the casing (2.4.7). Press and hold and Assign work use the same list, Assign work's rows saying which heads are chosen, and a chosen head takes the yellow ring whatever its state; a lit head lights only a ring around it, so its state still shows. axe runs on the list, and it fits at 320 px (1.4.10). - Automated, on every version (v3.189.2, issue #189):
dev/sprinklerbed-check.jschecks Add sprinklers from the home map. Add here places a head at the cross marking the exact middle of the viewport, for anyone using a keyboard or a screen reader, rather than only a precise tap, or zooms in first where heads are not yet drawn instead of placing on top of what cannot be seen (2.1.1, 2.5.8); a head already sitting on the cross is opened rather than a second one stacking under it, and a tap close beside an existing head opens it instead of placing one, matching a zone page's own 24 px target (2.5.8). Opening the Zones list, a mow day's own exact guard, Assign work and every other editing mode all end or block placing the same way they already do on a zone page, so bedMenu's and the bed sheet's own "Add sprinklers" never stay shown for a tap that would do nothing (4.1.2): each shares one guard,placingBlocked, withstartPlacingitself. axe runs on the home map while placing, and nothing spills sideways at 320 px (1.4.10). - Automated, on every version (v3.188.42, issue #136):
dev/meetings-check.jssection 7 checks Meetings in the left bar and Add meeting on the Today page's Who's in card. Add meeting is an outlined button with a decorative icon and its words as its name, beside Add time off; the action row wraps at 320 and 390 px with nothing off the screen, each button at least 44 px tall on touch (1.4.10, 2.5.8). Add a meeting opens with focus on For, its first control, a radio group with one tab stop (1.3.1, 2.4.3); Tab and Shift+Tab stay inside the dialog with either choice made, also after a click that moves no focus (Safari, an iPad tap), because the shared dialog Tab trap now counts a radio group once and goes by position (2.1.2, 2.4.3); Cancel, Escape and Save give focus back to Add meeting, and when another phone's change hides Add meeting under focus, or while its dialog is open, focus goes to Add time off beside it rather than the page (2.4.3). axe runs on the card and the dialog at 320, 390 and 1280 px. - Automated, on every version (v3.188.41, issue #157):
dev/bedctl-check.jschecks the sprinkler beds that name their controller and station. A bed given a Controller, and a Station number when it is one station, carries the station's label, "12-5", or "12" for a controller alone, in the same plate as a station outline's: paper on ink (about 11:1), 20 px tall on screen with a 12 px label, so a bed's station is never known by color alone (1.4.1, 1.4.3, 1.4.11). The label sits where no head's dot is under it, or as a tab beside a crowded bed, in a layer over the outlines so no edge runs through it, and it takes no taps (2.5.8); it is hidden from screen readers, which hear it at the start of the bed's own name, "Station 12-5, sprinkler bed, Bush bed in Zone 1, 3 sprinklers: ..." or "Controller 12, ..." (1.1.1, 2.5.3, 4.1.2). A head on a controller with no station number says "controller 12" in its name, its choices and Mark as fixed (1.3.1, 2.4.6), and an outline like it is "Station on controller 12". The bed's Details label both fields (Controller, Station number; 1.3.1, 3.3.2), offer the controllers already on the map through the field's native list, say what the fields do, and fit at 360 px (1.4.10); the bed's menu gains Details for a manager; Show on the map for a station drawn only as a bed lights the bed itself, drawn once. axe runs on the bed's Details, its menu and the Stations list. - Automated, on every version (v3.188.39, issue #141):
dev/sprexport-check.jschecks the sprinkler export in Manager settings > Data. The three buttons (Export CSV, Export KML, Export GeoJSON) are one group named by the section's heading, Sprinkler heads (1.3.1, 4.1.2); the line under them says how many heads there are and whether they carry latitude and longitude, and when they cannot, where to line the map up with the earth (3.3.3); it also describes the group (aria-describedby) and is brought up to date each time the drawer opens, which the check does not test yet (#145); each export says in the status message how many heads went, or why none did (4.1.3); axe runs on the open Data drawer at 1280 px and nothing spills sideways at 360 px (1.4.10). - Automated, on every version (v3.188.38, issue #141):
dev/station-check.jssection 6 checks the Stations list. The Stations button says it opens a dialog (aria-haspopup="dialog"); the list is one dialog with three levels (the controllers, a controller's stations, a station's heads), each row one button whose name says what it shows and what it opens ("Opens its stations", "Opens its heads", "Opens its details") (4.1.2, 2.5.3); a number a controller is missing is a row that does nothing and says "Not on the map yet" in words, its number in a dashed circle, never by color alone (1.4.1); the filter chips are buttons witharia-pressed, the chosen one leading with a check (1.4.1, 4.1.2); entering a level puts focus on its first row that opens something, Back on the row of the level just left, and another phone's change or Details closing keeps focus on the same control or the first row, never on the page below (2.4.3); how many stations a level or chip shows is said once, when it changes (4.1.3); a typed controller or station name is never sent to be translated. axe runs on the list at 360 px, and nothing spills sideways at 320 px (1.4.10). - Automated, on every version (v3.188.38, issue #141):
dev/station-check.jssection 7 checks the heads numbered by station. Each station numbers its own heads from 1, on a bed's dot and a round head's badge alike, and a head's name says whose number it is, "Sprinkler head 2 of station 12-3, in Zone 1", as do its choices, Needs repair and Details (1.1.1, 2.5.3). In the station's list a manager or a lead moves a head with Move up and Move down, named "Move sprinkler 2 up", as well as by dragging its number, so no order needs a drag (2.5.7, 4.1.2); the button that moved a head keeps focus, or at the end of the list its other button does (2.4.3), and where the head went is said, "Sprinkler moved to number 1 of 4" (4.1.3); a drop on a list another phone changed meanwhile moves nothing and says why. A bush bed's heads inside a station show no move buttons, which would not keep: the bed's list says they are put in order in the station's list and offers Open the station, and the bed's other heads keep their own numbers, never one twice (3.3.2, 1.3.1). A head's choices lead with what its number belongs to, its station first (1.3.1, 2.4.6). A tap on a station opens its heads, and the click the browser sends after the tap never presses the row under the finger (2.5.2); a tap that answers "which marker?" goes to it first, on a zone page and the home map. A station outline's name ends with what a tap opens, "Opens its heads", or "Opens its details" for a manager on one not yet named, witharia-haspopup="dialog", and says nothing of it while a tap does something else (placing heads, answering "which marker?", editing) (4.1.2). axe runs on the list with its move buttons and on a bed inside a station. - Automated, on every version (v3.188.37, issue #141):
dev/faults-check.jschecks Needs repair's problems. Other is a chip button that says what it opens (aria-expanded,aria-controls), moves focus into its labelled text field, and closes again when tapped, focus staying on it (2.4.3, 3.3.2, 4.1.2); Enter or Add puts the words on the report as a chosen chip and gives that chip focus, and Add with nothing typed says so in the status message and keeps focus in the field (3.3.1, 4.1.3). Focus never falls to the page: not when a typed chip is unchosen and goes (the chip in its place takes it), nor when the list arrives from another phone or comes back from this phone's own write while a chip has focus (2.4.3). Words typed but not added still go on the report, and leaving with them asks first (3.3.4). Keep it in the list for everyone is a labelled checkbox shown only to whoever may use it. Manager settings > Site > Sprinkler problems is an M3 list with one trailing delete button per row, named for its problem ("Remove Cracked riser"); Remove asks first (3.3.4) and gives focus to the next row's button, else Add a problem (2.4.3). The area's own and typed problems are kept from translation, as typed words are. axe runs on Needs repair with Other open at 320 px and on the Site drawer at 1280 px, and nothing spills sideways at 320 px (1.4.10). - Automated, on every version (v3.188.36, issue #141):
dev/station-check.jschecks the stations on the map. A named station outline carries its label, "12-3" (its controller's tag, then its number), paper on ink (about 11:1), 20 px tall on screen with a 12 px label, so a station is never known by its color alone (1.4.1, 1.4.3, 1.4.11); the label takes no taps, so a finger on it reaches the outline (2.5.8), and it is hidden from screen readers, which hear it at the start of the outline's own name, "Station 12-3, G-SP-N LAWN in Zone 1, not done", with its controller in full (1.1.1, 2.5.3, 4.1.2). A done station's label sits above its date's pill at every zoom, never over it. A head's name says its station after its place ("Sprinkler head 2 in Zone 1, station 12-3"), and its choices name the station in full on the line under the title (1.3.1, 2.4.6), as Needs repair, Mark as fixed and Details do. A station outline's Details label every field (Controller, Station number, Station name; 1.3.1, 3.3.2), offer the controllers already on the map through the field's native list rather than a new control, and fit at 360 px (1.4.10); axe runs on them and on a head's choices. - Automated, on every version (v3.188.33):
dev/fixtime-check.jsruns a crew member's sprinkler audit at 360 px against the real rules (Working, Needs repair, Fixed, a problem's pin and a head's repair fixed) and checks that the only status message after a fix is "Marked as fixed.": until this version a refused settings write replaced it a second later with "Only a manager's phone can change that. Unlock manager settings and try again.", an error that had not happened (3.3.1, 4.1.3). - Automated, on every version (v3.188.31):
dev/meetings-check.js's issue #107 sections check the leads calendar's follow-ups. The Leads button is listed under Manager settings > Left bar and moves like any other button through the same named rows, arrows and menus already checked there, staying only on the manager's and the leads' phones wherever it sits; a left-bar group heading toggled from the keyboard keeps focus on itself across the bar's redraw, as the items already did (2.4.3); a notice's landing never pulls focus out of an open dialog when the server's copy arrives, and once dropped it never jumps on a later copy (2.4.3); when the calendar closes with Leads inside a closed heading, focus still lands on the bar (2.4.3); and the agenda's day headings and Add a meeting's day run on the area's own calendar, the one the server keeps meetings by, so the day a person reads or picks is the one the area means, with an unreadable zone falling back to the phone's (1.3.1, 3.3.2). - Automated, on every version (v3.188.28):
dev/meetings-check.jsruns axe on the leads calendar and on Add a meeting at 320 px and 1280 px. Each day of the agenda is a heading over a list of its meetings, and each meeting's time, title, place, leads and notes are text (1.3.1); the manager's More is named for its meeting, and All leads and Chosen leads are a single-select group of radio buttons (4.1.2); what is missing in the dialog is said and focus moves to that field (3.3.1, 3.3.3); Escape and Back close the calendar and focus returns to Leads in the left bar (2.4.3); a notice's landing keeps focus inside the calendar while the server's copy of the meeting is awaited, and the meeting takes it when it lands (2.4.3); when this phone may no longer open the calendar, it closes, says why, and focus lands on Today in the left bar, surviving the bar's redraw (2.4.3, 4.1.3); a phone that may not open it says why in a status message, never a blank page (4.1.3); nothing spills sideways at 320 px (1.4.10). - Automated, on every version (v3.188.27):
dev/sprinklerbed-check.jsruns axe on Sprinklers in this bed at 360 px and checks that it fits at 320 px (1.4.10); that a sprinkler bed on the map is a button named for the bed and how its heads stand, which opens its list by tap or Enter and its menu by right-click, press and hold or the menu key, with focus given back to the bed when the list closes (2.1.1, 2.4.3, 4.1.2); that each head's row says its number, name, type and state in words (1.4.1, 4.1.2); that Move up and Move down reorder the heads as dragging a number does, with focus kept on the button that moved it and the new place announced (2.5.7, 4.1.3); and that the bed's hover pill shows each head's state glyph as well as its color (1.4.1). It drags with a real mouse and a real finger, checks that a drag let go after another phone changed the bed moves nothing and says so in a toast that is seen as well as read out (4.1.3, 1.4.1), that a second finger landing mid-drag is ignored (2.5.7), that a redraw from another phone keeps focus on the same control in the list, falling to the head's enabled button when its own is drawn disabled and ending at the dialog's Close button when the bed's last heads are deleted, so focus never lands behind the open dialog, and that Done gives focus back to Add sprinklers (2.4.3), that the place bar's unchanged hint is not written again after every placed head (until v3.189 it also checked the storage warning's colors and words, gone with the warning), and that the map's numbered dots and beds take the same yellow focus ring as other map objects, pressed from the keyboard, with the ring-hiding class asserted absent rather than removed before reading (2.4.7). - Automated, on every version (v3.188.26):
dev/localday-check.jsruns the app on Denver time at 7:30 pm and on Berlin time just after midnight, and checks that the days it shows and offers are the phone's own: Done on another day lists Today with today's date first and chosen, and the Hours drawer, the Progress charts, History's ranges and the dates beside saved times all follow the phone's calendar. Nothing is presented or operated differently; a date read aloud is now the one the person sees on their calendar (3.3.2, 1.3.1). - Automated, on every version (v3.188.23):
dev/buildingmap-check.jsruns axe on Floorkeeping's building map (this building, a supervisor's buildings, List) and on placing a building, at 320 px and 1280 px; it checks that every building's name on the map is a button named for it, also with the real map library, which names each marker "Map marker" as it adds it (1.1.1, 2.5.3, 4.1.2), that no name covers another or sits under the zoom buttons or the credit line, a name that would cover one waiting until there is room, with the note under the map saying so (2.5.8), that List reaches every building without the map, the ones not on it too, and replaces the map on a phone that cannot draw it (2.1.1), that focus starts on Back, that Tab and Shift+Tab stay inside the view, that the map itself shows a two-colour ring when it has the focus, and takes the focus from a name that has to go (2.4.7), and that focus returns to Map after Escape, or to the Settings button placing was opened from (2.4.3), that a corner can be added without a precise tap, at a cross in the middle with Add corner here, and the map moved by zoom buttons (2.5.7), that leaving with a corner added, moved or cleared asks first (3.3.4), and that nothing spills sideways at 320 px and every placing control can be reached on a phone on its side (568 by 320 px) and at 400% zoom (320 by 256 px) (1.4.10). The Campus map's area buttons kept the name "Map marker" for the same reason until this version; they now carry the area's name. - Automated, on every version (v3.188.22): KBota Run (its name since v3.188.24), the Break time game that was Cart Run, is a canvas game played by keyboard (left and right arrows, or A and D), by the 44 px buttons under the path on a phone, or by swipes and taps, so no path-based gesture is needed (2.5.1). Each level's name, season and what it brings is said in a polite live region as it starts, as are a bump, a spare tire and mud (4.1.3), and the heading above the path keeps the level, distance, tools and lives as text. The warning before an oncoming mower blinks over a small area, far under the area 2.3.1 limits, and stays on instead with reduced motion. The hazards this version adds (the mower and floor scrubber, scooters, mops, sprinkler spray, the wet floor) are drawn with a dark and a light edge or in a colour of at least 3:1 against every season's path and hall (1.4.11), and at dusk everything in the way keeps a reflector. The cones, benches, bridges, chairs and signs kept from Cart Run are still under 3:1 on some paths; outlining them is the first of the game's later items (GitHub issue #66). The start card says Level for this game, through the same names as the stage picker (Easier level, Harder level, 12 levels open).
dev/kbota-check.jsplays the game frame by frame anddev/games2-check.jschecks the start and end cards and the keys in a browser. - Automated, on every version (v3.188.18):
dev/issuemirror-check.jsruns axe on the author's issue dialog at 320 px and 1280 px, and checks that the words saying what is copied to GitHub are the dialog's supporting text under its headline, describe both the Title and Detail fields (1.3.1, 4.1.2), and fit without spilling sideways (1.4.10). - Automated, on every version (v3.188.17):
dev/indoor-check.jsreads everything a Floorkeeping manager can see in Settings, Help, Progress and the dialogs, with every drawer open, and fails on an outdoor-only word or a phrase the product's words would garble; it checks that a days field or season switch keeps the focus, and anything half typed, when another phone's save redraws Settings (2.4.3, 3.2.1), that a Help search marks its matches without rewriting Help, so each word keeps its language and the product's words, that a section switched off for a hidden task comes back when the task does, and runs axe on Settings and Help at 360 px and 1280 px. - Automated, on every version (v3.188.16):
dev/sprinkler-check.jsruns axe on a sprinkler head's choices (a bottom sheet at 360 px, a menu at 1280 px) and on Needs repair (full screen at 360 px, a basic dialog at 1280 px); it checks that the choices are named for the sprinkler, are single-select with the chosen one announced (4.1.2), open from the keyboard with focus in them and give focus back on Escape (2.4.3), that focus comes back to the head (or, on the Today page, to the same work's button) after Working, Undo, Needs repair, Mark as fixed and Details, also on a home map that draws over several frames (2.4.3), that the Working snackbar's Undo stays while focus is on it (2.2.1), that a real finger tap (at 360 px, with reduced motion, on two tablet sizes, and a press and hold let go) opens the choices and never chooses one by itself, and that a tap on the sheet's scrim only closes it (2.5.2), and that nothing spills sideways at 320 px (1.4.10). Since the review of that version it also checks that the sheet is a modal dialog holding the menu with the page behind it inert (1.3.2, 2.4.3), its handle a button beside the menu rather than one of its items and the three states one group (4.1.2), that the scrim is hidden from screen readers and closes on a click with no press, as a screen reader's double tap sends (4.1.2, 2.5.2), that a finger tap leaves focus on the head and that a dialog opened from any menu's item gives focus back to what opened the menu (2.4.3), that the Undo of Mark done on Today stays while focus is on it (2.2.1), that a menu with no room below what it is about opens above it instead of covering it, that turning the phone with the sheet open turns it into a menu and back with focus kept and the menu clear of the head (1.3.4), that in an audit by finger the head tapped, not the last one chosen, gets focus back (2.4.3), that a menu opened by a tap shows no focus ring until a key is pressed while one opened from the keyboard does (2.4.7), that focus stays on Today, never the page, after Mark done from the keyboard and after Mark fixed with Change details? (2.4.3), and that a button Safari leaves without focus when tapped still gets it back when its menu closes (2.4.3). - Automated, on every version: the public pages people reach without the app,
sms.html(text sign-up,dev/smsopt-check.js)report.html(the QR code sign's report form,dev/inboxin-check.js) andsupply.html(the restroom door sign,dev/supplies-check.js), are run through axe at 320 px and 1280 px, with their labels, errors, focus and reflow checked.
- Automated, on every version:
Terms used: Supports, Partially supports, Does not support, Not applicable.
Summary
The app supports WCAG 2.2 Level A and AA with three qualifications.
1. The satellite map and the marker layer are two-dimensional content. They are exempt from reflow, and every map object can still be reached and used with the keyboard. 2. A manager can choose colors for marker outlines, mow days, library folders and chips. On library folders and chips the app picks black or white text for whichever contrasts more. On the map it cannot guarantee contrast for a color a manager picks over the manager's own imagery, though a dark casing under every outline keeps the defaults readable. 3. How-to videos are the manager's own YouTube links, so their captions depend on how the manager uploads them. Help asks for captions.
Level A
| Criterion | Level | Conformance | Notes |
|---|---|---|---|
| 1.1.1 Non-text Content | A | Supports | Every icon button has an accessible name; decorative SVGs are aria-hidden; map markers carry names describing kind, zone, state, and assignment. |
| 1.2.1 Audio-only and Video-only | A | Not applicable | The app plays no prerecorded media of its own. |
| 1.2.2 Captions (Prerecorded) | A | Partially supports | How-to videos are manager-supplied YouTube links; captions are the uploader's responsibility (Help instructs managers to enable them). |
| 1.2.3 Audio Description or Media Alternative | A | Partially supports | As above. |
| 1.3.1 Info and Relationships | A | Supports | Native headings, lists, tables (schedule), details/summary drawers, labeled fields, dialog roles. |
| 1.3.2 Meaningful Sequence | A | Supports | DOM order matches visual order in panels and dialogs. |
| 1.3.3 Sensory Characteristics | A | Supports | Instructions refer to control names, not only shape or position. |
| 1.4.1 Use of Color | A | Supports | Done state uses fill plus date and initials; unfinished markers carry a glyph; a sprinkler head's Working, Needs repair and Fixed each carry their own glyph (check, exclamation mark, wrench) as well as their color, on zone pages and the choices, and their names are in the marker's accessible name; on the home map and the zone pages each head is a small dot whose shape tells its state as well (not checked a ring, working a dot, fixed a rounded square, needs repair a larger dot with its exclamation mark; issues #151 and #177), and the bed's list and hover pill give each head's state in words or with its glyph; mow days carry text labels; problem and photo pins differ in glyph; the status page's day bars differ in height as well as color, with a table of the days that were not working. |
| 1.4.2 Audio Control | A | Not applicable | No auto-playing audio. |
| 2.1.1 Keyboard | A | Supports | All controls are native buttons, inputs, and selects; map markers, pins, zones, mower areas, and mow days are focusable with Enter/Space to act and Shift+F10 for menus; shape points are focusable and moved with arrow keys. Segmented choices such as "When do you need it?" in Ask for a tool (v3.122) are radio groups with one tab stop, moved with the arrow keys. |
| 2.1.2 No Keyboard Trap | A | Supports | Dialogs trap Tab intentionally and release on Escape or any close action; menus close on Escape. |
| 2.1.4 Character Key Shortcuts | A | Supports | Single-key shortcuts (+, -, 0, arrows) are active only when focus is on the map region and not in a field, dialog, or panel. The Break time games' letter keys (W A S D, and in Sod Stack X, Z and C) work only while a game is open full screen. |
| 2.2.1 Timing Adjustable | A | Supports | No time limits on tasks; toasts are informational and announced. A message with an action (Undo, Try again) does not time out while focus is on it (a newer one with an action can take its place). The optional Break time games are timed by design, the real-time exception in 2.2.1; they never hold up work. |
| 2.2.2 Pause, Stop, Hide | A | Not applicable | No moving or auto-updating content beyond user-initiated map changes. |
| 2.3.1 Three Flashes | A | Supports | Nothing flashes more than twice a second: the warnings in the Break time games (a goose about to recover, the mower after a hit) blink at most twice a second, over a small area, and not at all with reduced motion. |
| 2.4.1 Bypass Blocks | A | Supports | Skip link to the map; landmarks for the rail, map, and panels. |
| 2.4.2 Page Titled | A | Supports | The product name. |
| 2.4.3 Focus Order | A | Supports | Rail, map objects, then panels; dialogs move focus in and return it. |
| 2.4.4 Link Purpose | A | Supports | Links are named ("Open in YouTube", "Open link"). |
| 2.5.1 Pointer Gestures | A | Supports | Pinch zoom has + and - buttons; box select has tap-to-add. Every swipe, drag and flick in the Break time games has an on-screen button (the pad, and Hold in Sod Stack) and a key. The photo viewer's swipe to the next photo has the Previous and Next buttons, above the photo on a phone too, and the arrow keys. |
| 2.5.2 Pointer Cancellation | A | Supports | Actions complete on release; marks can be cleared; drags can be reverted. |
| 2.5.3 Label in Name | A | Supports | Accessible names begin with the visible label. |
| 2.5.4 Motion Actuation | A | Not applicable | No motion input. |
| 3.1.1 Language of Page | A | Supports | html lang matches the language chosen in Settings ("en" by default). |
| 3.2.1 On Focus | A | Supports | Focus changes nothing. |
| 3.2.2 On Input | A | Supports | Selects and checkboxes apply settings without changing context; the effect is described beside them. |
| 3.2.6 Consistent Help | A | Supports | Help and Feedback are in the same position in the left bar on every view. |
| 3.3.1 Error Identification | A | Supports | Validation messages name the field and the fix (for example, hours format). |
| 3.3.2 Labels or Instructions | A | Supports | Every field has a visible label; formats are given as examples. |
| 3.3.7 Redundant Entry | A | Supports | Previously entered names, hours, and choices are prefilled or selectable. |
| 4.1.2 Name, Role, Value | A | Supports | Custom controls carry roles and states (pressed filters, checked stars, dialog, menu, menuitem, radio groups with aria-checked, the Me too chip with aria-pressed). |
Level AA
| Criterion | Level | Conformance | Notes |
|---|---|---|---|
| 1.2.4 Captions (Live) | AA | Not applicable | |
| 1.2.5 Audio Description | AA | Partially supports | Manager-supplied videos, see 1.2.2. |
| 1.3.4 Orientation | AA | Supports | Portrait and landscape both work. |
| 1.3.5 Identify Input Purpose | AA | Supports | Email fields use autocomplete="email"; the PIN uses current-password. |
| 1.4.3 Contrast (Minimum) | AA | Supports | Text on surfaces meets 4.5:1 (ink #1C1C1C and #46484B on light surfaces; white on navy #173D52 at 9.9:1). |
| 1.4.4 Resize Text | AA | Supports | Text resizes to 200% without loss; the rail widens and labels wrap. |
| 1.4.5 Images of Text | AA | Supports | None used. |
| 1.4.10 Reflow | AA | Supports (map exempt) | Panels reflow to 320 px; the schedule switches to a one-day list; the map is two-dimensional content. |
| 1.4.11 Non-text Contrast | AA | Partially supports | Controls and focus indicators meet 3:1; default marker outlines carry a dark casing over imagery, and a sprinkler head's dot a white edge over an ink casing, on the home map (v3.188.43, issue #151) and the zone pages (v3.188.50, issue #177); manager-chosen colors are outside the app's control. |
| 1.4.12 Text Spacing | AA | Supports | No fixed-height text containers that clip. |
| 1.3.1 (tables, v3.162) | A | Supports | Scrolling tables are keyboard-focusable named regions, so their content can be scrolled without a mouse. |
| 1.4.13 Content on Hover or Focus | AA | Supports | The hover pill is dismissible with Escape, hoverable, and persists while hovered. The first-run tour's card stays until Next, Back, End tour or Escape, and covers nothing it points at. |
| 2.4.5 Multiple Ways | AA | Not applicable | Single-page application; Help has search. |
| 2.4.6 Headings and Labels | AA | Supports | |
| 2.4.7 Focus Visible | AA | Supports | 2 px primary outline on controls; 4 px yellow ring on map objects. |
| 2.4.11 Focus Not Obscured (Minimum) | AA | Supports | Focused rail items scroll into view; panels do not cover focused controls. |
| 2.5.7 Dragging Movements | AA | Supports | Every drag has a non-drag alternative: arrow keys move points; taps add and select; zoom buttons replace pinch; a sprinkler bed's heads, reordered by dragging a number, also have Move up and Move down (v3.188.27). |
| 2.5.8 Target Size (Minimum) | AA | Supports | Buttons 40 px, chips 32 px, edge dots and points 24 px, gallery buttons 32 px. A map marker's tap pad is 48 px however small it draws; while placing sprinklers, a head keeps a target of at least 24 px across (v3.188.29). In Edit this zone a head is the same dot with the same reach; while a drawing tool is on it takes no taps so a corner can be placed beside it, except with the head tool, where a tap on or beside a head selects it (v3.189.3, issue #199). Anywhere in Edit this zone a tap does not open the list of overlapping heads; it selects the one on top, and Select area or a zoom in reaches the others. A sprinkler head's dot reaches 48 px across with a finger and 24 px with a mouse, on the home map (v3.188.43, issue #151) and the zone pages (v3.188.50, issue #177); where several heads' reaches overlap, a tap opens a list of them with rows at least 48 px tall, the equivalent control. |
| 3.1.2 Language of Parts | AA | Supports | The interface can be shown in another language; the page language (html lang and dir) changes with it. Text people type is shown as written. |
| 3.2.3 Consistent Navigation | AA | Supports | The left bar is the same on every view (managers may reorder it, which is a user-initiated change). |
| 3.2.4 Consistent Identification | AA | Supports | |
| 3.3.3 Error Suggestion | AA | Supports | |
| 3.3.4 Error Prevention (Legal, Financial, Data) | AA | Supports | Deletions and clears confirm; marks are reversible. |
| 3.3.8 Accessible Authentication (Minimum) | AA | Supports | The manager PIN field allows paste and password-manager fill (autocomplete="current-password") and can be kept unlocked. The two-step code field allows paste and one-time-code autofill, and accepts spaces; so do the author's code fields, and the author's recovery codes can be copied and pasted (v3.188.11). Campus sign-in uses the university's own sign-in page. Author and organization keys are pasted. |
| 4.1.3 Status Messages | AA | Supports | Toasts and the schedule's "in right now" line are announced through a polite live region, and so is a zoom that shows or hides the sprinkler heads (v3.188.50, issue #177). The status page announces a change in its headline, not each minute's refresh. |
Known limitations and roadmap
- Testing with assistive technology on devices is scheduled (
docs/AT-TEST-PLAN.md), and results will be summarized here fromdocs/AT-RESULTS.md. Until then, this report reflects code review and automated checks. - Manager-chosen colors and manager-supplied videos are outside the app's control. The defaults, automatic text color in the Library, and Help guidance address them.
- The live satellite map is context for the marker layer; its attribution control comes from the map library.
- Every new screen or dialog is added to
dev/axe-check.js, and a version with a violation does not ship.
Contact
Accessibility problems or questions: rems.byustudies@gmail.com. You will have an answer within three working days.