Skip to the content

Props

NameTypeDefaultDescription
vehicles*readonly VehicleTableVehicle[](required)The vehicles to show, one row each, in this order: each one's line, destination, data state and delay. An empty array shows the header row alone (CR-6).
sortVehicleTableSort | null—The order the caller has put vehicles in, which the table marks on that column's header with aria-sort and an arrow; it reorders no row. Left out, or null, no column is marked (CR-10).
onSortChange(event: MouseEvent<HTMLButtonElement>, sort: VehicleTableSort) => void—Called when a column's header is pressed, with the press's event and the sort it asks for: that column ascending, or the other way when sort already names it. Given, every header is a button and says its sort state; the caller sorts vehicles and passes the new sort (CR-10).
delayColumnHiddenbooleanfalseLeaves out the Delay column, for vehicles whose data carries no delay at all (CR-12)
lastSeenColumnVisiblebooleanfalseShows the Last seen column, the time each vehicle last reported, before the Delay column (CR-11)
lineColumnLabelstringThe locale'sOverrides "Line", the header of the line column
destinationColumnLabelstringThe locale'sOverrides "Destination", the header of the destination column
positionColumnLabelstringThe locale'sOverrides "Position", the header of the data state column
delayColumnLabelstringThe locale'sOverrides "Delay", the header of the delay column
lastSeenColumnLabelstringThe locale'sOverrides "Last seen", the header of the last-seen column
unknownLabelstringThe locale'sOverrides "Unknown", heard in place of "—" for a vehicle with no last-seen time
liveLabelstringThe locale'sOverrides "Live", the state's word when a vehicle's variant is 'live'; given to every VehicleDotLabel
ageingLabelstringThe locale'sOverrides "Ageing", the state's word when a vehicle's variant is 'ageing'; given to every VehicleDotLabel
estimatedLabelstringThe locale'sOverrides "Estimated", the state's word when a vehicle's variant is 'estimated'; given to every VehicleDotLabel
timetableOnlyLabelstringThe locale'sOverrides "Timetable only", the state's word when a vehicle's variant is 'timetable-only'; given to every VehicleDotLabel
noSignalLabelstringThe locale'sOverrides "No signal", the state's word when a vehicle's variant is 'no-signal'; given to every VehicleDotLabel
minutesUnitLabelstringThe locale'sOverrides "min", the unit after each delay's figure; given to every Delay
minutesLateLabelstringThe locale'sOverrides "min late", heard after the number when late; given to every Delay
minutesEarlyLabelstringThe locale'sOverrides "min early", heard after the number when early; given to every Delay
onTimeLabelstringThe locale'sOverrides "On time"; given to every Delay
delayUnknownLabelstringThe locale'sOverrides "Delay unknown", heard in place of "—"; given to every Delay
classNamestring—Joined after pn-VehicleTable on the root
styleCSSProperties—Reaches the root
refRef<HTMLTableElement>—Reaches the root element
vehicles (required)readonly VehicleTableVehicle[], default (required)
The vehicles to show, one row each, in this order: each one's line, destination, data state and delay. An empty array shows the header row alone (CR-6).
sortVehicleTableSort | null, default —
The order the caller has put vehicles in, which the table marks on that column's header with aria-sort and an arrow; it reorders no row. Left out, or null, no column is marked (CR-10).
onSortChange(event: MouseEvent<HTMLButtonElement>, sort: VehicleTableSort) => void, default —
Called when a column's header is pressed, with the press's event and the sort it asks for: that column ascending, or the other way when sort already names it. Given, every header is a button and says its sort state; the caller sorts vehicles and passes the new sort (CR-10).
delayColumnHiddenboolean, default false
Leaves out the Delay column, for vehicles whose data carries no delay at all (CR-12)
lastSeenColumnVisibleboolean, default false
Shows the Last seen column, the time each vehicle last reported, before the Delay column (CR-11)
lineColumnLabelstring, default The locale's
Overrides "Line", the header of the line column
destinationColumnLabelstring, default The locale's
Overrides "Destination", the header of the destination column
positionColumnLabelstring, default The locale's
Overrides "Position", the header of the data state column
delayColumnLabelstring, default The locale's
Overrides "Delay", the header of the delay column
lastSeenColumnLabelstring, default The locale's
Overrides "Last seen", the header of the last-seen column
unknownLabelstring, default The locale's
Overrides "Unknown", heard in place of "—" for a vehicle with no last-seen time
liveLabelstring, default The locale's
Overrides "Live", the state's word when a vehicle's variant is 'live'; given to every VehicleDotLabel
ageingLabelstring, default The locale's
Overrides "Ageing", the state's word when a vehicle's variant is 'ageing'; given to every VehicleDotLabel
estimatedLabelstring, default The locale's
Overrides "Estimated", the state's word when a vehicle's variant is 'estimated'; given to every VehicleDotLabel
timetableOnlyLabelstring, default The locale's
Overrides "Timetable only", the state's word when a vehicle's variant is 'timetable-only'; given to every VehicleDotLabel
noSignalLabelstring, default The locale's
Overrides "No signal", the state's word when a vehicle's variant is 'no-signal'; given to every VehicleDotLabel
minutesUnitLabelstring, default The locale's
Overrides "min", the unit after each delay's figure; given to every Delay
minutesLateLabelstring, default The locale's
Overrides "min late", heard after the number when late; given to every Delay
minutesEarlyLabelstring, default The locale's
Overrides "min early", heard after the number when early; given to every Delay
onTimeLabelstring, default The locale's
Overrides "On time"; given to every Delay
delayUnknownLabelstring, default The locale's
Overrides "Delay unknown", heard in place of "—"; given to every Delay
classNamestring, default —
Joined after pn-VehicleTable on the root
styleCSSProperties, default —
Reaches the root
refRef<HTMLTableElement>, default —
Reaches the root element

