Suspension Row
betaA player in the sin bin, sent off, or off for a temporary replacement, as a row in a card, counting down on the same clock value as MatchClock.
Preview
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the component.
npx shadcn@latest add @txbrendan/suspension-row
Use it for
- One row per player off, in a Card, beside the MatchClock on a scorer's or timekeeper's screen.
- Pass the match's clock value and format, the same ones the MatchClock gets. A card counts down on playing time: it stops when the clock stops, carries over half-time, and counts the time played after time is up.
- Record at as matchPlayed(clock) and time as matchClockDisplay(clock, format) when the card is shown.
- yellow uses the format's sin bin: 10 minutes in 15s, 2 in sevens and 10s, 5 to 7 in the age grades. Pass minutes when a competition sets its own.
- red reads Sent off for the rest of the match. red20 is elite rugby's 20-minute red card: after 20 minutes of playing time a replacement may come on.
- blood and hia are temporary replacements on the wall clock, from since: 15 minutes for blood, 12 for an HIA, or minutes={17} for both.
- Pass onReturn. A person button beside Can return, or Back within, brings them back on.
- Remove the row once the player is back on. A team's players on the field is its format's players less its rows.
Avoid it for
- A row for a suspected concussion in community or age-grade rugby. Recognise and Remove takes them off for good, which is a permanent replacement, not a row.
- Your own countdown timer. The row works out the time from the clock value, so every device agrees.
- Telling the cards apart by the mark's color alone. The words under the name say which card it is.
Props
- player
The player, with their photo and shirt number.
{ person: string; photo?: string; number?: number }
- team
The player's team, in the line under the name.
string
- reason
Why the player is off: a card, or a temporary replacement.
"yellow" | "red" | "red20" | "blood" | "hia"
- clock
The match's MatchClock value.
MatchClockValue | null
- format
The same format as the MatchClock. It sets the sin bin.
MatchFormatId | MatchFormat · default "15s"
- at
matchPlayed(clock) when the card was shown. Cards only.
number · default 0
- time
What the clock read when it was shown, like "8:25".
string
- minutes
Minutes off, when a competition sets its own sin bin, or 17 for blood and an HIA together.
number
- since
blood and hia: when the player left, in ms since the epoch.
number
- onReturn
Brings them back on, or a replacement on. A person button shows once it can.
() => void
- index
Place in a list. Rows fade up one after another as they first show.
number
- divider
A hairline above the row, inset from the card's edges.
boolean · default false
- className
Extra classes. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| player | { person: string; photo?: string; number?: number } | - | The player, with their photo and shirt number. |
| team | string | - | The player's team, in the line under the name. |
| reason | "yellow" | "red" | "red20" | "blood" | "hia" | - | Why the player is off: a card, or a temporary replacement. |
| clock | MatchClockValue | null | - | The match's MatchClock value. |
| format | MatchFormatId | MatchFormat | "15s" | The same format as the MatchClock. It sets the sin bin. |
| at | number | 0 | matchPlayed(clock) when the card was shown. Cards only. |
| time | string | - | What the clock read when it was shown, like "8:25". |
| minutes | number | - | Minutes off, when a competition sets its own sin bin, or 17 for blood and an HIA together. |
| since | number | - | blood and hia: when the player left, in ms since the epoch. |
| onReturn | () => void | - | Brings them back on, or a replacement on. A person button shows once it can. |
| index | number | - | Place in a list. Rows fade up one after another as they first show. |
| divider | boolean | false | A hairline above the row, inset from the card's edges. |
| className | string | - | Extra classes. Later classes win. |
Accessibility
- The row reads as one line: the number, the name, and the status, like "#3 Owen Price · Back in 4:12".
- Which card it is, or why they are off, is in words in the line under the name. The card mark before those words is hidden from screen readers.
- The return button is a native button named for what it does, like "Bring Owen Price back on", with the same words in its tooltip.
- A polite live region says once when the time is served, like "Owen Price: Can return". The countdown itself is not read out each second.
- Can return, Sent off, and Replacement stays are words in the tone's soft text, which the theme holds to 4.5:1. A card mark keeps a border in forced-colors mode.
Examples
A yellow card counting down beside the clock
// clock is the match's MatchClock value. at and time were recorded when the card was shown.
<Card className="gap-0 px-0">
<SuspensionRow
player={{ person: "Owen Price", number: 3 }}
team="Rovers"
reason="yellow"
at={card.at}
time={card.time}
clock={clock}
format="15s"
onReturn={() => bringBack("owen")}
/>
</Card>A blood replacement on the wall clock
<SuspensionRow
player={{ person: "Will Hardy", number: 1 }}
team="Saints"
reason="blood"
since={leftAt}
clock={clock}
onReturn={() => bringBack("will")}
divider
/>