Modal

● Stable · v0.1

A 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

9:41

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)
<Modal
visible={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