Use it

import { VehicleTable } from '@mylder/perron/patterns'

Ask your agent

search_docs({ query: "VehicleTable" })

Keyboard and ARIA

No Base UI part, so nothing from Base UI here: the rows are read, not operated. They take no focus, and no key does anything of their own (CR-7). In a sortable table each header's sortButton is a native button, one tab stop each, in column order, pressed with Enter or Space as any button is (CR-10). A screen reader's table keys, which move from cell to cell, are the screen reader's.

KeyDoesFrom
TabIn a sortable table, moves to each header's button in column order; past them, or in a table that is not sortable, passes the rows by, or reaches a caller's link in a destination, row by row in reading orderThe browser
Enter, SpaceOn a header's button, calls onSortChange with that column's sort (CR-10)The browser's click
ElementRoleAccessible name fromStates announced
roottable, from the native <table>The caller's aria-labelledby or aria-label; none of its ownnone
headerCellcolumnheader, from <th scope="col">Its text, the column's owned stringIts sort, from aria-sort: ascending or descending on the sorted column, and no aria-sort on the others (CR-10)
sortButtonbutton, from the native <button>Its text, the column's owned string; the sortIcon is aria-hiddennone of its own; its header's sort
rowrow, from the native <tr>All its cells' text, "5C Husum Torv Live On time", or "5C Husum Torv Live 08:14 On time" with Last seennone
lineCellrowheader, from <th scope="row">Its text, the badge's lineNamenone
destinationCellrowheader, from <th scope="row">Its text, the vehicle's destinationnone
positionCellcell, from the native <td>Its text, the state's word; the dot is aria-hidden inside VehicleDotLabelnone
lastSeenCellcell, from the native <td>The time, "08:14", or "Unknown" in place of "—" (CR-11)none
delayCellcell, from the native <td>Delay's words: "3 min late", "On time", "Delay unknown"none

