Modal

An overlay that appears in front of all other content, and requires a user to take an action before continuing.

Props

heading
string
The heading text displayed at the top of the modal.
closable
boolean
Show close icon and allow clicking the background to close the modal.
Defaults to false.
open
boolean
Controls if modal is visible or not.
Defaults to false.
transition
fast | slow | none
Sets the animation transition when opening/closing. 'fast' or 'slow' for animated, 'none' for instant.
Defaults to none.
calloutVariant
""
Define the context and colour of the callout modal. It is required when type is set to callout.
maxWidth
string
Set the max allowed width of the modal.
Defaults to 60ch.
testId
string
Sets a data-testid attribute for automated testing.
Defaults to modal.
width
string
Use maxwidth instead.

Events

onClose
(event: Event) => void
_close
CustomEvent

Slots

heading
Named slot for content
content
Named slot for content
actions
Named slot for content
Examples

Add another item in a modal

const [open, setOpen] = useState(false);
  const [type, setType] = useState<string>();
  const [name, setName] = useState<string>();
  const [description, setDescription] = useState<string>();
<GoabButton type="tertiary" leadingIcon="add" onClick={() => setOpen(true)}>
        Add another item
      </GoabButton>
      <GoabModal
          heading="Add a new item"
          open={open}
          actions={
            <GoabButtonGroup alignment="end">
              <GoabButton type="tertiary" size="compact" onClick={() => setOpen(false)}>
                Cancel
              </GoabButton>
              <GoabButton type="primary" size="compact" onClick={() => setOpen(false)}>
                Save new item
              </GoabButton>
            </GoabButtonGroup>
          }
        >
          <p>Fill in the information to create a new item</p>
          <GoabFormItem label="Type" mt="l">
            <GoabDropdown onChange={(e) => setType(e.value)} value={type}>
              <GoabDropdownItem value="1" label="Option 1" />
              <GoabDropdownItem value="2" label="Option 2" />
            </GoabDropdown>
          </GoabFormItem>
          <GoabFormItem label="Name" mt="l">
            <GoabInput
              onChange={(e) => setName(e.value)}
              value={name}
              name="name"
              width="100%"
            />
          </GoabFormItem>
          <GoabFormItem label="Description" mt="l">
            <GoabTextArea
              name="description"
              rows={3}
              width="100%"
              onChange={(e) => setDescription(e.value)}
              value={description}
            />
          </GoabFormItem>
      </GoabModal>
open = false;
  type: string | undefined = "";
  name = "";
  description = "";

  toggleModal() {
    this.open = !this.open;
  }

  updateType(event: any) {
    this.type = event.value;
  }

  updateName(event: any) {
    this.name = event.value;
  }

  updateDescription(event: any) {
    this.description = event.value;
  }
<goab-button type="tertiary" leadingIcon="add" (onClick)="toggleModal()">Add another item</goab-button>
<goab-modal [open]="open" (onClose)="toggleModal()" heading="Add a new item" [actions]="actions">
  <p>Fill in the information to create a new item</p>
  <goab-form-item label="Type" mt="l">
    <goab-dropdown (onChange)="updateType($event)" [value]="type">
      <goab-dropdown-item value="1" label="Option 1"></goab-dropdown-item>
      <goab-dropdown-item value="2" label="Option 2"></goab-dropdown-item>
    </goab-dropdown>
  </goab-form-item>
  <goab-form-item label="Name" mt="l">
    <goab-input name="name" width="100%" (onChange)="updateName($event)" [value]="name"></goab-input>
  </goab-form-item>
  <goab-form-item label="Description" mt="l">
    <goab-textarea name="description" width="100%" [rows]="3" (onChange)="updateDescription($event)" [value]="description"></goab-textarea>
  </goab-form-item>
  <ng-template #actions>
    <goab-button-group alignment="end">
      <goab-button type="tertiary" size="compact" (onClick)="toggleModal()">Cancel</goab-button>
      <goab-button type="primary" size="compact" (onClick)="toggleModal()">Save new item</goab-button>
    </goab-button-group>
  </ng-template>
</goab-modal>
const modal = document.getElementById("add-item-modal");
const openBtn = document.getElementById("open-modal-btn");
const cancelBtn = document.getElementById("cancel-btn");
const saveBtn = document.getElementById("save-btn");

openBtn.addEventListener("_click", () => {
  modal.setAttribute("open", "true");
});

