
A modal is a box that takes over the screen until the user deals with it. Used well it keeps people in their flow; used badly it traps them. This guide covers everything oks-ui's Modal does and when to reach for something else.
import { Modal } from "oks-ui/modal";
import "oks-ui/modal.css";
import { Button } from "oks-ui/button";
import "oks-ui/button.css";The shape of a modal
Modal is controlled: you own the open state.
const [open, setOpen] = useState(false);
<Button color="primary" onPress={() => setOpen(true)}>Invite people</Button>
<Modal
isOpen={open}
onClose={() => setOpen(false)}
title="Invite people"
actions={
<>
<Button variant="bordered" onPress={() => setOpen(false)}>Cancel</Button>
<Button color="primary" onPress={send}>Send invites</Button>
</>
}
>
Anyone you invite can read and comment on your posts.
</Modal>title renders the heading that names the dialog for screen readers; actions is the row at the bottom. Put the confirming action last, on the right, where people expect it.
Sizes
size takes xs (320px), sm (420px), md (640px, the default), lg (860px), xl (1100px) and full. You can also pass a number of pixels or any CSS width.
- Confirmations:
sm. A short question doesn't need a wide box. - Forms:
md. - Tables or previews:
lgand up — but consider a page instead.
Confirmations
For a question that interrupts the user and needs an answer, use role="alertdialog" and name the thing in the title:
<Modal
isOpen={open}
onClose={close}
role="alertdialog"
size="sm"
title="Delete “Q3 report”?"
actions={
<>
<Button variant="bordered" onPress={close}>Cancel</Button>
<Button color="danger" onPress={remove}>Delete</Button>
</>
}
>
The file and its history are removed. This can't be undone.
</Modal>Write what will happen, not "Are you sure?".
Forms inside a modal
Two changes make a form dialog behave:
<Modal
isOpen={open}
onClose={close}
title="New project"
closeOnOutsideClick={false}
initialFocusRef={nameRef}
actions={
<>
<Button variant="bordered" onPress={close}>Cancel</Button>
<Button color="primary" isLoading={saving} onPress={save}>Create</Button>
</>
}
>
<input ref={nameRef} />
</Modal>closeOnOutsideClick={false} stops a stray click from losing what someone typed, and initialFocusRef puts the cursor in the first field.
Controlling how it closes
closeOnEscape(default on) — leave it on; Escape is what people press.closeOnOutsideClick(default on) — turn off for forms.dismissible={false}hides the close button, for a dialog that must be answered. Use it rarely, and never without a visible action that closes it.
Appearance
backdrop passes options through to the overlay — blur and backgroundOpacity adjust how much of the page shows through. animationDuration and animationType control the entrance; both respect a reader's "reduce motion" setting.
What it handles for you
Focus moves into the dialog when it opens and is trapped while it's open, Escape closes it, focus returns to the button that opened it, and the page behind stops scrolling. The dialog carries role="dialog", aria-modal="true" and its title as the accessible name.
When not to use a modal
- Long forms. A dialog that scrolls is a page in disguise. Use a page.
- Anything a user might want to link to or refresh. Modals have no address.
- On phones, for anything but a short confirmation. A
Drawersliding from the bottom feels more natural and leaves more room. - Success messages. A toast says "Saved" without asking the user to dismiss it.
Try every prop on the Modal component page.