The row headers are what a screen reader adds to a cell as it moves through a column: moving down the delay column, it reads "5C, Husum Torv, Delay, On time", the row headers and the column header before the cell: each delay is heard with the vehicle it belongs to. A header's sort is heard from its aria-sort, which screen readers read with the column header, "Delay, sorted descending"; the arrow beside the words is hidden, since aria-sort says the same, and a sortable column not sorted says none, which the button's role already tells apart from a header that cannot sort.

What axe won't catch, from the contract, each with how this component meets it:

  • Everything the eye is told, the ear is told, for every shape each prop accepts: the line's colour is named by lineName, read from the badge; the dot's shape by the state's word beside it, the dot aria-hidden (VehicleDotLabel's CR-2); the delay's figure and colour by Delay's words, and an unknown one's "—" by "Delay unknown" (CR-4); an unknown last-seen time's "—" by "Unknown" (CR-11); the sorted column's arrow and ink by its header's aria-sort (CR-10); each column's meaning by its header, which a screen reader reads with each cell.
  • Every landmark and named region has an accessible name: the table is not a landmark; it takes its name from the caller, through aria-label or aria-labelledby on the root, and has none of its own (CR-5): every story names it with aria-label, and Default's play names one from a heading through aria-labelledby, as Do and don't asks of a screen.
  • Every default label can be overridden: lineColumnLabel, destinationColumnLabel, positionColumnLabel, delayColumnLabel, lastSeenColumnLabel, unknownLabel, the five state labels from liveLabel to noSignalLabel, and Delay's five, minutesUnitLabel, minutesLateLabel, minutesEarlyLabel, onTimeLabel and delayUnknownLabel.
  • Every shape the type accepts renders, in every variant: lineName and destination render a string, an element and a list alike; vehicles renders empty, with one vehicle and with many; delay a number, null and left out, in every variant (CR-4); lastSeen a time and left out (CR-11); sort each column and direction, null and left out, with and without onSortChange (CR-10); a long destination wraps inside its column (CR-8).
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: none here; nothing animates or collapses. The dot and the delay's figure are aria-hidden inside their components because words say them, not for animation.
  • Native semantics first: a native <table>, <thead>, <tbody>, <tr>, <th scope>, <td>, <time> and, for a sortable header, <button>, whose roles no CSS display overrides (CR-1); the one ARIA of the table's own is aria-sort, which no element says.
  • Focus moves in reading order and returns on close: the rows take no focus and the table opens nothing; a sortable table's header buttons are reached in column order, then a caller's link in a destination in row order, its DOM order.
  • Every target is at least size.target-min square: a header's sortButton fills its cell and is at least size.target-min tall and wide (CR-10); the rows have no target of their own (CR-7); a caller's link in a destination is typography.label text in a row at least size.vehicle-row tall, with that much room around it.
  • Loops stop when the system asks for less motion: none here; nothing moves. A vehicle whose data changes is new props from the screen, not an animation.

Owned strings

The line's name and the destination are the caller's data, and so is the table's accessible name (see Props); the column headers are the table's own, and the state's words and the delay's are VehicleDotLabel's and Delay's, overridable here through the props above.

The table adds five owned strings for its column headers, lineColumn "Line", destinationColumn "Destination", positionColumn "Position", lastSeenColumn "Last seen" and delayColumn "Delay", outside content design's table, and says the existing unknown, "Unknown", for a last-seen time no one gave. No key in packages/perron/src/locale/en.ts says any of them: delays, "Delays", is the heading of a line's delays and the name of a kind of disruption, plural, not a column's name. "Position" names what the five data states measure, how fresh a vehicle's reported position is, in a word a rider knows, where "Data state" is the system's own term; and the five are a table's headers rather than data-state, delay or disruption words, as DepartureRow's "Now" and StopTimeline's "Next stop" stayed out of content design's table. "Last seen" is the words Data states uses of a no-signal vehicle, which keeps moving on the timetable "from where it was last seen", for the time that state is measured from. A sorted header adds no words: its state is aria-sort's, which the screen reader says in its own language.

