Modal

A modal window that opens on top of all other content.

The Modal component can be used to display a form that requires user input, or to display a message to the user that requires their attention.

import { Modal } from '@rewind-ui/core';

function App() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Modal size="md" open={open} onClose={() => setOpen(false)}>
        <Card>
          // ...
        </Card>
      </Modal>

      <Button onClick={() => setOpen(true)}>Click me!</Button>
    </>
  );
}

Import


Import the Modal component using the following import statement.

import { Modal } from '@rewind-ui/core';

Mode


The Modal component can be used as a normal dialog or a fullscreen dialog. The mode prop can be used to set the mode of the dialog.

<View>
  <Modal mode="dialog">
    // ...
  </Modal>

  <Modal mode="fullscreen">
    // ...
  </Modal>
</View>

Position


The Modal component can be positioned at the top, center or bottom of the screen. The position prop can be used to set the position of the dialog.

<View>
  <Modal position="top">
    // ...
  </Modal>

  <Modal position="center">
    // ...
  </Modal>

  <Modal position="bottom">
    // ...
  </Modal>
</View>

Sizes


Available size values are: auto, sm, md, lg and screen. The available sizes are responsive and will change based on the screen size.

<View>
  <Modal size="auto">
    // ...
  </Modal>

  <Modal size="sm">
    // ...
  </Modal>

  <Modal size="md">
    // ...
  </Modal>

  <Modal size="lg">
    // ...
  </Modal>

  <Modal size="xl">
    // ...
  </Modal>

  <Modal size="screen">
    // ...
  </Modal>
</View>

Radiuses


Available radius values are: none, sm, base, md and lg.

<View>
  <Modal radius="none">
    // ...
  </Modal>

  <Modal radius="sm">
    // ...
  </Modal>

  <Modal radius="base">
    // ...
  </Modal>

  <Modal radius="md">
    // ...
  </Modal>

  <Modal radius="lg">
    // ...
  </Modal>
</View>

Shadows


Available shadow values are: none, sm, base, md, lg and xl.

<View>
  <Modal shadow="none" overlayColor="white">
    // ...
  </Modal>

  <Modal shadow="sm" overlayColor="white">
    // ...
  </Modal>

  <Modal shadow="base" overlayColor="white">
    // ...
  </Modal>

  <Modal shadow="md" overlayColor="white">
    // ...
  </Modal>

  <Modal shadow="lg" overlayColor="white">
    // ...
  </Modal>

  <Modal shadow="xl" overlayColor="white">
    // ...
  </Modal>
</View>

Overlay


The underlying Overlay component can be customized using the overlayColor, overlayBlur and overlayOpacity props.

Close on click

The closeOnClick prop can be used to close the modal when the overlay is clicked.

<View>
  <Modal overlayCloseOnClick={true}>
    // ...
  </Modal>

  <Modal overlayCloseOnClick={false}>
    // ...
  </Modal>
</View>

Close on escape


The closeOnEscape prop can be used to close the modal when the escape key is pressed.

<View>
  <Modal closeOnEscape={true}>
    // ...
  </Modal>

  <Modal closeOnEscape={false}>
    // ...
  </Modal>
</View>

Multiple modals


An unlimited amount of modals can be added using the Modal.Group component.

import { useState } from 'react';
import { Modal, Card, Button } from '@rewind-ui/core';

const MultiModal = () => {
  const [parentOpen, setParentOpen] = useState(false);
  const [firstChildOpen, setFirstChildOpen] = useState(false);
  const [secondChildOpen, setSecondChildOpen] = useState(false);

  return (
    <>
      <Modal.Group>
        <Modal open={parentOpen} onClose={() => setParentOpen(false)}>
          <Card className="w-full">
            <Card.Body>
              <h1>This is a parent modal</h1>
            </Card.Body>

            <Card.Footer className="bg-gray-50/50 justify-end space-x-2">
              <Button variant="secondary" onClick={() => setParentOpen(false)}>
                Cancel
              </Button>
              <Button onClick={() => setFirstChildOpen(true)}>Next</Button>
            </Card.Footer>
          </Card>
        </Modal>

        <Modal open={firstChildOpen} onClose={() => setFirstChildOpen(false)}>
          <Card className="w-full">
            <Card.Body>
              <h1>This is a child modal</h1>
            </Card.Body>

            <Card.Footer className="bg-gray-50/50 justify-end space-x-2">
              <Button variant="secondary" onClick={() => setFirstChildOpen(false)}>
                Back
              </Button>
              <Button onClick={() => setSecondChildOpen(true)}>Next</Button>
            </Card.Footer>
          </Card>
        </Modal>

        <Modal open={secondChildOpen} onClose={() => setSecondChildOpen(false)}>
          <Card className="w-full">
            <Card.Body>
              <h1>This is a second child modal</h1>
            </Card.Body>

            <Card.Footer className="bg-gray-50/50 justify-end space-x-2">
              <Button variant="secondary" onClick={() => setSecondChildOpen(false)}>
                Back
              </Button>
            </Card.Footer>
          </Card>
        </Modal>
      </Modal.Group>

      <Button onClick={() => setParentOpen(true)}>Open</Button>
    </>
  );
};

Focus trap


The Modal component is using a focus trap hook to trap focus within the modal. This means that when the modal is open, the user can only interact with the modal and not with the rest of the page.

