shadcn.io is not affiliated with official shadcn/ui
Coin Flip Dialog Block for React Aria
A group chat's flip dialog shown open over a drawn thread: two named options, a coin that spins in CSS 3D, the winner, a tally, the last ten results as marks and a Best of 3 mode that stops at two wins.
Installation
Overview
Settle a tie in the thread instead of arguing about it, with this coin flip dialog for React with React Aria. It opens from a group chat in Coinwren, an app for deciding together: the header holds an outline Button named /flip, and the thread it opens over is drawn under a soft scrim inside a hairline frame with a plus mark on each corner, with bubbles on both sides and a composer. The Dialog takes two options in Inputs, Cinema and Bowling, shows a coin whose faces carry their first letters and that spins in CSS 3D, then names the winner, keeps a tally and a row of the last ten results, and offers a Best of 3 Toggle that stops at two wins and a Reset tally button. Flip is the only Shine Button and leaves the dialog open so you can flip again. The flips are seeded: a small generator draws the sequence, so a reload repeats the same flips in the same order, and nothing is decided for real. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog, with the same state. For group chats, game nights, classroom tools and any app that needs a fair-looking tiebreaker.
FAQ
Why is the dialog already open, and what do I keep?
So you can see it without a click. On load the dialog is a copy drawn in place over the thread: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The /flip button in the header opens the real Dialog, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same FlipFields component and share the block's state, so what you set in the copy is there in the real one. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.
Is the coin really random?
No, and the block says so. drawsOf runs a small seeded generator (Park-Miller, seed 90,210) once at module scope and keeps 60 draws, each one a first or second option. Flip reads the next draw, so a reload repeats the same sequence and a test can rely on it. It is fine for a demo and a tiebreaker nobody audits. If the result has to be fair for something that matters, take the side from your server or from crypto.getRandomValues in flip, and keep the rest as it is.
What happens with reduced motion?
The coin is two faces on a rotating wrapper, and its turn is a 700 ms CSS transition. With reduced motion on, the transition is off, so the coin shows the winning face at once. The result, the tally and the marks wait for the turn to finish with a delay and a fade in the normal case, and appear at once otherwise. The coin is hidden from assistive technology; the winner is in text and in a polite status message.
How does Best of 3 work?
With the chip on, the first option to reach two wins takes it: the note under the marks says who, and Flip answers with a message instead of flipping again. Reset tally clears the counts and the marks and the seeded sequence carries on from where it was. Turning the chip off keeps the counts. The two names must be filled in and different, and a refused flip puts the problem in one reserved line and focuses the first option.
What stays local, and how do I wire it for real?
Everything. Nothing is posted to the thread: the drawn thread behind only shows a small bubble saying who won the last flip. In your app post the result from flip as a message, store the tally per conversation, and keep the names as the people's actual options. Closing the dialog keeps the tally and the names, so reopening continues.
Is it accessible from the keyboard?
Yes. The real dialog is named by its title and described by its sentence, focus is trapped while it is open, and Escape and the close button return focus to the header button. The button and the dialog sit inside DialogTrigger, controlled with isOpen and onOpenChange, and the title is a level 2 heading. The copy shown on load moves no focus; Escape inside it and its close button close it and put focus back on the header button. Enter in either option flips, because Flip is the form's submit button. The winner is spoken by the dialog's polite status message.
How does it look on a phone?
The copy shown on load docks to the foot of the frame as a bottom sheet with Close and Flip pinned below a scrolling body. The two options sit side by side, the coin and the result below them. The real dialog keeps the Dialog's own phone layout: a sheet stuck to the bottom of the screen.