Skip to content
txbrendan/ui

Bracket Match

beta

One knockout match on its own, 124px tall and as wide as its column, with a peek that adds what the tile has no room for: the round, the result in words, and facts like the venue or form.

Preview

Effect

hover or focus a match: its peek adds what the tile has no room for

before kick-off, and live: a badge replaces FT

other badges: AET and Forfeit

width={280}, and never under 240

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/bracket-match

Use it for

  • A single knockout fixture in a feed, a list, or a side panel.
  • Pass the same BracketMatchData a KnockoutBracket takes. It shows the date and time before kick-off, and FT once it's finished.
  • Pass round, like "Cup semi-final", so its peek says which round it is.
  • Pass onClick and selected to make it a match you choose, like a card that opens the match panel.
  • Use badge for "Live 9'", "AET", or "Forfeit". It replaces FT. A level score with kicks reads FT (P).

Avoid it for

  • A whole draw. Use KnockoutBracket, which lays the tiles out round by round.
  • A fixture outside a knockout, or with a flat team shape. Use MatchCard, which has no peek.
  • Putting the peek's facts only in the peek. The tile already shows them, so the peek just repeats them.

Props

  • match

    The match: { id, date?, time?, status?, home, away, winner?, badge? }. Each side is { team, score, penalties?, label? }.

    BracketMatchData

  • round

    The round's name. It titles the peek, like "Cup semi-final". Without it, the teams do.

    string

  • facts

    What the tile cannot show, for the peek, as [label, value] pairs, like ["Venue", "Riverside Park"] or each side's form. The peek never repeats the tile's teams, score, or date.

    PeekFact[]

  • selected

    Draws it as the chosen match. Only a match with onClick can be chosen.

    boolean · default false

  • onClick

    Makes the match a button that says whether it is pressed.

    () => void

  • width

    A fixed width in px, never under 240. By default it fills its column.

    number

  • effect

    How the peek opens and closes. rise fades in and moves out from the match. grow fades in and grows from the match, and closes the same way. Left out, it follows an effect-<name> class on <body>, where popups open, and rises with none.

    "rise" | "grow" · default "rise"

  • className

    Extra classes for the root. Later classes win.

    string

Accessibility

  • The tile names both sides, the scores, and the winner in text. The winner's caret and weight only show it.
  • With onClick, the tile is a button with aria-pressed, and the peek follows its hover and focus. Enter stays the button's own.
  • Without onClick, the tile is the peek's trigger: a button that says it opens a dialog.
  • The peek is a dialog named by its title: the round, or the two teams when there is no round.
  • A chosen match has a 2px outline that shows in forced colors.

Examples

Finished, with the round and the venue in its peek

<BracketMatch
  round="Cup semi-final"
  facts={[
    ["Venue", "Riverside Park"],
    ["Rovers' form", "W W D W"],
  ]}
  match={{
    id: "sf1",
    date: "Sat 17 Oct",
    time: "15:20",
    status: "finished",
    home: { team: { name: "Rovers" }, score: 24 },
    away: { team: { name: "Lions" }, score: 17 },
  }}
/>

Chosen, with onClick

<BracketMatch match={match} round="Final" selected={selected === match.id} onClick={() => setSelected(match.id)} />

Live, at a fixed width

<BracketMatch
  width={280}
  match={{
    id: "f",
    time: "15:00 · Pitch 1",
    badge: "Live 9'",
    home: { team: { name: "Hawks" }, score: 3 },
    away: { team: { name: "Wolves" }, score: 0 },
  }}
/>

Peek grows from the match

<BracketMatch
  effect="grow"
  round="Cup final"
  match={{
    id: "f1",
    date: "Sat 24 Oct",
    time: "15:00",
    home: { team: { name: "Rovers" }, score: null },
    away: { team: { name: "Hawks" }, score: null },
  }}
/>