modal.addEventListener("_close", () => {
  modal.removeAttribute("open");
});

cancelBtn.addEventListener("_click", () => {
  modal.removeAttribute("open");
});

saveBtn.addEventListener("_click", () => {
  modal.removeAttribute("open");
});
<goa-button version="2" id="open-modal-btn" type="tertiary" leadingicon="add">Add another item</goa-button>
<goa-modal version="2" id="add-item-modal" heading="Add a new item">
  <p>Fill in the information to create a new item</p>
  <goa-form-item version="2" label="Type" mt="l">
    <goa-dropdown version="2" id="type-dropdown">
      <goa-dropdown-item value="1" label="Option 1"></goa-dropdown-item>
      <goa-dropdown-item value="2" label="Option 2"></goa-dropdown-item>
    </goa-dropdown>
  </goa-form-item>
  <goa-form-item version="2" label="Name" mt="l">
    <goa-input version="2" name="name" width="100%" id="name-input"></goa-input>
  </goa-form-item>
  <goa-form-item version="2" label="Description" mt="l">
    <goa-textarea version="2" name="description" width="100%" rows="3" id="description-textarea"></goa-textarea>
  </goa-form-item>
  <div slot="actions">
    <goa-button-group alignment="end">
      <goa-button version="2" id="cancel-btn" type="tertiary" size="compact">Cancel</goa-button>
      <goa-button version="2" id="save-btn" type="primary" size="compact">Save new item</goa-button>
    </goa-button-group>
  </div>
</goa-modal>

Confirm a change

const [open, setOpen] = useState(false);
  const [effectiveDate, setEffectiveDate] = useState<Date | undefined>(new Date());

  const onChangeEffectiveDate = (detail: GoabDatePickerOnChangeDetail) => {
    setEffectiveDate(detail.value as Date);
  };
<GoabButton onClick={() => setOpen(true)}>Save and continue</GoabButton>

      <GoabModal
        heading="Address has changed"
        open={open}
        onClose={() => setOpen(false)}
        actions={
          <GoabButtonGroup alignment="end">
            <GoabButton type="secondary" size="compact" onClick={() => setOpen(false)}>
              Undo address change
            </GoabButton>
            <GoabButton type="primary" size="compact" onClick={() => setOpen(false)}>
              Confirm
            </GoabButton>
          </GoabButtonGroup>
        }>
        <GoabContainer type="non-interactive" accent="filled" padding="compact" width="full">
          <GoabText as="h4" mt="none" mb="s">Before</GoabText>
          <GoabText mt="none">123456 78 Ave NW, Edmonton, Alberta</GoabText>
          <GoabText as="h4" mt="none" mb="s">After</GoabText>
          <GoabText mt="none" mb="none">881 12 Ave NW, Edmonton, Alberta</GoabText>
        </GoabContainer>
        <GoabFormItem label="Effective date" mt="l">
          <GoabDatePicker
            onChange={onChangeEffectiveDate}
            name="effectiveDate"
            value={effectiveDate}
          />
        </GoabFormItem>
      </GoabModal>
open = false;
  effectiveDate = new Date();

  toggleModal(): void {
    this.open = !this.open;
  }

  onChangeEffectiveDate(event: GoabDatePickerOnChangeDetail): void {
    this.effectiveDate = event.value as Date;
  }
<goab-button (onClick)="toggleModal()">Save and continue</goab-button>

<goab-modal [open]="open" (onClose)="toggleModal()" heading="Address has changed" [actions]="actions">
  <goab-container type="non-interactive" accent="filled" padding="compact" width="full">
    <goab-text as="h4" mt="none" mb="s">Before</goab-text>
    <goab-text mt="none">123456 78 Ave NW, Edmonton, Alberta</goab-text>
    <goab-text as="h4" mt="none" mb="s">After</goab-text>
    <goab-text mt="none" mb="none">881 12 Ave NW, Edmonton, Alberta</goab-text>
  </goab-container>
  <goab-form-item label="Effective date" mt="l">
    <goab-date-picker (onChange)="onChangeEffectiveDate($event)" name="effectiveDate" [value]="effectiveDate"></goab-date-picker>
  </goab-form-item>
  <ng-template #actions>
    <goab-button-group alignment="end">
      <goab-button type="secondary" size="compact" (onClick)="toggleModal()">
        Undo address change
      </goab-button>
      <goab-button type="primary" size="compact" (onClick)="toggleModal()">
        Confirm
      </goab-button>
    </goab-button-group>
  </ng-template>
