A card for one disruption on Copenhagen's network: its kind, the lines it touches, a short title and a plain sentence on what is happening, then the stops it affects, when it runs and one action, set as a raised card in a list of disruptions or in a line's detail panel.
When to use
Use DisruptionCard for one disruption on the network, with its kind, its lines, what is happening and where and when, in a list of disruptions or in a line's detail panel.
| Use instead | When |
|---|---|
| SeverityLabel | Only the kind of a disruption is shown, such as in a row of a list of lines |
| NormalService | The line has no disruption, and the screen says it runs normally |
| Alert | The message is about the app or the screen, such as a data feed problem, not a disruption on the network |
- F
Signal fault at Danshøj
Variants and states
| Rest | Hover | Pressed | Focus | |
|---|---|---|---|---|
| Default | DelaysAffected lines
Signal fault at Danshøj Trains between Flintholm and Ny Ellebjerg run slower than planned. | DelaysAffected lines
Signal fault at Danshøj Trains between Flintholm and Ny Ellebjerg run slower than planned. | DelaysAffected lines
Signal fault at Danshøj Trains between Flintholm and Ny Ellebjerg run slower than planned. | DelaysAffected lines
Signal fault at Danshøj Trains between Flintholm and Ny Ellebjerg run slower than planned. |
- RestDelaysAffected lines
- F
Signal fault at Danshøj
Trains between Flintholm and Ny Ellebjerg run slower than planned. - HoverDelaysAffected lines
- F
Signal fault at Danshøj
Trains between Flintholm and Ny Ellebjerg run slower than planned. - PressedDelaysAffected lines
- F
Signal fault at Danshøj
Trains between Flintholm and Ny Ellebjerg run slower than planned. - FocusDelaysAffected lines
- F
Signal fault at Danshøj
Trains between Flintholm and Ny Ellebjerg run slower than planned.
Props
| Name | Type | Default | Description |
|---|---|---|---|
severity* | 'delay' | 'data' | 'diversion' | 'planned' | 'notice' | (required) | The kind of disruption, shown by the SeverityLabel in the card's header: delay for vehicles running late, diversion for a changed route, planned for planned works, data for a problem with the real-time data feed, notice for a service notice whose message does not say which of the others it is. |
lines | readonly DisruptionCardLine[] | — | The lines the disruption touches, in the order to show them, each as a LineBadge: line gives its colours and name the short name riders know, "M2", "5C". Leave it out, or pass an empty array, for a disruption across the network, such as a data feed problem. |
title* | ReactNode | (required) | What is happening, in a few words: "Signal fault at Kongens Nytorv". Required. Replaces the HTML title attribute. |
children | ReactNode | — | The sentence under the title: where, why and for how long, and how sure we are. "M2 and M3 trains between Kongens Nytorv and Christianshavn run slower than planned." Left out, or a value that renders nothing (null, a boolean, '' or an array of only such values), it renders no description |
stops | DisruptionCardStretch | readonly string[] | — | The stops the disruption affects, under the sentence: a list of stop names, in the order to show them, "Affects Nørreport, Kongens Nytorv and 3 more", or a stretch by its two ends, "Between Hellerup and Klampenborg". A list names three stops at most: past three it names the first two and counts the rest. Leave it out, or pass an empty array, to show no stops. |
time | DisruptionCardTime | — | When the disruption runs, under the stops: from alone reads "From 08:03", to alone "Until 22:00", and both "24 to 25 October", each value written as the caller formats it. Leave it out, or give neither, to show no time. |
action | ReactNode | — | One link or button at the foot of the card, such as a LinkButton "Show on map". Leave it out when the card is itself a link through render: links and buttons can't nest. |
affectedLinesLabel | string | The locale's | Overrides "Affected lines", the words screen readers hear just before the list of line badges |
affectsStopsLabel | string | The locale's | Overrides "Affects {stops}", the words around the stops; {stops} is the names |
stopListEndLabel | string | The locale's | Overrides "{stops} and {stop}", which joins the last stop name to those before it |
moreStopsLabel | string | The locale's | Overrides "{stops} and {count} more", the list cut short past three stops; {count} is how many it leaves out, 2 or more |
betweenStopsLabel | string | The locale's | Overrides "Between {from} and {to}", the words around a stretch's two ends |
fromTimeLabel | string | The locale's | Overrides "From {time}", the time with a start and no end |
untilTimeLabel | string | The locale's | Overrides "Until {time}", the time with an end and no start |
timeRangeLabel | string | The locale's | Overrides "{from} to {to}", the time with a start and an end |
className | string | — | Reaches the root; className is merged after pn-DisruptionCard |
style | CSSProperties | — | Reaches the root; className is merged after pn-DisruptionCard |
ref | Ref<HTMLElement> | — | Reaches the root element, whichever element render makes it |
slotProps | { header?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> | undefined; severity?: Omit<SeverityLabelProps, "severity"> | undefined; lines?: DetailedHTMLProps<HTMLAttributes<HTMLUListElement>, HTMLUListElement> | undefined; title?: Omit<TextProps, "children" | "variant"> | undefined; description?: Omit<TextProps, "children" | "variant"> | undefined; stops?: Omit<TextProps, "children" | "variant"> | undefined; time?: Omit<TextProps, "children" | "variant"> | undefined; action?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> | undefined; } | — | Props for each part: header, severity, lines, title, description, stops, time, action. |
severity (required)'delay' | 'data' | 'diversion' | 'planned' | 'notice', default (required)- The kind of disruption, shown by the SeverityLabel in the card's header:
delayfor vehicles running late,diversionfor a changed route,plannedfor planned works,datafor a problem with the real-time data feed,noticefor a service notice whose message does not say which of the others it is. linesreadonly DisruptionCardLine[], default —- The lines the disruption touches, in the order to show them, each as a LineBadge:
linegives its colours andnamethe short name riders know, "M2", "5C". Leave it out, or pass an empty array, for a disruption across the network, such as a data feed problem. title (required)ReactNode, default (required)- What is happening, in a few words: "Signal fault at Kongens Nytorv". Required. Replaces the HTML
titleattribute. childrenReactNode, default —- The sentence under the title: where, why and for how long, and how sure we are. "M2 and M3 trains between Kongens Nytorv and Christianshavn run slower than planned." Left out, or a value that renders nothing (
null, a boolean,''or an array of only such values), it renders nodescription stopsDisruptionCardStretch | readonly string[], default —- The stops the disruption affects, under the sentence: a list of stop names, in the order to show them, "Affects Nørreport, Kongens Nytorv and 3 more", or a stretch by its two ends, "Between Hellerup and Klampenborg". A list names three stops at most: past three it names the first two and counts the rest. Leave it out, or pass an empty array, to show no stops.
timeDisruptionCardTime, default —- When the disruption runs, under the stops:
fromalone reads "From 08:03",toalone "Until 22:00", and both "24 to 25 October", each value written as the caller formats it. Leave it out, or give neither, to show no time. actionReactNode, default —- One link or button at the foot of the card, such as a LinkButton "Show on map". Leave it out when the card is itself a link through
render: links and buttons can't nest. affectedLinesLabelstring, default The locale's- Overrides "Affected lines", the words screen readers hear just before the list of line badges
affectsStopsLabelstring, default The locale's- Overrides "Affects {stops}", the words around the stops;
{stops}is the names stopListEndLabelstring, default The locale's- Overrides "{stops} and {stop}", which joins the last stop name to those before it
moreStopsLabelstring, default The locale's- Overrides "{stops} and {count} more", the list cut short past three stops;
{count}is how many it leaves out, 2 or more betweenStopsLabelstring, default The locale's- Overrides "Between {from} and {to}", the words around a stretch's two ends
fromTimeLabelstring, default The locale's- Overrides "From {time}", the time with a start and no end
untilTimeLabelstring, default The locale's- Overrides "Until {time}", the time with an end and no start
timeRangeLabelstring, default The locale's- Overrides "{from} to {to}", the time with a start and an end
classNamestring, default —- Reaches the root;
classNameis merged afterpn-DisruptionCard styleCSSProperties, default —- Reaches the root;
classNameis merged afterpn-DisruptionCard refRef<HTMLElement>, default —- Reaches the root element, whichever element
rendermakes it slotProps{ header?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> | undefined; severity?: Omit<SeverityLabelProps, "severity"> | undefined; lines?: DetailedHTMLProps<HTMLAttributes<HTMLUListElement>, HTMLUListElement> | undefined; title?: Omit<TextProps, "children" | "variant"> | undefined; description?: Omit<TextProps, "children" | "variant"> | undefined; stops?: Omit<TextProps, "children" | "variant"> | undefined; time?: Omit<TextProps, "children" | "variant"> | undefined; action?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> | undefined; }, default —- Props for each part:
header,severity,lines,title,description,stops,time,action.
Use it
import { DisruptionCard } from '@mylder/perron/patterns'Ask your agent
search_docs({ query: "DisruptionCard" })Keyboard and ARIA
At rest DisruptionCard is text: it takes no focus and no key does anything. Made a link through render={<a href="…" />} (CR-8) it is one tab stop, with the browser's own keys. Its action is the caller's link or button, one tab stop with its own keys; the card around it stays unfocusable.
| Key | Does | From |
|---|---|---|
| none | At rest the card is not focusable or operable | Perron |
Tab | Moves focus to the card, when render makes it a link | The browser, on <a href> |
Enter | Follows the link, when render makes the card a link | The browser, on <a href> |
Tab | Moves focus to the action's link or button, when given | The caller's component |
| Element | Role | Accessible name from | States announced |
|---|---|---|---|
root | none: a generic <div>; listitem, article or link through render | Not named at rest; as a link, its whole text content: the kind, "Affected lines", each line's short name, the title, the sentence, the stops and the time | none |
severity | none: SeverityLabel's <span>, its icon aria-hidden | Its text, the kind's owned name: "Delays", "Diversions", "Planned works", "Data feed problems", "Service notice" | none |
lines | list | Not named: the owned string "Affected lines", overridden by affectedLinesLabel, is read just before it as VisuallyHidden text | none |
line | listitem | Its text, the line's short name from lines | none |
title | Paragraph, or a heading when slotProps.title.render makes it one | Its text, from title | none |
description | none: a generic <div> | Its text, from children | none |
stops | Paragraph | Its text: the owned words with the names from stops, "Affects Nørreport, Kongens Nytorv and 3 more" | none |
time | Paragraph | Its text: the owned words with the ends from time, "From 08:03" | none |
action | none: a generic <div>; the caller's link or button inside | The caller's link or button, named by its own text, "Show on map" | none |
The line badges are an unnamed list after a new owned string, "Affected lines", in a VisuallyHidden span inside the header, so a screen reader hears "Affected lines, list, 2 items, M2, M3" rather than two bare names after the kind: the eye reads the badges' place in the card as "these lines are affected", and the ear needs the same in words, as the contract's first checklist line asks. The words are text, not the list's aria-label, because a name replaces the content it names: a card made a link takes its name from its text, and an aria-label on the list would put "Affected lines" in that name in place of "M2" and "M3".
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 kind is SeverityLabel's word as well as its icon and colour; the badges' place in the card is said by "Affected lines", read just before the list, and each badge's colour by its short name, so a card made a link is named with the kind, "Affected lines", every line's short name and the title; with no
lines, neither the words nor the list renders and nothing is announced for them. A list of stops cut short says "and 3 more" to the eye and the ear alike, and a stretch says both its ends. - Every landmark and named region has an accessible name: the card is no landmark by default; a caller who renders it as
<article>or<section>names it from its title, througharia-labelledbyandslotProps.title.id. Thelineslist is no region and has no name: its owned string is read just before it. - Every default label can be overridden:
affectedLinesLabel,affectsStopsLabel,stopListEndLabel,moreStopsLabel,betweenStopsLabel,fromTimeLabel,untilTimeLabelandtimeRangeLabel, and SeverityLabel'sdelaysLabel,diversionsLabel,plannedWorksLabel,dataFeedProblemsLabelandserviceNoticeLabelthroughslotProps.severity. - Every shape the type accepts renders, in every variant:
titleandchildrenrender a string, an element and a list alike, the title as a<p>holds phrasing content and the sentence as a<div>holds block content too;linesrenders none, one and many, wrapping onto new rows;stopsrenders none, one, two, three and more than three names, and a stretch;timerenders a start, an end and both, each a string or an element such as<time>;actionrenders the caller's element; everyseverityhas its story. - A hidden copy kept for animation is
aria-hidden, and a collapsed region isinert: none here; the card does not animate. SeverityLabel's icon isaria-hiddenbecause its word speaks for it. - Native semantics first: a
<ul>of<li>for the lines, a<p>for the title made a nativeh2toh6throughrender, a<div>for the sentence, and a native<a href>when the caller makes the card a link; no ARIA attribute, "Affected lines" beingVisuallyHiddentext. - Focus moves in reading order and returns on close: at rest the card takes no focus, and its
action, last in the card, is the one tab stop inside it; as a link it is one tab stop in the document's order, and it opens or closes nothing. - Every target is at least
size.target-minsquare: at rest none but theaction, whose size is the caller's component's; as a link the whole card is the target, far larger thansize.target-min. - Loops stop when the system asks for less motion: none here; the card has no motion.
Owned strings
The card shows SeverityLabel's name for its kind, which SeverityLabel owns and reads itself, and adds strings of its own: the words said before the list of lines, and the words around the stops and the time. Every other word comes from the caller, through title, children, lines, the names in stops, the ends of time and action.
The words around the stops and the time are owned strings with {name} placeholders, filled by interpolate (the contract's Words), so each locale puts the names and the times where its grammar needs them. The names a list shows are joined by ", ", as all four languages join a list, and the last by stopListEnd; a list past three names the first two and moreStops counts the rest, always 2 or more, so no locale needs a singular "1 more".
| Key | English | Override | Seen or heard where |
|---|---|---|---|
affectedLines | "Affected lines" | affectedLinesLabel | header, VisuallyHidden text just before the lines list, "Affected lines, list, 2 items"; never seen; new |
delays | "Delays" | slotProps.severity.delaysLabel | severity when severity='delay', seen and read; SeverityLabel's, the key en.ts has |
diversions | "Diversions" | slotProps.severity.diversionsLabel | severity when severity='diversion'; SeverityLabel's, the key en.ts has |
plannedWorks | "Planned works" | slotProps.severity.plannedWorksLabel | severity when severity='planned'; SeverityLabel's, the key en.ts has |
dataFeedProblems | "Data feed problems" | slotProps.severity.dataFeedProblemsLabel | severity when severity='data'; SeverityLabel's, the key en.ts has |
serviceNotice | "Service notice" | slotProps.severity.serviceNoticeLabel | severity when severity='notice'; SeverityLabel's, new with it |
affectsStops | "Affects {stops}" | affectsStopsLabel | stops when stops is a list, around the names; new |
stopListEnd | "{stops} and {stop}" | stopListEndLabel | stops, joining the last of two or three names to those before it; new |
moreStops | "{stops} and {count} more" | moreStopsLabel | stops, a list of four or more cut to its first two names and the count of the rest; new |
betweenStops | "Between {from} and {to}" | betweenStopsLabel | stops when stops is a stretch; new |
fromTime | "From {time}" | fromTimeLabel | time when only time.from is given; new |
untilTime | "Until {time}" | untilTimeLabel | time when only time.to is given; new |
timeRange | "{from} to {to}" | timeRangeLabel | time when both ends are given; new |
affectedLines joins packages/perron/src/locale/en.ts with the JSDoc "A disruption: said just before the list of lines it affects, heard by screen readers only.", and its Danish, Swedish and Norwegian in da.ts, sv.ts and nb.ts: "Berørte linjer", "Berörda linjer" and "Berørte linjer".
The stops' and the time's strings join them with a JSDoc each that names its placeholders, in Danish, Swedish and Norwegian:
| Key | Danish | Swedish | Norwegian Bokmål |
|---|---|---|---|
affectsStops | "Berører {stops}" | "Berör {stops}" | "Berører {stops}" |
stopListEnd | "{stops} og {stop}" | "{stops} och {stop}" | "{stops} og {stop}" |
moreStops | "{stops} og {count} andre" | "{stops} och {count} till" | "{stops} og {count} andre" |
betweenStops | "Mellem {from} og {to}" | "Mellan {from} och {to}" | "Mellom {from} og {to}" |
fromTime | "Fra {time}" | "Från {time}" | "Fra {time}" |
untilTime | "Indtil {time}" | "Fram till {time}" | "Fram til {time}" |
timeRange | "{from} til {to}" | "{from} till {to}" | "{from} til {to}" |
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.
| Rule | Level | Do | Don't | Checked by |
|---|---|---|---|---|
| DD-1 | must | Name the lines in words in the title or the sentence, "M2 and M3 trains…", as well as in the badges. | Let the badges be the only mention of a line: five official badge pairs miss 4.5:1, and LineBadge asks for the name in words beside them. | review |
| DD-2 | should | Say what is happening, then where, how long and how sure we are, in plain sentences: "Signal fault at Kongens Nytorv" over its sentence. | Repeat the kind in the title ("Delays on M2"): the SeverityLabel already says it, in the content-design word every screen shares. | review |
| DD-3 | must | Set a list of disruptions as a <ul> of cards, each rendered as <li>, or make each card a link with render={<a href="…" />} to open the line. | Make the card a <button>, or put a link or button inside a card that is itself a link: the card's blocks are invalid in a button, and links can't nest. | review |
| DD-4 | must | Make the title a heading with slotProps.title.render at the level the panel around it needs. | Pass a heading element as title: the title part is a <p> until render swaps it, and a heading inside a <p> is invalid. | review |
| DD-5 | should | Leave lines out for a disruption across the whole network, such as a data feed problem. | Pass every line on the network, or a placeholder badge, to fill the header. | review |
| DD-6 | should | Give the stops and the time as data, stops and time, from the message's own fields. | Write "Affects …" or "From …" into the sentence yourself: the words then differ from every other card's, and from the other languages'. | review |
| DD-7 | must | Give severity='notice' to a message whose fields don't say which kind it is. | Show an unread kind as planned works, or guess one from the message's words. | review |
| DD-8 | must | Give one action, a LinkButton or Button, such as "Show on map", when the card is not itself a link. | Give an action to a card made a link, or put several actions in it: links can't nest, and one action keeps the card one decision. | review |
| DD-9 | should | Put DisruptionCards inside a panel, which supplies the glass or opaque surface they sit on. | Tint or stripe the card in the kind's colour with className: the colour belongs to the SeverityLabel and never carries meaning alone. | review |