Modal
● Stable · v0.1A centered dialog that interrupts to ask for ONE decision — confirm a destructive action, acknowledge an error, choose between two paths. It floats over a dimmed backdrop and takes the whole screen's attention, so reach for it only when the user genuinely must respond before continuing.
In context
Purpose
A Modal stops the flow for a decision that can't be deferred. That interruption is exactly why it's powerful and why it's easy to misuse: every modal is a wall the user must clear. Use it for "are you sure?" on something destructive, for an error they must see, or for a fork with two real choices — not for information that could sit inline.
Anatomy
- Backdrop — a dimmed scrim; tapping it dismisses when
dismissable. - Icon disc (optional) — a tone-tinted circle that says at a glance what kind of moment this is (a red disc for destructive).
- Title — the decision in plain words, ideally a question.
- Body (optional) — the consequence, concretely ("removes ฿1,200").
- Actions — one primary, plus an optional secondary. Two actions sit side by
side; a lone action goes full width. An × dismisses when
dismissable.
Variants
tone="default"— teal primary action (a neutral choice/confirm).tone="destructive"— red primary action + red icon disc, for delete/remove.dismissable(default true) — shows the × and lets a backdrop tap close. Set it false when the user MUST pick an action (no silent escape).
Code
import { Modal } from '@daytwo/components'const [open, setOpen] = useState(false)<Modalvisible={open}onClose={() => setOpen(false)}tone="destructive"icon={<Warning size={26} color={colors.feedback.errorFg} />}title="Delete this sale?"description="This removes ฿1,200 from today's total. You can't undo it."primaryLabel="Delete"secondaryLabel="Keep it"onPrimary={remove}/>
When to use
- A destructive confirm (delete, remove, discard).
- An error the user must acknowledge before doing anything else.
- A two-path fork where both options are real and immediate.
When NOT to use
- A focused task (a form, a picker, capture) — rise a BottomSheet.
- A passing "it saved" — use a toast or ConfirmationState.
- Information the user could read inline — use an Alert.
Good vs bad
- Good: "Delete this sale? — removes ฿1,200 from today. [Keep it] [Delete]". One decision, the consequence is concrete, the destructive action is red.
- Bad: a modal titled "Notice" with a paragraph of explanation and a single "OK" — that's information, not a decision; it belongs inline and just taxes the user with a wall to dismiss.
Edge cases
Must-answer decision
Set dismissable={false} — no × / backdrop escape
WhySome choices can't be silently skipped
Long Burmese/Thai body
Text wraps; the card grows and stays centered
WhyMY/TH run longer — copy must never clip
One action only
Pass just primaryLabel — it goes full width
WhyA lone confirm shouldn't look like it's missing a partner
Dark mode
Card + backdrop follow the theme surfaces/overlay
WhyThe dialog must read in both schemes
One decision per modal
If a dialog has more than one thing to decide, it's a screen, not a modal. Split it or move it into a sheet.
Tokens used
color.surface.primarycolor.overlaycolor.brand.tintcolor.feedback.errorBgcolor.text.primaryradius.2xlelevation.lgtypography.title3