Bedrock Design System

Modal

Modals are interactive dialogs that overlay the current content, requiring user engagement before being dismissed. They often present information, gather input, or confirm actions.


AnatomyPermalink to: Anatomy

...
  1. Title: Describes the purpose of the Modal

  2. Close button: Dismisses the Modal

  3. Description (optional): Description that adds more context

  4. Tabs (optional): Built in tabs

  5. Content: The main content

  6. Actions (optional): Action buttons can be placed here


UsagePermalink to: Usage

The Modal is used for displaying content that requires immediate user attention or interaction, without leaving the context of the current page. When a Modal is shown, the rest of the page becomes inert until the Modal has been dismissed.

When to usePermalink to: When to use

  • For short forms, brief information or confirmations prompts.

When not to usePermalink to: When not to use

  • For longer bodies of text and forms, the Slide In is better suited, as it provides a better scroll experience for the user.

AccessibilityPermalink to: Accessibility

The Modal uses the native HTML dialog element(external link) which will ensure good accessibility in all modern browsers, including: native focus trapping, layering of several dialogs, backdrop and focus restoration.

Headings are requiredPermalink to: Headings are required

To ensure a meaningful title for the Modal, the heading prop is required.

A close label is requiredPermalink to: A close label is required

The Modal has a built-in close button, which ensures that the Modal will always have at least one focusable element, and that the first focused element is a method to dismiss the Modal. The close button needs a label passed with the closeLabel prop.

Focus restorationPermalink to: Focus restoration

For focus restoration to work, it is important that the element that triggers the Modal is still visible on the page when the Modal is dismissed. If that's not possible, focus restoration should be handled manually with the onDismiss callback.


ExamplesPermalink to: Examples

SizesPermalink to: Sizes

The Modal comes in four sizes:

NameMax widthHeightFixed height
small512pxHug content~316px
medium768pxHug content~475px
large1024pxHug content~712px
fullscreenFill viewport (minus padding)Fill viewport (minus padding)N/A

The Modal's width will fill out the viewport (minus padding), until the viewport is bigger than the max width, where it will stay at max width and center itself in the viewport.

Fixed height is based on the golden ration relative to the max width (See Fixed height).

Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open small modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Do you want to save this?"
  closeLabel="Close modal"
>
  <Modal.Content>
    <BodyText>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id
      tortor tincidunt metus feugiat maximus.
    </BodyText>
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Cancel
    </Button>
  </Modal.Actions>
</Modal>
Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open medium modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Do you want to save this?"
  closeLabel="Close modal"
  size="medium"
>
  <Modal.Content>
    <BodyText>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id
      tortor tincidunt metus feugiat maximus.
    </BodyText>
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Cancel
    </Button>
  </Modal.Actions>
</Modal>
Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open large modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Do you want to save this?"
  closeLabel="Close modal"
  size="large"
>
  <Modal.Content>
    <BodyText>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id
      tortor tincidunt metus feugiat maximus.
    </BodyText>
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Cancel
    </Button>
  </Modal.Actions>
</Modal>
Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open fullscreen modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Do you want to save this?"
  closeLabel="Close modal"
  size="fullscreen"
>
  <Modal.Content>
    <BodyText>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id
      tortor tincidunt metus feugiat maximus.
    </BodyText>
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Cancel
    </Button>
  </Modal.Actions>
</Modal>

With descriptionPermalink to: With description

The Modal has an optional description situated between the header and Content.

Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Do you want to save this?"
  closeLabel="Close modal"
  description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
>
  <Modal.Content>
    <BodyText>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id
      tortor tincidunt metus feugiat maximus.
    </BodyText>
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Cancel
    </Button>
  </Modal.Actions>
</Modal>

Interactive contentPermalink to: Interactive content

To make interactive content - such as form elements - stand out, you can add a grey background to the Modal content.

Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { InputField } from "@peakon/bedrock/react/form";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Personal information"
  closeLabel="Close modal"
>
  <Modal.Content isInteractive>
    <InputField label="First name" />
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Close
    </Button>
  </Modal.Actions>
</Modal>

Fixed heightPermalink to: Fixed height

The default behavior for the Modal is to grow in height based on the content. For some Modals, eg. if content is expected to grow substantially, it works better if the height of the Modal is fixed. fixedHeight will set a fixed height, based on the size (See Sizes) of the Modal, as long as the viewport height allows it.

Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open small modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Do you want to save this?"
  closeLabel="Close modal"
  fixedHeight
>
  <Modal.Content>
    <BodyText>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id
      tortor tincidunt metus feugiat maximus.
    </BodyText>
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Cancel
    </Button>
  </Modal.Actions>
</Modal>
Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open medium modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Do you want to save this?"
  closeLabel="Close modal"
  size="medium"
  fixedHeight
>
  <Modal.Content>
    <BodyText>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id
      tortor tincidunt metus feugiat maximus.
    </BodyText>
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Cancel
    </Button>
  </Modal.Actions>
</Modal>
Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open large modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Do you want to save this?"
  closeLabel="Close modal"
  size="large"
  fixedHeight
