Skip to content

Modal

A modal is a dialog that intentionally calls for users attention. Models interrupt users workflow by design.

They are overlays that prevent users from interacting with the rest of the application until a specific action is taken. They can be disruptive because they require merchants to take an action before they can continue interacting with the rest of the site. It should be used thoughtfully and sparingly.

React

released

Vue

released

Elements

released

Android

released

iOS

released

Example

Usage

Design Guidelines

See Figma: Warp - Components / Modal

Accessibility

Modal needs either aria-label or aria-labelledby to be accessible to screen readers.

All dialogs must have a title. Titles appear in bold at the top of the dialog and use a few words to convey the outcome of what will happen if a user continues with an action. Use the property title for this.

Questions?

Feel free to ask any questions on usage in the Warp DS Slack channel: #nmp-warp-design-system

Frameworks

Import

You can import the component like so:

js
import { Modal } from '@warp-ds/react';

or import it individually to optimize your JS bundle size by adding only the components you need:

js
import { Modal } from '@warp-ds/react/components/modal'

Props

Required props

nametypedefaultnotes

children

Element
|Element[]

The modal contents

open

boolean

Whether the modal is open or not

Optional Props

nametypedefaultnotes

title

string
|Element
|Element[]

A string or your own custom elements

left

boolean
|Element
|Element[]

Whether a default back button should render with an onDismiss() callback. It can also be your own custom element(s).

right

boolean
|Element
|Element[]

A default close button or your own custom elements

footer

Element
|Element[]

Buttons passed to the footer

className

string

Additional classes added to the container

id

string

An id for the container and ARIA attributes. A random id is generated if none is provided.

style

CSSProperties

Additional styles to the container. More info about CSSProperties

aria-label

number

Defines a string value that labels the current element. Must be set if neither aria-labelledby or <ModalHeading> is defined,

aria-labelledby

string

Identifies the element (or elements) that labels the current element. Must be set if neither aria-label or <ModalHeading> is defined.

initialFocusRef

RefObject

A reference to the element that should be focused. By default it'll be the first interactive element. More info

Events

namewhen

onDismiss

() => void

Handler that is called when the user presses esc or clicks outside the modal.

onLeftClick

() => void

Handler that is called when the user clicks the default back button. Requires the left prop to be true.

Example

jsx
function Example() {
  const [open, setOpen] = React.useState(false);
  const [left, setLeft] = React.useState(true);
  const [height, setHeight] = React.useState('68%');

  const toggleModal = () => setOpen(!open);

  return (
    <>
      <Button utility onClick={toggleModal}>
        Open modal
      </Button>

      <Modal
        left={left}
        right
        open={open}
        onDismiss={toggleModal}
        title="Hello title"
        footer={
          <Button primary onClick={toggleModal}>
            Confirm
          </Button>
        }
        style={{
          '--w-modal-max-height': height,
          '--w-modal-height': '100%',
        }}
      >
        <div className="space-x-8">
          <button
            onClick={() => setHeight(height === '68%' ? '100%' : '68%')}
            className="button button--utility button--small mb-32"
          >
            Modify height
          </button>
          <button
            onClick={() => setLeft(!left)}
            className="button button--utility button--small mb-32"
          >
            Toggle the back-button
          </button>
        </div>
        <div>
          <h1 className="h4 mb-16">This is a title for the content area</h1>
          <p>
            Life as a shorty shouldn't be so rough. Behold the bold soldier
            control the globe slowly, proceeds to blow, swinging swords like
            Shinobi. The game of chess, is like a swordfight, you must think
            first before you move. My beats travel like a vortex through your
            spine, to the top of your cerebral cortex. I smoke on the mic like
            smoking Joe Frazier, the hell raiser, raising hell with the flavor.
          </p>
          <p>
            I breaks it down to the bone gristle, Ill speaking Scud missile heat
            seeking, Johnny Blazing. Protect Ya Neck, my sword still remain
            imperial before I blast the mic, RZA scratch off the serial.
            Shackling the masses with drastic rap tactics, graphic displays melt
            the steel like blacksmiths. Perpendicular to the square we stay in
            gold like Flair, escape from your dragon's lair in particular. Shame
            on you when you stepped through to The Ol Dirty Bastard straight
            from the Brooklyn Zoo. Protect Ya Neck, my sword still remain
            imperial before I blast the mic, RZA scratch off the serial.
          </p>
          <p>
            Life as a shorty shouldn't be so rough. Handcuffed in the back of a
            bus, forty of us. Rae got it going on pal, call me the rap
            assassinator, rhymes rugged and built like Schwarzenegger. My beats
            travel like a vortex through your spine, to the top of your cerebral
            cortex. Life as a shorty shouldn't be so rough. Well I'm a sire, I
            set the microphone on fire, rap styles vary and carry like Mariah.
          </p>
        </div>
      </Modal>
    </>
  );
}

Content

WARP guidelines state the following:

  • The cancel action should always be on the left, while the main action is to the right.

Non dismissable modals

The onDismiss prop is optional. It can be dropped to create a modal that won't respond to esc key presses and/or clicking outside the modal.

jsx
function Example() {
  const [open, setOpen] = React.useState(false);
  const [mustAgree, setMustAgree] = React.useState(false);
  const [hasAgreed, setHasAgreed] = React.useState(false);

  const toggleModal = () => {
    if (open && !hasAgreed) {
      setMustAgree(true);
      return;
    }
    setMustAgree(false);
    setOpen(!open);
  };

  return (
    <>
      <Button utility onClick={toggleModal}>
        Open modal
      </Button>

      <Modal
        open={open}
        onDismiss={hasAgreed ? toggleModal : undefined}
        title="Non dismissable"
        footer={
          <>
            {mustAgree && <p className="m-10">You must agree first!</p>}
            <Button primary onClick={toggleModal}>
              Save
            </Button>
          </>
        }
      >
        <p>To go further, you need to agree to these bogus terms</p>
        <Toggle
          type="checkbox"
          label="I agree"
          checked={hasAgreed}
          onChange={(state) => setHasAgreed(state)}
        />
      </Modal>
    </>
  );
}

Customizing focus behavior

By default, the first interactive element in the modal will be focused when the modal opens. Use initialFocusRef to customize this behavior. For instance, this can be used to focus a call-to-action button.

jsx
function Example() {
  const [open, setOpen] = React.useState(false);
  const focusRef = React.useRef();

  const toggleModal = () => setOpen(!open);

  return (
    <>
      <Button utility onClick={toggleModal}>
        Open modal
      </Button>
      <Modal
        open={open}
        initialFocusRef={focusRef}
        title="Customized focus behavior"
        footer={
          <>
            <Button onClick={toggleModal} className="mr-12">
              Cancel
            </Button>
            <Button primary ref={focusRef} onClick={toggleModal}>
              Accept
            </Button>
          </>
        }
      >
        <p>The call to action button has inital focus.</p>
      </Modal>
    </>
  );
}