Skip to content
txbrendan/ui

Knockout Wheel

beta

The draw as a circle: the champion at the hub with a trophy and a warm glow, and each round a ring further out. Hover or focus a team to light its run.

Preview

eight sides, played out: the champion's run is lit. Hover, focus, or press a team to light its own

Arrow keys move between teams: up toward the champion, down to the round before, and left and right around a ring. Enter keeps a team's run lit.

Champion: Hawks.

sixteen sides

Arrow keys move between teams: up toward the champion, down to the round before, and left and right around a ring. Enter keeps a team's run lit.

Champion: Rovers.

halfway: the quarter-finals are played, and the hub waits for a champion

Arrow keys move between teams: up toward the champion, down to the round before, and left and right around a ring. Enter keeps a team's run lit.

Champion to be decided.

initialRound={1} starts the outer ring at the quarter-finals, at maxWidth={300}

Arrow keys move between teams: up toward the champion, down to the round before, and left and right around a ring. Enter keeps a team's run lit.

Champion: Rovers.

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/knockout-wheel

Use it for

  • A season's draw at a glance, like on a cup's home page or once the final is played.
  • Pass the same rounds a KnockoutBracket takes, widest first.
  • Pass initialRound to start the outer ring at a later round, so a big draw stays readable.
  • Give each team its kit colors. Each node is the team's TeamFlag, cut to a circle.
  • Pair it with a KnockoutBracket or a list when people need the dates and scores at a glance. The wheel says them only to screen readers and on focus.

Avoid it for

  • Reading the dates, times, or pitches. Use KnockoutBracket, which shows them on each match.
  • Draws of more than 32 sides. The outer ring gets too tight to point at. Start at a later round with initialRound.
  • A league. Nothing feeds anything, so there is no wheel to draw.

Props

  • rounds

    Widest round first. rounds[r].matches[k] is fed by matches 2k and 2k+1 of the round before.

    BracketRound[]

  • initialRound

    The index of the outermost round to draw.

    number · default 0

  • maxWidth

    The widest it draws, in px. It fills a narrower container, down to 280px, or maxWidth if less. Under 280px the hub shows the trophy alone.

    number · default 544

  • aria-label

    Names the wheel.

    string · default "Knockout wheel"

  • className

    Extra classes. Later classes win.

    string

Accessibility

  • The wheel is a group, with each round's ring a list named by the round, outermost first, then the champion.
  • Each team is a button that names the team, the round, and the result, like "Rovers, Semi-finals: beat Lions 24–17". So each match names both teams and who won.
  • One Tab stop. Up goes toward the champion, down to the round before, left and right around the ring, and Home and End to a ring's ends. The group's description says so.
  • Enter, Space, or a tap keeps a team's run lit, and the button says it is pressed. Press it again, or Esc, to let go.
  • The lit run is a thicker line and a ring around each of its nodes, so it shows in forced colors too, not only by its color.
  • The rings and the lines are drawn in a hidden SVG. The hub's readout is hidden too, since each button says the same.
  • Nodes spring in only from the motion tokens. Under reduced motion they fade, and with motion off they just show.

Examples

A whole draw, played out

// rounds is the same BracketRound[] a KnockoutBracket takes.
<KnockoutWheel rounds={rounds} />

From the quarter-finals, in a narrow column

<KnockoutWheel rounds={rounds} initialRound={1} maxWidth={320} aria-label="Cup draw from the quarter-finals" />