Modal components

Powerful and customizable React modal components built for modern applications and websites. These modals are built using React Aria and styled with Tailwind CSS.

Installation

You can add this modal component using our CLI or manually:

npx untitledui add modal

Modals examples

Below are examples and variations of this modals component:

API reference

DatePickerModal

Import DatePickerModal from @/components/application/modals/date-picker-modal. Renders React Aria’s DateRangePicker — all of its props are supported.

DatePickerModal props
PropTypeDefaultDescription
onApply(() => void)—Called when the user presses Apply, just before the modal closes. Use it to commit the range reported by onChange.
onCancel(() => void)—Called when the user presses Cancel, just before the modal closes. Use it to revert to the range you held before the modal opened.
Props from React Aria (41)
DatePickerModal inherited props
PropTypeDefaultDescription
onBlur((e: FocusEvent<Element, Element>) => void)—Handler that is called when the element loses focus.
onFocus((e: FocusEvent<Element, Element>) => void)—Handler that is called when the element receives focus.
onFocusChange((isFocused: boolean) => void)—Handler that is called when the element's focus status changes.
autoFocusboolean—Whether the element should receive focus on render.
onKeyDown((e: KeyboardEvent) => void)—Handler that is called when a key is pressed.
onKeyUp((e: KeyboardEvent) => void)—Handler that is called when a key is released.
formstring—The <form> element to associate the input with. The value of this attribute must be the id of a <form> in the same document.
allowsNonContiguousRangesboolean—When combined with isDateUnavailable, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.
defaultOpenboolean—Whether the overlay is open by default (uncontrolled).
defaultValueRangeValue<DateValue> | null—The default value (uncontrolled).
descriptionReactNode—A description for the field. Provides a hint such as specific requirements for what to choose.
endNamestring—The name of the end date input element, used when submitting an HTML form.
errorMessageReactNode | ((v: ValidationResult) => ReactNode)—An error message for the field.
firstDayOfWeek
"sun""mon""tue""wed""thu""fri""sat"
—The day that starts the week.
granularity
"day""hour""minute""second"
—Determines the smallest unit that is displayed in the date picker. By default, this is "day" for dates, and "minute" for times.
hideTimeZonebooleanfalseWhether to hide the time zone abbreviation.
hourCycle
1224
—Whether to display the time in 12 or 24 hour format. By default, this is determined by the user's locale.
idstring—The element's unique identifier.
isDateUnavailable((date: DateValue, anchorDate: CalendarDate | null) => boolean)—Callback that is called for each date of the calendar. If it returns true, then the date is unavailable.
isDisabledboolean—Whether the input is disabled.
isInvalidboolean—Whether the input value is invalid.
isOpenboolean—Whether the overlay is open by default (controlled).
isReadOnlyboolean—Whether the input can be selected but not changed by the user.
isRequiredboolean—Whether user input is required on the input before form submission.
labelReactNode—The content to display as the label.
maxValueDateValue | null—The maximum allowed date that a user may select.
minValueDateValue | null—The minimum allowed date that a user may select.
onChange((value: RangeValue<DateValue> | null) => void)—Handler that is called when the value changes.
onOpenChange((isOpen: boolean) => void)—Handler that is called when the overlay's open state changes.
pageBehavior
"single""visible"
visibleControls the behavior of paging. Pagination either works by advancing the visible page by visibleDuration (default) or one unit of visibleDuration.
placeholderValueDateValue | null—A placeholder date that influences the format of the placeholder shown when no value is selected. Defaults to today's date at midnight.
shouldForceLeadingZerosboolean—Whether to always show leading zeros in the month, day, and hour fields. By default, this is determined by the user's locale.
startNamestring—The name of the start date input element, used when submitting an HTML form.
validate((value: RangeValue<DateValue>) => ValidationError | true | null | undefined)—A function that returns an error message if a given value is invalid. Validation errors are displayed to the user when the form is submitted if validationBehavior="native". For realtime validation, use the isInvalid prop instead.
validationBehavior
"aria""native"
"aria"Whether to use native HTML form validation to prevent form submission when the value is missing or invalid, or mark the field as required or invalid via ARIA.
validationState
"valid""invalid"
—Deprecated.—
valueRangeValue<DateValue> | null—The current value (controlled).
aria-describedbystring—Identifies the element (or elements) that describes the object.
aria-detailsstring—Identifies the element (or elements) that provide a detailed, extended description for the object.
aria-labelstring"Date range picker"Defines a string value that labels the current element.
aria-labelledbystring—Identifies the element (or elements) that labels the current element.

