Thirteen UI components, extracted from software that ran an actual wiffle ball tournament. On why the best component libraries are byproducts, not products.
4 min read· Jul 5, 2026·Updated Jul 20, 2026388 views
This site now has a component registry - thirteen components you can install into any React project with one command:
A tournament bracket. A pool standings table. A balls-strikes-outs display. A base-runner diamond. A play-by-play feed. The app shell an umpire uses to keep score on a phone. If that list sounds oddly specific, it's because none of it was designed as a component library. All of it was extracted from software that ran an actual event - the Schott Classic, the wiffle ball tournament I've writtenaboutbefore.
Most component libraries start as products: someone imagines what developers might need, builds abstractions for hypothetical use cases, and ships a card component with nineteen props. The abstractions are smooth because they've never touched the ground.
Everything in this registry took the opposite path. The base-runner diamond exists because umpires needed to track ghost runners between pitches without taking their eyes off the game. The count display shows one dot fewer than the maximum in each row because that's how a park scoreboard works - a full row means the next one ends it. The pool standings component breaks ties by run differential, then runs scored, because that's the actual tiebreak order printed on the actual rules sheet, and it counts a tie as half a win because pool play allows ties and somebody has to decide what that means for seeding.
None of those decisions came from a design review. They came from 298 teams' worth of edge cases.
The originals were tangled into the app the way real code always is - wired to a state store, an accessibility context, an offline sync queue, a brand color. Extraction meant rewriting, not copying: every component in the registry takes plain props, styles itself with theme tokens, and carries zero dependencies. The offline-sync machinery stayed at the ballpark, where it belongs.
The interesting part is what survives that process. The sync queue didn't. The Zustand store didn't. What survived was the domain knowledge - the tiebreak order, the scoreboard convention, the shape of a bracket connector, the fact that a champion card should collapse to a single row with a trophy. That's the stuff you can't get from imagining use cases, and it's the only part worth publishing.
The tournament bracket is the piece I'm proudest of, mostly because of how it improved: by being reviewed against a real 16-team field, it grew a helper that derives later rounds from winners (because I personally advanced the wrong team by hand within an hour of building it), a hover trace that lights up a team's whole run, live-game states, schedule strips with field assignments, and a mobile mode that admits the truth: the bracket shape is what doesn't fit on a phone, not the information.
There's even a pick'em variant now - tap a team to advance them, and changing an early pick quietly voids everything downstream, because picks are stored by team name and a team that no longer reaches a game simply can't win it. That rule fell out of the data model instead of being enforced by code, which is the kind of small win that keeps me doing this.
Partly because the components might genuinely be useful - youth leagues, rec tournaments, church softball, anyone with a bracket and a whiteboard problem. Partly because publishing forces honesty: docs with props tables generated from the real TypeScript types can't drift, and a demo you can actually play is a claim you can't fake.
But mostly because I think this is what personal software should do: compound. The tournament needed an app. The app grew components. The components became a registry. The registry became the most alive part of this site. Nothing in that chain was built to be impressive - it was built to run a wiffle ball tournament on thirteen fields without losing anyone's score.
The tournament is this weekend, July 11–12. The whiteboard is retired.