KeyEnglishOverrideSeen or heard where
lineColumn"Line"lineColumnLabelThe line column's headerCell, seen, and heard with each cell of the column; new
destinationColumn"Destination"destinationColumnLabelThe destination column's headerCell; new
positionColumn"Position"positionColumnLabelThe data state column's headerCell; new
delayColumn"Delay"delayColumnLabelThe delay column's headerCell; new
lastSeenColumn"Last seen"lastSeenColumnLabelThe last-seen column's headerCell; new with Last seen
unknown"Unknown"unknownLabelA lastSeenCell with no lastSeen, heard in place of "—"; an existing key
live"Live"liveLabelstatus, seen and heard when a vehicle's variant is 'live'; an existing key
ageing"Ageing"ageingLabelstatus, when 'ageing'; an existing key
estimated"Estimated"estimatedLabelstatus, when 'estimated'; an existing key
timetableOnly"Timetable only"timetableOnlyLabelstatus, when 'timetable-only'; an existing key
noSignal"No signal"noSignalLabelstatus, when 'no-signal'; an existing key
minutesUnit"min"minutesUnitLabeldelay, after the figure, as Delay's spec gives it; an existing key
minutesLate"min late"minutesLateLabeldelay, as Delay's spec gives it; an existing key
minutesEarly"min early"minutesEarlyLabeldelay, as Delay's spec gives it; an existing key
onTime"On time"onTimeLabeldelay, as Delay's spec gives it; an existing key
delayUnknown"Delay unknown"delayUnknownLabeldelay, as Delay's spec gives it; an existing key

The five new keys join en.ts, each with a JSDoc line such as "A vehicle table: the header of the column of each vehicle's line.", and their Danish, Swedish and Norwegian join da.ts, sv.ts and nb.ts: "Last seen" is "Sidst set", "Senast sedd" and "Sist sett". unknown's JSDoc names VehicleTable among the components that say it.

Do and don't

For a caller using the pattern, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.

RuleLevelDoDon'tChecked by
DD-1mustName the table from the heading above it, through aria-labelledby, such as a Section's "Vehicles on line 5C"Leave the table without a name: a screen reader's list of tables can't tell it from anotherreview
DD-2mustWork out each vehicle's variant from its last report, with the app's thresholds, by default vehicle-dot.live-threshold and vehicle-dot.ageing-thresholdPass live for a vehicle placed from the timetable: the table would claim more than the data knowsreview
DD-3mustKeep each vehicle's id the same from one update to the nextKey the vehicles by their place in the array: a row would keep another vehicle's state while the list reordersreview
DD-4shouldSort the vehicles before you pass them, such as by line and then by delay, and keep the order steady between updatesReorder the rows on every update: the rows jump under the rider's eye and a screen reader's placereview
DD-5mustSort vehicles by the sort you pass, and pass the sort onSortChange asks for once you have sorted by itPass a sort the rows are not in: the arrow and aria-sort would tell the eye and the ear an order that isn't therereview
DD-6shouldHide the Delay column when nothing in the data can know a delayLeave a column of "—" on every row: it reads as a delay feed that failedreview
DD-7mustShow Last seen where a rider asks how old a position is, and leave lastSeen out for a timetable-only vehiclePass the time of the timetable's call as lastSeen: the column would claim a report that never camereview
DD-8shouldHold the table in DataStatePanel while its vehicles load or when there are noneShow an empty table as the only answer to "no vehicles": a header row says nothing about whyreview
DD-9mustAnnounce a vehicle's change of state in the screen's own polite live region, oncePut the table in a live region: every delay's change would be readreview
DD-10mustMake a vehicle openable by passing a Perron Link as its destinationAdd onClick to a row: it has no focus, role or pressed look, and a keyboard can't reach itreview
DD-11shouldKeep lineName the short name riders know and destination the place: "5C" to "Husum Torv"Put the line in destination, "5C Husum Torv": the line column already reads "5C"review