</goab-modal>
const modal = document.getElementById('change-modal');
const openBtn = document.getElementById('open-modal-btn');
const undoBtn = document.getElementById('undo-btn');
const confirmBtn = document.getElementById('confirm-btn');
const datePicker = document.getElementById('effective-date');

let effectiveDate = new Date();
datePicker.setAttribute('value', effectiveDate.toISOString());

openBtn.addEventListener('_click', () => {
  modal.setAttribute('open', 'true');
});

undoBtn.addEventListener('_click', () => {
  modal.removeAttribute('open');
});

confirmBtn.addEventListener('_click', () => {
  modal.removeAttribute('open');
});

modal.addEventListener('_close', () => {
  modal.removeAttribute('open');
});

datePicker.addEventListener('_change', (e) => {
  effectiveDate = e.detail.value;
});
<goa-button version="2" id="open-modal-btn">Save and continue</goa-button>

<goa-modal version="2" id="change-modal" heading="Address has changed">
  <goa-container type="non-interactive" accent="filled" padding="compact" width="full">
    <goa-text as="h4" mt="none" mb="s">Before</goa-text>
    <goa-text mt="none">123456 78 Ave NW, Edmonton, Alberta</goa-text>
    <goa-text as="h4" mt="none" mb="s">After</goa-text>
    <goa-text mt="none" mb="none">881 12 Ave NW, Edmonton, Alberta</goa-text>
  </goa-container>
  <goa-form-item version="2" label="Effective date" mt="l">
    <goa-date-picker version="2" id="effective-date" name="effectiveDate"></goa-date-picker>
  </goa-form-item>
  <div slot="actions">
    <goa-button-group alignment="end">
      <goa-button version="2" type="secondary" size="compact" id="undo-btn">
        Undo address change
      </goa-button>
      <goa-button version="2" type="primary" size="compact" id="confirm-btn">
        Confirm
      </goa-button>
    </goa-button-group>
  </div>
</goa-modal>

Confirm a destructive action

const [open, setOpen] = useState(false);
<GoabButton
        type="tertiary"
        leadingIcon="trash"
        onClick={() => setOpen(true)}>
        Delete record
      </GoabButton>
      <GoabModal
        heading="Are you sure you want to delete this record?"
        open={open}
        onClose={() => setOpen(false)}
        actions={
          <GoabButtonGroup alignment="end">
            <GoabButton type="tertiary" size="compact" onClick={() => setOpen(false)}>
              Cancel
            </GoabButton>
            <GoabButton
              type="primary"
              variant="destructive"
              size="compact"
              onClick={() => setOpen(false)}>
              Delete record
            </GoabButton>
          </GoabButtonGroup>
        }>
        <p>This action cannot be undone.</p>
      </GoabModal>
open = false;

  toggleModal(): void {
    this.open = !this.open;
  }
<goab-button type="tertiary" leadingIcon="trash" (onClick)="toggleModal()">Delete record</goab-button>
<goab-modal [open]="open" (onClose)="toggleModal()" heading="Are you sure you want to delete this record?" [actions]="actions">
  <p>This action cannot be undone.</p>
  <ng-template #actions>
    <goab-button-group alignment="end">
      <goab-button type="tertiary" size="compact" (onClick)="toggleModal()">Cancel</goab-button>
      <goab-button type="primary" variant="destructive" size="compact" (onClick)="toggleModal()">Delete record</goab-button>
    </goab-button-group>
  </ng-template>
</goab-modal>
const modal = document.getElementById('delete-modal');
const deleteBtn = document.getElementById('delete-btn');
const cancelBtn = document.getElementById('cancel-btn');
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');

deleteBtn.addEventListener('_click', () => {
  modal.setAttribute('open', 'true');
});

cancelBtn.addEventListener('_click', () => {
  modal.removeAttribute('open');
});

confirmDeleteBtn.addEventListener('_click', () => {
  modal.removeAttribute('open');
});

modal.addEventListener('_close', () => {
  modal.removeAttribute('open');
});
<goa-button version="2" type="tertiary" leadingicon="trash" id="delete-btn">Delete record</goa-button>
<goa-modal version="2" id="delete-modal" heading="Are you sure you want to delete this record?">
  <p>This action cannot be undone.</p>
  <div slot="actions">
    <goa-button-group alignment="end">
      <goa-button version="2" type="tertiary" size="compact" id="cancel-btn">Cancel</goa-button>
      <goa-button version="2" type="primary" variant="destructive" size="compact" id="confirm-delete-btn">Delete record</goa-button>
    </goa-button-group>
  </div>
