Club Switcher
betaThe club you're in, with its kit flag, name, and your roles, that opens a menu of every club you belong to.
Preview
button
no kit
inline
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/club-switcher
Use it for
- The top of an app's rail, for people in more than one club: a coach in one, a parent in another.
- Give each club an id, its name, its kit colors for the TeamFlag, and your roles there, like "Coach · Admin".
- Pressing it opens every club under it, with a check on the current one. Pass onJoin to end the menu with Join another club.
- Pass value and onChange to control it, or defaultValue to start in a club. Without either it starts in the first.
- Use inline to draw the open menu in a card or a doc, with no button.
Avoid it for
- Drawing a club's crest. Its kit colors stand in for it. Brand logos are never drawn.
- Someone in one club only. Show the club's name and flag as plain text.
- Switching teams inside one club. Use a select or tabs.
Props
- clubs
Required. Every club you belong to. colors are CSS colors from your data, set only on the flag's own style.
{ id: string; name: string; colors?: [string, string]; pattern?: "hoops" | "halves" | "thirds" | "split"; role?: string }[]
- value
The club you're in. Controlled, with onChange.
string
- defaultValue
The club you start in. Default the first.
string
- onChange
Runs with the club's id when you switch.
(id: string) => void
- onJoin
Offers "Join another club" at the end of the menu.
() => void
- inline
Draws the open menu in the page's flow, with no button. Nothing closes it.
boolean · default false
- className
Extra classes on the button, or on the menu with inline. Later classes win.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| clubs | { id: string; name: string; colors?: [string, string]; pattern?: "hoops" | "halves" | "thirds" | "split"; role?: string }[] | - | Required. Every club you belong to. colors are CSS colors from your data, set only on the flag's own style. |
| value | string | - | The club you're in. Controlled, with onChange. |
| defaultValue | string | - | The club you start in. Default the first. |
| onChange | (id: string) => void | - | Runs with the club's id when you switch. |
| onJoin | () => void | - | Offers "Join another club" at the end of the menu. |
| inline | boolean | false | Draws the open menu in the page's flow, with no button. Nothing closes it. |
| className | string | - | Extra classes on the button, or on the menu with inline. Later classes win. |
Accessibility
- The button is named by the club and your roles, and says it opens a menu.
- The menu opens with focus on your club. The arrow keys move without wrapping, Home and End go to the ends, letters jump to a club, Enter switches, and Esc closes it and puts focus back.
- Each club is a menuitemradio, checked for the one you're in. The check is drawn, not only colored.
- Inline, the menu is one Tab stop that lands on your club, with the same keys. Nothing closes it.
- The kit flag is hidden from screen readers, since the name beside it says the club.
Examples
At the top of a rail
<ClubSwitcher
clubs={[
{ id: "rovers", name: "Rovers", colors: ["#1e3a8a", "#ffffff"], pattern: "hoops", role: "Coach · Admin" },
{ id: "saints", name: "Saints", colors: ["#b91c1c", "#111827"], role: "Parent of Ava" },
]}
defaultValue="rovers"
onChange={(id) => console.log("switch to", id)}
onJoin={() => console.log("join")}
/>