>
  <Modal.Content>
    <BodyText>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id
      tortor tincidunt metus feugiat maximus.
    </BodyText>
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Cancel
    </Button>
  </Modal.Actions>
</Modal>

With TabsPermalink to: With Tabs

The Modal has built-in Tabs if you need to add several views.

Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open modal with tabs
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Information"
  closeLabel="Close modal"
>
  <Modal.TabList>
    <Modal.Tab name="tab1">Tab 1</Modal.Tab>
    <Modal.Tab name="tab2">Tab 2</Modal.Tab>
  </Modal.TabList>

  <Modal.Content>
    <Modal.TabPanel controlledBy="tab1">
      <BodyText>This is the first tab.</BodyText>
    </Modal.TabPanel>
    <Modal.TabPanel controlledBy="tab2">
      <BodyText>This is the second tab.</BodyText>
    </Modal.TabPanel>
  </Modal.Content>

  <Modal.Actions>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Close
    </Button>
  </Modal.Actions>
</Modal>

CompositionPermalink to: Composition

The composite nature of the Modal will let you extend the design by adding elements anywhere inside the container, eg. between the header and the content.

Open
import { useState } from "react";

import { Button } from "@peakon/bedrock/react/button";
import { Modal } from "@peakon/bedrock/react/dialog";
import { BodyText } from "@peakon/bedrock/react/typography";
import { Tag } from "@peakon/bedrock/react/tag";

const [isModalOpen, setIsModalOpen] = useState(false);

<Button variant="primary" onClick={() => setIsModalOpen(true)}>
  Open modal
</Button>
<Modal
  open={isModalOpen}
  onDismiss={() => setIsModalOpen(false)}
  heading="Do you want to save this?"
  closeLabel="Close modal"
>
  <div style={{ paddingInline: "16px", paddingBlockEnd: "12px" }}>
    <Tag label="Custom composition" variant="custom" />
  </div>
  <Modal.Content>
    <BodyText>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus id
      tortor tincidunt metus feugiat maximus.
    </BodyText>
  </Modal.Content>
  <Modal.Actions>
    <Button variant="primary" onClick={() => setIsModalOpen(false)}>
      Save
    </Button>
    <Button variant="secondary" onClick={() => setIsModalOpen(false)}>
      Cancel
    </Button>
  </Modal.Actions>
</Modal>

Props TablePermalink to: Props Table

Props extend from HTML Dialog Element(external link), with the omission of className and style

NameTypeDescriptionDefaultRequired
refHTMLDialogElement

Forwards a ref to the Modal.

No
headingstring

The heading for the Modal.

Yes
closeLabelstring

The label for the close button of the Modal (for assistive technology).

Yes
descriptionstring

An optional description for the Modal, underneath the header.

No
openboolean

Controls whether the Modal is open or not.

No
onDismiss() => void

Callback function for when the Modal is dismissed internally, e.g. with the escape key. Make sure to set it explicitly e.g. setIsOpen(false) and not toggle it e.g. setIsOpen(!isOpen) as this can lead to unexpected behavior.

No
onMountedChange(isMounted: boolean) => void

Callback function for when the Modal is mounted and unmounted.

No
sizesmall | medium | large | fullscreen

Defines the size of the Modal.

smallNo
fixedHeightboolean

Defines whether the Modal has a fixed height.

falseNo
defaultTabstring

For defining what Tab should be active by default when using uncontrolled Tabs.

No
activeTabstring

Defines the current active Tab. Used alongside onTabChange to make Tabs controlled. This should correspond one of the Tab elements by referencing its name.

Yes, when controlled

onTabChangefunction

For controlling the changing of Tabs and any side effects. Used alongside activeTab to make Tabs controlled.

Yes, when controlled


Modal.ContentPermalink to: Modal.Content

Props extend from HTML Div Element(external link), with the omission of className and style

NameTypeDescriptionDefaultRequired
isInteractivestring

Will give the content a grey background when true. Used when the content is interactive, eg. forms.

Yes
noPaddingboolean

The label for the close button of the Modal (for assistive technology).

Yes

Modal.ActionsPermalink to: Modal.Actions

Props extend from HTML Div Element(external link), with the omission of className and style


Modal.TabListPermalink to: Modal.TabList

Props extend from HTML Div Element(external link), with the omission of className, style, role, aria-labelledby and aria-label


Modal.TabPermalink to: Modal.Tab

Props extend from HTML Div Element(external link), with the omission of className, style, aria-controls, tabIndex and role

NameTypeDescriptionDefaultRequired
namestring

A reference for the Tab. Internally, a unique ID is built using this name that will be used to link to a corresponding TabPanel.

Yes

Modal.TabPanelPermalink to: Modal.TabPanel

Props extend from HTML Div Element(external link), with the omission of className and style

NameTypeDescriptionDefaultRequired
controlledBystring

Defines which Tab the TabPanel is controlled by, ie. which Tab will reveal the TabPanel contents when active. It must correspond to the name of a Tab (see Modal.Tab props table)

Yes