</goa-modal>

Confirm before navigating away

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

  const handleChangeRoute = () => {
    setOpen(false);
    // In a real app, you would use your router's navigate function
    // setTimeout(() => navigate("/some-path"), 300);
    console.log("Navigating to new route...");
  };
<GoabButton onClick={() => setOpen(true)}>Open</GoabButton>
      <GoabModal
        heading="Are you sure you want to change route?"
        open={open}
        onClose={() => setOpen(false)}
        actions={
          <GoabButtonGroup alignment="end">
            <GoabButton type="secondary" size="compact" onClick={() => setOpen(false)}>
              Cancel
            </GoabButton>
            <GoabButton type="primary" size="compact" onClick={handleChangeRoute}>
              Change route
            </GoabButton>
          </GoabButtonGroup>
        }
      />
open = false;

  constructor(private router: Router) {}

  onOpen(): void {
    this.open = true;
  }

  onClose(): void {
    this.open = false;
  }

  onChangeRoute(): void {
    this.open = false;
    // setTimeout will allow any modal transitions to be run
    setTimeout(() => this.router.navigate(["/components"]), 0);
  }
<goab-button (onClick)="onOpen()">Open</goab-button>
<goab-modal [open]="open" heading="Are you sure you want to change route?" [actions]="actions">
  <ng-template #actions>
    <goab-button-group alignment="end">
      <goab-button type="secondary" size="compact" (onClick)="onClose()">Cancel</goab-button>
      <goab-button type="primary" size="compact" (onClick)="onChangeRoute()">Change route</goab-button>
    </goab-button-group>
  </ng-template>
</goab-modal>
const modal = document.getElementById('route-modal');
const openBtn = document.getElementById('open-btn');
const cancelBtn = document.getElementById('cancel-btn');
const changeRouteBtn = document.getElementById('change-route-btn');

openBtn.addEventListener('_click', () => {
  modal.setAttribute('open', 'true');
});

cancelBtn.addEventListener('_click', () => {
  modal.removeAttribute('open');
});

changeRouteBtn.addEventListener('_click', () => {
  modal.removeAttribute('open');
  // setTimeout will allow any modal transitions to be run
  setTimeout(() => {
    window.location.href = '/components';
  }, 300);
});

modal.addEventListener('_close', () => {
  modal.removeAttribute('open');
});
<goa-button version="2" id="open-btn">Open</goa-button>
<goa-modal version="2" id="route-modal" heading="Are you sure you want to change route?">
  <div slot="actions">
    <goa-button-group alignment="end">
      <goa-button version="2" type="secondary" size="compact" id="cancel-btn">Cancel</goa-button>
      <goa-button version="2" type="primary" size="compact" id="change-route-btn">Change route</goa-button>
    </goa-button-group>
  </div>
</goa-modal>

Require user action before continuing

const [open, setOpen] = useState(false);
<GoabButton onClick={() => setOpen(true)}>Open Basic Modal</GoabButton>
      <GoabModal
        heading="Are you sure you want to continue?"
        open={open}
        onClose={() => setOpen(false)}
        actions={
          <GoabButtonGroup alignment="end">
            <GoabButton type="secondary" size="compact" onClick={() => setOpen(false)}>
              Back
            </GoabButton>
            <GoabButton type="primary" size="compact" onClick={() => setOpen(false)}>
              Continue
            </GoabButton>
          </GoabButtonGroup>
        }
      >
        <p>You cannot return to this page.</p>
      </GoabModal>
open = false;

  toggleModal(): void {
    this.open = !this.open;
  }
<goab-button (onClick)="toggleModal()">Open Basic Modal</goab-button>
<goab-modal
  [open]="open"
  (onClose)="toggleModal()"
  heading="Are you sure you want to continue?"
  [actions]="actions">
  <p>You cannot return to this page.</p>
  <ng-template #actions>
    <goab-button-group alignment="end">
      <goab-button type="secondary" size="compact" (onClick)="toggleModal()">Back</goab-button>
      <goab-button type="primary" size="compact" (onClick)="toggleModal()">Continue</goab-button>
    </goab-button-group>
  </ng-template>
