Dialog

Dialogs are windows containing contextual information, tasks, or workflows that appear over the user interface. Depending on the kind of Dialog, further interactions may be blocked until the Dialog is acknowledged.

installyarn add @react-spectrum/dialog
version3.0.0-alpha.1
usageimport {Dialog} from '@react-spectrum/dialog'

Example#


import {ActionButton, Button} from '@react-spectrum/button';
import {ButtonGroup} from '@react-spectrum/buttongroup';
import {Content, Header} from '@react-spectrum/view';
import {Dialog, DialogTrigger} from '@react-spectrum/dialog';
import {Divider} from '@react-spectrum/divider';
import {Heading, Text} from '@react-spectrum/typography';

<DialogTrigger>
  <ActionButton>Check connectivity</ActionButton>
  {(close) => (
    <Dialog>
      <Heading>Internet Speed Test</Heading>
      <Header>Connection status: Connected</Header>
      <Divider />
      <Content>
        <Text>
          Start speed test?
        </Text>
      </Content>
      <ButtonGroup>
        <Button variant="secondary" onPress={close}>Cancel</Button>
        <Button variant="cta" onPress={close}>Confirm</Button>
      </ButtonGroup>
    </Dialog>
  )}
</DialogTrigger>

Content#


A standard Dialog has the following anatomy:

Body areaTitle areaFooter content area (optional)Header content area (optional)Button group areaHeaderFooterBodyDivider

These sections can be populated by providing the following components to your Dialog as children: Header, Heading (title), Divider, Content (body), ButtonGroup, and Footer. Each of these components are required in a Spectrum compliant Dialog except for Header, Footer, and Divider so be sure to design your Dialog accordingly.

Examples#

A typical Dialog with a title, contents, and action buttons can be created like so:

<DialogTrigger>
  <ActionButton>Publish</ActionButton>
  {(close) => (
    <Dialog>
      <Heading>Publish 3 pages</Heading>
      <Divider />
      <Content>Confirm publish?</Content>
      <ButtonGroup>
        <Button variant="secondary" onPress={close}>Cancel</Button>
        <Button variant="cta" onPress={close} autoFocus>Confirm</Button>
      </ButtonGroup>
    </Dialog>
  )}
</DialogTrigger>

A dismissable Dialog forgoes its ButtonGroup in favor of rendering a close button at the top right of the Dialog. If your Dialog is dismissable, make sure that you do not include a ButtonGroup in the render. While the Dialog will hide any elements occupying the ButtonGroup section, it will not remove them from the DOM, resulting in possible tab order issues.

<DialogTrigger isDismissable>
  <ActionButton>Status</ActionButton> 
  <Dialog>
    <Heading>Status</Heading>
    <Divider />
    <Content>Printer Status: Connected</Content>
  </Dialog>
</DialogTrigger>

It is important to note that the Heading, Header, Content, and Footer content elements accept any renderable node, not just strings. This allows you to create Dialogs for more complex workflows, such as including a form for the user to fill out or adding confirmation checkboxes.

<DialogTrigger>
  <ActionButton>Register</ActionButton> 
  {(close) => (
    <Dialog>
      <Heading>
        <Flex alignItems="center">
          <Book size="S" />
          <Text>
            Register for newsletter
          </Text>
        </Flex>
      </Heading>
      <Header>
        <Link>
          <a href="//example.com" target="_blank">What is this?</a>
        </Link>
      </Header>
      <Divider />
      <Content>
        <Form>
          <TextField label="First Name" placeholder="John" autoFocus />
          <TextField label="Last Name" placeholder="Smith" />
          <TextField label="Street Address" placeholder="123 Any Street" />
          <TextField label="City" placeholder="San Francisco" />
        </Form>
      </Content>
      <Footer>
        <Checkbox>
          I want to receive updates for exclusive offers in my area.
        </Checkbox>
      </Footer> 
      <ButtonGroup>
        <Button variant="secondary" onPress={close}>Cancel</Button>
        <Button variant="cta" onPress={close}>Register</Button>
      </ButtonGroup>
    </Dialog>
  )}
</DialogTrigger>

The example below illustrates how a Dialog with a hero image could be rendered via the hero slot:

<DialogTrigger>
  <ActionButton>Upload</ActionButton>
  {(close) => (
    <Dialog>
      <Image slot="hero" src="https://i.imgur.com/Z7AzH2c.png" objectFit="cover" />
      <Heading>Upload file</Heading>
      <Divider />
      <Content>Are you sure you want to upload this file?</Content>
      <ButtonGroup>
        <Button variant="secondary" onPress={close}>Cancel</Button>
        <Button variant="cta" onPress={close} autoFocus>Confirm</Button>
      </ButtonGroup>
    </Dialog>
  )}
