Confirm
A question asked in a dialog, awaited like any other answer. useConfirm() returns a function that resolves to what the reader pressed, so the code that asks is the code that acts.
import { ConfirmProvider, useConfirm } from 'neba';
<ConfirmProvider>
<App />
</ConfirmProvider>;
// anywhere under it
const confirm = useConfirm();
if (await confirm({ title: 'Delete the project?', color: 'danger' })) {
remove();
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Everything that may call useConfirm |
| defaults | Pick<ConfirmOptions, 'color' | 'size' | 'width' | 'locale'> | — | Shared by every question raised under it. Each call overrides them |
The options
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | The question: the dialog's heading, and what a screen reader announces |
| description | ReactNode | — | What answering costs: the consequence, not a restatement of the title |
| confirmLabel | ReactNode | — | The confirming button's label. Defaults to the locale's own word |
| cancelLabel | ReactNode | — | The cancelling button's label |
| color | NebaColor | 'primary' | The colour family. danger for anything that destroys something, which is most of them |
| size | NebaSize | 'sm' | The sheet's step on the size ladder |
| alert | boolean | false | Drops the cancelling button, leaving one way out: for telling rather than asking. It still resolves, always true, so the same await works either way |
| dismissible | boolean | true | Whether Escape and the backdrop answer no. Off makes a question the reader has to answer with a button: use it rarely, since a modal with no way out is the thing people report |
| width | number | string | — | How wide the sheet is, passed to Dialog |
| locale | string | — | BCP 47 tag deciding the two default labels. Defaults to the provider's |
confirm('Delete the project?') is shorthand for confirm({ title: 'Delete the project?' }).
The promise it returns
"Are you sure?" is the most common dialog there is, and writing it by hand means a piece of state per question, a useState for what was being deleted when the dialog opened, and a callback that has to carry it. onConfirm splits one decision across two functions; awaiting it keeps the decision where it was made.
It never rejects. A question answered no is an answer, not a failure, and a promise that throws for it turns every call site into a try.
Cancelling, Escape and a click on the backdrop all resolve false: they are the cancelling button by another route, so they answer the same way rather than leaving a promise pending forever.
Examples
color and destructive questions
Most confirms are about destroying something. color: 'danger' turns the confirming button and the sheet's accents over together.
await confirm({
title: 'Delete 12 files?',
description: 'They go to the trash and are removed after 30 days.',
confirmLabel: 'Move to trash',
color: 'danger'
});alert
Drops the cancelling button, leaving one way out: for telling rather than asking. It still resolves, always true, so the same await works either way.
await confirm({ title: 'Your export is ready.', alert: true });dismissible
false makes a question the reader has to answer with a button: Escape and the backdrop stop working. Use it where an accidental dismissal is the expensive answer, and almost nowhere else: a modal with no way out is the thing people report.
defaults
ConfirmProvider takes the settings every question under it shares, and each call overrides them.
<ConfirmProvider defaults={{ size: 'md', locale: 'ko' }}>Two at once
Questions queue. Raising a second one while the first is up puts it behind the first; nothing is answered on the reader's behalf.
That matters more than it looks. Resolving the older one false to make room would report an answer nobody gave, and at the call site, false reads as "they said no", so the code would take the cancelled branch for a question that was never shown.
Accessibility
- It renders a Dialog, so everything that has is here: the focus trap, the scroll lock, the inert page behind, and focus returning to whatever raised the question.
- The confirming button takes the focus when the sheet opens, so
Enteranswers yes andEscapeanswers no. titlebecomes the dialog's accessible name anddescriptionitsaria-describedby. A question with neither is a dialog a screen reader announces as nothing: always pass at least atitle.