</goab-modal>
const modal = document.getElementById("confirmation-modal");
const openBtn = document.getElementById("open-modal-btn");
const backBtn = document.getElementById("back-btn");
const continueBtn = document.getElementById("continue-btn");

function openModal() {
  modal.setAttribute("open", "true");
}

function closeModal() {
  modal.removeAttribute("open");
}

openBtn.addEventListener("_click", openModal);
backBtn.addEventListener("_click", closeModal);
continueBtn.addEventListener("_click", closeModal);
modal.addEventListener("_close", closeModal);
<goa-button version="2" id="open-modal-btn">Open Basic Modal</goa-button>
<goa-modal version="2" id="confirmation-modal" heading="Are you sure you want to continue?">
  <p>You cannot return to this page.</p>
  <div slot="actions">
    <goa-button-group alignment="end">
      <goa-button version="2" id="back-btn" type="secondary" size="compact">Back</goa-button>
      <goa-button version="2" id="continue-btn" type="primary" size="compact">Continue</goa-button>
    </goa-button-group>
  </div>
</goa-modal>

Warn a user of a deadline

const [open, setOpen] = useState(false);
<GoabButton type="secondary" onClick={() => setOpen(true)}>
        Save for later
      </GoabButton>
      <GoabModal
        heading="Complete submission prior to 1PM"
        calloutVariant="important"
        open={open}
        onClose={() => setOpen(false)}
        actions={
          <GoabButtonGroup alignment="end">
            <GoabButton type="primary" onClick={() => setOpen(false)}>
              I understand
            </GoabButton>
          </GoabButtonGroup>
        }
      >
        <p>
          You've selected to adjourn a matter that is required to appear today. This Calgary court
          location does not accept adjournment requests past 1PM MST. Please submit your
          adjournment request as soon as possible.
        </p>
      </GoabModal>
  );
}
open = false;

  toggleModal(): void {
    this.open = !this.open;
  }
<goab-button type="secondary" (onClick)="toggleModal()">Save for later</goab-button>
<goab-modal
  [open]="open"
  calloutVariant="important"
  (onClose)="toggleModal()"
  heading="Complete submission prior to 1PM"
  [actions]="actions">
  <p>
    You've selected to adjourn a matter that is required to appear today. This Calgary court
    location does not accept adjournment requests past 1PM MST. Please submit your adjournment
    request as soon as possible.
  </p>
  <ng-template #actions>
    <goab-button-group alignment="end">
      <goab-button type="primary" (onClick)="toggleModal()">I understand</goab-button>
    </goab-button-group>
  </ng-template>
</goab-modal>
const modal = document.getElementById("deadline-modal");
const openBtn = document.getElementById("open-modal-btn");
const understandBtn = document.getElementById("understand-btn");

openBtn.addEventListener("_click", () => {
  modal.setAttribute("open", "true");
});

understandBtn.addEventListener("_click", () => {
  modal.removeAttribute("open");
});

modal.addEventListener("_close", () => {
  modal.removeAttribute("open");
});
<goa-button version="2" type="secondary" id="open-modal-btn">Save for later</goa-button>
<goa-modal version="2"
  id="deadline-modal"
  calloutvariant="important"
  heading="Complete submission prior to 1PM">
  You've selected to adjourn a matter that is required to appear today. This Calgary court
  location does not accept adjournment requests past 1PM MST. Please submit your adjournment
  request as soon as possible.
  <div slot="actions">
    <goa-button-group alignment="end">
      <goa-button version="2" type="primary" size="compact" id="understand-btn">I understand</goa-button>
    </goa-button-group>
  </div>
</goa-modal>

Content

Cancel application
Use descriptive language in both modal content and button text to inform users of the resulting destructive action.

Submitting the assessment will inform the proponent and email a copy of the report.

CancelSubmit
Use a concise and descriptive modal title that spans less than one line.

Are you sure that you want to cancel your application? This action will permanently cancel your application and delete any information collected.

BackCancel application
Use descriptive language in content and button text for destructive actions.

Types

Delete account
Use the destructive button variant for actions that cannot be easily undone, like permanently deleting data or removing a user from a system.
Delete record
Don't use a destructive button to trigger a confirmation. Reserve destructive styling for the final action inside the modal.
All GoA Design System components are built to meet WCAG 2.2 AA standards. The following guidelines provide additional context for accessible implementation.

No accessibility-specific guidelines have been documented for this component yet.

View old component docs