Dialog

Import Dialog from @/components/application/modals/modal. Renders React Aria’s Dialog — all of its props are supported.

Dialog props
PropTypeDefaultDescription
childrenReactNode | ((opts: DialogRenderProps) => ReactNode)—Children of the dialog. A function may be provided to access a function to close the dialog.
idstring—The element's unique identifier.
Styling (4)
Dialog styling props
PropTypeDefaultDescription
slotstring | null—A slot name for the component. Slots allow the component to receive props from a parent component. An explicit null value indicates that the local props completely override all props received from a parent.
classNamestring—The CSS className for the element.
renderDOMRenderFunction<"section", undefined>—Overrides the default DOM element with a custom render function. This allows rendering existing components with built-in styles and behaviors such as router links, animation libraries, and pre-styled components.Requirements:
  • You must render the expected element type (e.g. if <button> is expected, you cannot render an <a>).
  • Only a single root DOM element can be rendered (no fragments).
  • You must pass through props and ref to the underlying DOM element, merging with your own prop as appropriate.
styleCSSProperties—The inline style for the element.
Accessibility (5)
Dialog accessibility props
PropTypeDefaultDescription
aria-describedbystring—Identifies the element (or elements) that describes the object.
aria-detailsstring—Identifies the element (or elements) that provide a detailed, extended description for the object.
aria-labelstring—Defines a string value that labels the current element.
aria-labelledbystring—Identifies the element (or elements) that labels the current element.
role
"dialog""alertdialog"
"dialog"The accessibility role for the dialog.

Also accepts the global HTML attributes (dir, lang, hidden, inert, translate), all standard React DOM event handlers.

FAQs

Please refer to our frequently asked questions page for more.

Add the base modal.tsx with npx untitledui add modal. The examples on this page are PRO and install one at a time with the command shown on each, for example npx untitledui@latest add modals/stacked-left-aligned.

Put the isOpen and onOpenChange props directly on ModalOverlay and drive them from your own state: <ModalOverlay isOpen={isOpen} onOpenChange={setIsOpen} isDismissable>. Anything inside the modal can then close it by calling setIsOpen(false), and a CloseButton closes it without a handler because it fills React Aria's "close" slot.

Three components from modal.tsx, nested in this order: ModalOverlay (holds the open state through isOpen/onOpenChange and renders the backdrop and positioning), Modal (the container, including the zoom animations), and Dialog (the content wrapper). Add a CloseButton inside the Dialog yourself if you want a close icon in the corner.

Leave isDismissable off ModalOverlay (it defaults to false) so clicking the backdrop does nothing, and pass isKeyboardDismissDisabled to stop Escape from closing it. Neither prop affects a CloseButton, so remove that too if the examples you copied include one.

ModalOverlay fades in over 300ms and out over 200ms, and Modal zooms in from and out to 95% scale with the same timings. Both accept a className function that receives isEntering and isExiting, and your classes are merged last with tailwind-merge, so they override the defaults.

Below the sm breakpoint the overlay aligns the modal to the bottom of the screen; from sm up it is centered. The modal's max height is capped to the visible viewport minus the overlay padding, and Dialog scrolls its content when it is taller than that.

Give each Dialog an accessible name, either with a React Aria Heading using slot="title", as the examples do, or with aria-label. React Aria handles the rest: focus is contained inside the modal while it is open and returns to the previously focused element when it closes, and Escape closes it unless isKeyboardDismissDisabled is set.