</DialogTrigger>

Accessibility#

Keep in mind when creating your Dialog that the tab order within the Dialog follows the order of the children provided. You are also responsible for determining what component, if any, should be automatically focused on Dialog render.

Labeling#


Accessibility#

The title of a Dialog is typically provided via its heading. By default, the Dialog sets its aria-labelledby to match the heading id, but this can be overridden by providing an aria-labelledby prop to the Dialog directly. If a visible label isn't specified, an aria-label must be provided instead.

<DialogTrigger>
  <ActionButton>See Terms and Conditions</ActionButton>
  {(close) => (
    <Dialog aria-labelledby="heading-id">
      <Text slot="header" id="heading-id">Terms and Conditions</Text> 
      <Divider />
      <Content>
        <Text>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Mi proin sed libero enim. Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Sed enim ut sem viverra aliquet eget sit amet tellus. Diam quis enim lobortis scelerisque fermentum dui faucibus in ornare. Diam quam nulla porttitor massa id. Eleifend mi in nulla posuere sollicitudin. Turpis nunc eget lorem dolor sed viverra ipsum nunc. Faucibus in ornare quam viverra. Risus commodo viverra maecenas accumsan lacus vel facilisis volutpat est. Nam libero justo laoreet sit amet cursus sit. Netus et malesuada fames ac. Dictum fusce ut placerat orci nulla pellentesque dignissim enim sit. Eros donec ac odio tempor orci. Ut etiam sit amet nisl purus in mollis nunc. Nisl rhoncus mattis rhoncus urna neque viverra. Convallis aenean et tortor at risus. Diam phasellus vestibulum lorem sed risus ultricies.</Text>
      </Content>
      <ButtonGroup>
        <Button autoFocus variant="cta" onPress={close}>Acknowledge</Button>
      </ButtonGroup>
    </Dialog>
  )}
</DialogTrigger>

Events#


For Dialogs, user defined callbacks should be chained with the DialogTrigger's close function in the press/click handler of the Dialog's action buttons. The following example alerts if the Dialog's save or cancel button is clicked.

function Example() {
  let alertSave = (cb) => {
    cb();
    alert('Profile saved!');
  }

  let alertCancel = (cb) => {
    cb();
    alert('Profile not saved!');
  }

  return (
    <DialogTrigger>
      <ActionButton>Set Profile</ActionButton>
      {(close) => (
        <Dialog>
          <Heading>Profile</Heading>
          <Divider />
          <ButtonGroup>
            <Button autoFocus variant="cta" onPress={() => alertSave(close)}>Save</Button>
            <Button variant="secondary" onPress={() => alertCancel(close)}>Cancel</Button>
          </ButtonGroup>
          <Content>
            <Form>
              <TextField label="Name" />
              <Checkbox>Make private</Checkbox>
            </Form>
          </Content>    
        </Dialog>
      )}
    </DialogTrigger>
  );
}

Additionally, DialogTrigger accepts an onOpenChange prop which is triggered whenever the Dialog is opened or closed. For more information on the onOpenChange prop see the DialogTrigger docs.

Dismissable dialogs#

Dismissable Dialogs support an onDismiss prop which is triggered whenever the Dialog's close button is clicked. Similar to non-dismissable dialogs, you must chain the DialogTrigger's close function with whatever callback you provide as onDismiss.

function Example() {
  let alertDismiss = (cb) => {
    cb();
    alert('Dialog dismissed.');
  }

  return (
    <DialogTrigger isDismissable>
      <ActionButton>Info</ActionButton>
      {(close) => (
        <Dialog onDismiss={() => alertDismiss(close)}>
          <Heading>Version Info</Heading>
          <Divider />
          <Content>
            <Text>
              Version 1.0.0, Copyright 2020
            </Text>
          </Content>    
        </Dialog>
      )}
    </DialogTrigger>
  );
}

Note that onDismiss is optional. If you don't need to add a onDismiss handler to your dismissable Dialog, you may omit the wrapping close function surrounding the Dialog. An example of this can be found in the Examples section above.

Props#


