Introduction
Confirmation dialogs, toasts, and other "ask the user something, then continue" UI are naturally imperative: you want to trigger them from inside an event handler and get an answer back. window.confirm is the simplest way to do that. But window.confirm can't be styled, animated, or made accessible the way a real component can.
Building that imperative feel with your own components usually means state for "is it open", state for "what was it called with", and a way to resolve a promise from inside the component when it's done. toi packages that pattern into a single function call.
How it works
toi has three moving parts:
toi— mounts a component and returns a promise. Call it from anywhere, including inside an event handler.ToiHost— renders every component currently mounted bytoi. It needs to exist somewhere in your tree, once.ToiProps— therefandresolveprops toi injects into the component you pass it. Callingresolveis what settles the promisetoireturned.
Try it below — click "Delete", then "OK" or "Cancel", and watch the result appear:
Confirm is a completely ordinary component — no context, no imperative ref API to learn. The only requirement is that it accepts resolve (and optionally forwards ref to its animatable root element).
Headless by design
toi doesn't render any markup, styles, or animations of its own. ToiHost only renders whatever components are currently mounted; everything else — the <dialog>, the buttons, the transitions — is your component. This keeps toi usable with any design system or styling approach.
Exit animations
Calling resolve doesn't unmount the component immediately. toi waits for any running animations on the element attached to ref to finish first, so a fade-out or slide-out transition can play before the component is removed and the promise resolves. See Resolving for details.