Examples


Card example

Combining the Modal component with the Card component can be used to create a modal with a header, a body and a footer.

import { Button, Card, Modal, Text } from '@rewind-ui/core';
import { useState } from 'react';
import * as React from 'react';
import { X } from '@phosphor-icons/react';

const App = () => {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Modal open={open} size="md" onClose={() => setOpen(false)}>
        <Card>
          <Card.Header
            className="bg-gray-50/50"
            actions={
              <Button onClick={() => setOpen(false)} size="xs" color="gray" icon={true}>
                <X />
              </Button>
            }
          >
            Privacy Policy
          </Card.Header>
          <Card.Body className="max-h-[300px] overflow-auto space-y-3">
            <Text className="block">
              At [Company], we are committed to protecting your privacy and personal information. We
              collect information about you when you use our services and when you provide it to us
              directly. This information is used to provide, maintain, and improve our services, as
              well as to develop new features and protect the rights and safety of our users and the
              public.
            </Text>

            <Text className="block">
              We may share your information with third parties in certain circumstances, such as
              with your consent, to service providers and partners, or to comply with legal
              obligations. We take steps to secure your information, but cannot guarantee its
              complete protection.
            </Text>

            <Text className="block">
              We respect your right to privacy and will allow you to control your personal
              information. You may access and update your information, or opt out of certain
              communications, at any time by contacting us. If you have any concerns about how we
              handle your information, please don't hesitate to reach out. We are committed to
              addressing any issues and to continually improving our privacy practices.
            </Text>
          </Card.Body>
          <Card.Footer className="bg-gray-50/50 justify-end space-x-2">
            <Button onClick={() => setOpen(false)} size="md" tone="transparent" color="gray">
              Cancel
            </Button>
            <Button
              className="font-semibold"
              onClick={() => setOpen(false)}
              size="md"
              color="blue"
              tone="light"
            >
              Accept
            </Button>
          </Card.Footer>
        </Card>
      </Modal>

      <Button onClick={() => setOpen(true)}>Open</Button>
    </>
  );
};

Form example

Click Tab button on the following modal it can be noticed that the focus is trapped within the modal.

import { Button, Card, Modal } from '@rewind-ui/core';
import { useState } from 'react';
import * as React from 'react';
import { X } from '@phosphor-icons/react';

const App = () => {
  const [open, setOpen] = useState(false);

  return (
    <>
      <Modal open={open} size="sm" onClose={() => setOpen(false)}>
        <Card size="sm" className="w-full">
          <Card.Header
            className="bg-gray-50/50 font-medium"
            actions={
              <Button onClick={() => setOpen(false)} size="xs" color="gray" icon={true}>
                <X />
              </Button>
            }
          >
            Sign in
          </Card.Header>
          <Card.Body className="space-y-3">
            <FormControl size="sm">
              <FormControl.Label required>Email address</FormControl.Label>
              <FormControl.Input type="email" tone="solid" placeholder="Enter your email..." />
            </FormControl>

            <FormControl size="sm">
              <FormControl.Label required>Password</FormControl.Label>
              <FormControl.Input
                type="password"
                tone="solid"
                placeholder="Enter your password..."
              />
            </FormControl>
          </Card.Body>
          <Card.Footer className="bg-gray-50/50 justify-end space-x-2">
            <Button onClick={() => setOpen(false)} size="sm" tone="transparent" color="gray">
              Close
            </Button>
            <Button
              className="font-semibold"
              onClick={() => setOpen(false)}
              size="sm"
              color="blue"
              tone="light"
            >
              Login
            </Button>
          </Card.Footer>
        </Card>
      </Modal>

      <Button onClick={() => setOpen(true)}>Open</Button>
    </>
  );
};

API Reference


Properties

PropTypeDescriptionDefault
closeOnEscapebooleanMakes the modal close when the escape key is pressedtrue
colorModalColorSets the modal colorwhite
modeModalModeSets the modal modedialog
onClose() => voidSets the onClose callback functionundefined
openbooleanSets the open statefalse
overlayBlurOverlayBlurSets the overlay backdrop blur filtersm
overlayCloseOnClickbooleanMakes the modal close when the overlay is clickedtrue
overlayColorOverlayColorSets the overlay colordark
overlayOpacityOverlayOpacitySets the overlay opacity50
radiusModalRadiusSets the border radiusmd
shadowModalShadowSets the shadow sizebase
sizeModalSizeSets the modal sizesm

Types

type ModalColor = 'white' | 'gray' | 'slate' | 'zinc';
type ModalMode = 'fullscreen' | 'dialog';
type ModalRadius = 'none' | 'sm' | 'base' | 'md' | 'lg';
type ModalShadow = 'none' | 'sm' | 'base' | 'md' | 'lg' | 'xl';
type ModalSize = 'auto' | 'sm' | 'md' | 'lg' | 'xl' | 'screen';

Accessibility


The Modal component partially adheres to the WAI-ARIA Dialog (Modal) Pattern.

To make the modal dialog fully accessible, aria-labelledby and aria-describedby props should be provided to the Modal component. The aria-labelledby prop should point to the id of the Modal header element, and the aria-describedby prop should point to the id of the body element.

Keyboard interactions

KeyDescription
TabMoves focus to the next focusable element
Shift + TabMoves focus to the previous focusable element
EscapeCloses the dialog