# Knockout Bracket

> A knockout draw round by round, up to three rounds side by side, paged one round at a time with chevrons. Cards, elbows, and titles glide together.

- Name: `knockout-bracket`
- Status: beta
- Categories: sport, display
- File: the install writes `knockout-bracket.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `lucide-react`. The install adds them.
- Other registry items: `@arbokui/button`, `@arbokui/team-flag`. The install adds them.
- Gallery: https://arbokui.com/components/knockout-bracket
- Registry item: https://arbokui.com/r/knockout-bracket.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/knockout-bracket
   ```

## Use it for

- A cup or a play-off draw, from the widest round to the final.
- Pass rounds widest first. rounds\[r\].matches\[k\] is fed by matches 2k and 2k+1 of the round before.
- Give each side its team, or team: null with a label like "QF 3 winner" until it is known.
- Pass thirdPlace to draw a third-place play-off under the final.
- Pass onSelect, or a selectedId, to make each match a button you can choose, like for a match panel beside it.
- It shows three rounds from 808px wide, two from 528px, and one below that. Each round is 240 to 300px wide, and the bracket never draws narrower than 240px.

## Avoid it for

- A league table or a round robin. Use a table, since nothing feeds anything.
- A single fixture in a feed. Use BracketMatch, which adds a peek, or MatchCard.
- The draw as a picture. Use KnockoutWheel for the circle.

## Props

- `rounds`: `BracketRound[]`. Widest round first. Each is { name, matches }, and each match is BracketMatchData.
- `initialRound`: `number`. Default `0`. The index of the leftmost round on mount. It pages on to the final, no further.
- `thirdPlace`: `BracketMatchData`. A third-place play-off, drawn under the final.
- `thirdPlaceLabel`: `string`. Default `"Third-place play-off"`. The title over the third-place match.
- `selectedId`: `string`. Controlled: the chosen match's id.
- `defaultSelectedId`: `string`. The match chosen first, when you don't control it. It also makes each match a button.
- `onSelect`: `(match: BracketMatchData) => void`. Makes each match a button, and hears the one chosen.
- `className`: `string`. Extra classes for the root. Later classes win.

## Accessibility

- The bracket is a group named "Knockout bracket". Pass aria-label to name it yourself.
- Each round is a list, named by its title. The third-place match comes after the final.
- Each match names both sides, the scores, and the winner in text. The winner's caret and weight only show it.
- Every round stays in the reading order, even when it is paged out of view. Focus that lands in a hidden round pages to it.
- The chevrons say "Previous round" and "Next round", and stay focusable at the ends. A page turn is said politely.
- A chosen match is a button with aria-pressed, and a 2px outline that shows in forced colors.
- The elbows are hidden from screen readers.

## Examples

### Semi-finals into a final, with sides not known yet

```tsx
// rounds come from your data. Each side is { team: { name, colors?, pattern? } | null, score, penalties?, label? }.
<KnockoutBracket
  rounds={[
    {
      name: "Semi-finals",
      matches: [
        { id: "sf1", status: "finished", home: { team: { name: "Rovers" }, score: 24 }, away: { team: { name: "Lions" }, score: 17 } },
        { id: "sf2", date: "Sat 17 Oct", time: "15:20", home: { team: { name: "Hawks" }, score: null }, away: { team: { name: "Wolves" }, score: null } },
      ],
    },
    {
      name: "Final",
      matches: [{ id: "f", date: "Sat 24 Oct", time: "15:00", home: { team: null, label: "SF 1 winner", score: null }, away: { team: null, label: "SF 2 winner", score: null } }],
    },
  ]}
/>
```

### Choose a match

```tsx
const [selected, setSelected] = useState("sf1");

<KnockoutBracket rounds={rounds} selectedId={selected} onSelect={(match) => setSelected(match.id)} />
```

### With a third-place play-off

```tsx
<KnockoutBracket
  rounds={rounds}
  thirdPlace={{
    id: "tp",
    date: "Sat 24 Oct",
    time: "13:00",
    home: { team: null, label: "SF 1 loser", score: null },
    away: { team: null, label: "SF 2 loser", score: null },
  }}
/>
```