NameTypeDefaultDescription
childrenReactNodeThe contents of the Dialog.
size'S''M''L'The size of the Dialog. Only applies to "modal" type Dialogs.
isDismissablebooleanWhether the Dialog is dismissable. See the examples for more details.
UNSAFE_classNamestring
UNSAFE_styleCSSProperties
Events
NameTypeDefaultDescription
onDismiss() => voidHandler that is called when the 'x' button of a dismissable Dialog is clicked.
Layout
NameTypeDefaultDescription
flexstringnumberboolean
flexGrownumber
flexShrinknumber
flexBasisnumberstring
alignSelf'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'stretch'
justifySelf'auto' | 'normal' | 'start' | 'end' | 'flex-start' | 'flex-end' | 'self-start' | 'self-end' | 'center' | 'left' | 'right' | 'stretch'
flexOrdernumber
gridAreastring
gridColumnstring
gridRowstring
gridColumnStartstring
gridColumnEndstring
gridRowStartstring
gridRowEndstring
Spacing
NameTypeDefaultDescription
marginDimensionValue
marginTopDimensionValue
marginLeftDimensionValue
marginRightDimensionValue
marginBottomDimensionValue
marginStartDimensionValue
marginEndDimensionValue
marginXDimensionValue
marginYDimensionValue
Sizing
NameTypeDefaultDescription
widthDimensionValue
minWidthDimensionValue
maxWidthDimensionValue
heightDimensionValue
minHeightDimensionValue
maxHeightDimensionValue
Positioning
NameTypeDefaultDescription
position'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'
topDimensionValue
bottomDimensionValue
leftDimensionValue
rightDimensionValue
startDimensionValue
endDimensionValue
zIndexnumber
isHiddenboolean
Accessibility
NameTypeDefaultDescription
role'dialog''alertdialog'The role of the Dialog.
idstring
tabIndexnumber
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-controlsstringIdentifies the element (or elements) whose contents or presence are controlled by the current element.
aria-ownsstringIdentifies an element (or elements) in order to define a visual, functional, or contextual parent/child relationship between DOM elements where the DOM hierarchy cannot be used to represent the relationship.
aria-hiddenboolean'false''true'Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable.

Visual options#


Dialog types#

Dialogs can be rendered as modals, popovers, or trays. See the DialogTrigger docs for more information.

<DialogTrigger isDismissable type="modal">
  <ActionButton>Trigger Modal</ActionButton>
  <Dialog>
    <Heading>Modal</Heading>
    <Divider />
    <Content>
      <Text>
        This is a modal.
      </Text>
    </Content>    
  </Dialog>
</DialogTrigger>

<DialogTrigger type="popover">
  <ActionButton>Trigger Popover</ActionButton>
  <Dialog>
    <Heading>Popover</Heading>
    <Divider />
    <Content>
      <Text>
        This is a popover.
      </Text>
    </Content>    
  </Dialog>
</DialogTrigger>

<DialogTrigger type="tray">
  <ActionButton>Trigger Tray</ActionButton>
  <Dialog>
    <Heading>Tray</Heading>
    <Divider />
    <Content>
      <Text>
        This is a tray.
      </Text>
    </Content>    
  </Dialog>
</DialogTrigger>

Size#

Only modal type Dialogs support a user defined size prop. Note that the fullscreen and fullscreenTakeover sizes require the DialogTrigger type prop to be set as fullscreen and fullscreenTakeover respectively for container sizing considerations. Modal sizes on mobile devices are also unaffected by this prop due to screen constraints.

<DialogTrigger>
  <ActionButton>Small</ActionButton>
  {(close) => (
    <Dialog size="S">
      <Heading>Profile</Heading>
      <Divider />
      <ButtonGroup>
        <Button autoFocus variant="cta" onPress={close}>Save</Button>
        <Button variant="secondary" onPress={close}>Cancel</Button>
      </ButtonGroup>
      <Content>
        <Form>
          <TextField label="Name" />
          <Checkbox>Make private</Checkbox>
        </Form>
      </Content>    
    </Dialog>
  )}
</DialogTrigger>

<DialogTrigger>
  <ActionButton>Medium</ActionButton>
  {(close) => (
    <Dialog size="M">
      <Heading>Profile</Heading>
      <Divider />
      <ButtonGroup>
        <Button autoFocus variant="cta" onPress={close}>Save</Button>
        <Button variant="secondary" onPress={close}>Cancel</Button>
      </ButtonGroup>
      <Content>
        <Form>
          <TextField label="Name" />
          <Checkbox>Make private</Checkbox>
        </Form>
      </Content>    
    </Dialog>
  )}
</DialogTrigger>

<DialogTrigger>
  <ActionButton>Large</ActionButton>
  {(close) => (
    <Dialog size="L">
      <Heading>Profile</Heading>
      <Divider />
      <ButtonGroup>
        <Button autoFocus variant="cta" onPress={close}>Save</Button>
        <Button variant="secondary" onPress={close}>Cancel</Button>
      </ButtonGroup>
      <Content>
        <Form>
          <TextField label="Name" />
          <Checkbox>Make private</Checkbox>
        </Form>
      </Content>    
    </Dialog>
  )}
</DialogTrigger>