Skip to content

Modal

Organism

Modals are overlay components used for tasks or decisions that require user focus and confirmation. They block access to the main interface and should be used only for critical actions or information that cannot appear inline. When a modal appears, a background overlay using black at 25% opacity (RGBA 0,0,0,0.25) is applied.

ModelValues

For more information on ModelValues please have a look to the Vue's Data Binding section.

ts
const modelValue = defineModel<boolean>('modelValue', {
  required: true,
  default: false,
});

Properties

ts
export interface ModalProps {
  title?: string;
  closeOnClick?: boolean;
  size?: 'narrow' | 'wide';
  closeButton?: boolean;
  headingIcon?: IconName;
  headerSize?: 'default' | 'tiny';
  content?: string | (() => VNode) | (() => VNode[]);
  infoText?: string;
  actions?: (AppButtonProps & {
    component?: 'AppButton',
    text: string;
    onClick: () => void;
  } | Omit<LinkProps, 'to'> & {
    component: 'Link',
    to?: string;
    text: string;
    onClick: () => void;
  })[];
}

Developer Notes

NOTE

Submit Event (@submit): Triggered when user clicks the default submit button or any other button who's onClick function calls the submit component method.

Usage Example

In general there are two different ways to use the Modal component. You have the option to define the entire modal in HTML only which is suitable for cases in which you only have few modals with static content inside of one component.

In those cases you pass everything via regular Vue props. This will demonstrate the actual implementation of this playground Modal.

vue
<script lang="ts" setup>
const modalValue = ref(false);
</script>

<template>
<Modal
  v-model="modelValue"
  :actions="[
    {
      text: 'Link',
      component: 'Link' as const,
      type: 'primary' as NonNullable<LinkProps['type']>,
      onClick: () => {
        console.log('Cancel');
        modalRef.value?.cancel();
      },
    },
    {
      text: 'Secondary',
      secondary: true,
      icon: 'close' as IconName,
      onClick: () => {
        console.log('Close');
        modalRef.value?.cancel();
      },
    },
    {
      text: 'Primary',
      icon: 'send' as IconName,
      trailingIcon: true,
      onClick: () => {
        console.log('Send');
        modalRef.value?.submit();
      },
    },
  ]"
  // in the playground the icon is set dynamically based on your actual selection
  :headingIcon="icon"
>
Here goes your main content of the Modal including everything you can render with native HTML
Including Forms, Inputs, Icons, ...

</Modal>
</template>

Alternatively, if you need various Modals and/or need to pass content dynamically for another reason during runtime, it is recommended to use the built-in code control via componentRef. When doing so, it is strongly recommended that you define your actions to include one of the exposed util functions cancel or submit. For purposes that demand for dynamic and complex content rendering, vue's render function often is the best choice. Alternately you can also pass a simple string as content, if that is sufficient for your purpose.

A more complex example might look like this.

vue
<script lang="ts" setup>
import { h } from 'vue';

const modelValue = ref(false);
const userName = ref('');

const modalRef = useTemplateRef<InstanceType<typeof Modal>>('modal');

const showModal = () => {
  modalRef.value?.showModal({
    title: 'Opened via code',
    size: 'wide',
    content: () => [
      h(TextInput, {
        label: 'User Name',
        placeholder: 'Enter your user name',
        modelValue: userName.value,
        'onUpdate:modelValue': (value: string) => {
          userName.value = value;
        },
      }),
      h('div', { class: 'modal__content__user-name', style: { color: 'red' } }, userName.value)
    ],
    closeButton: true,
    headingIcon: 'ai' as IconName,
    actions: [
      {
        text: 'Cancel',
        component: 'AppButton',
        secondary: true,
        onClick: () => {
          console.log('Cancel');
          modalRef.value?.cancel();
        },
      },
      {
        text: 'Submit',
        component: 'AppButton',
        onClick: () => {
          console.log('Submit');
          modalRef.value?.submit();
        },
      },
    ]
  });
};
</script>

<template>
  <Modal ref="modal" v-model="modelValue" />
</template>

You are surely free to combine those approaches, too. You could, for example, define different contents inside the default slot with conditional rendering based on different rendering modes.

vue
<script setup lang="ts">

const renderingModes = ['form', 'dialog', 'delete-user'];
const activeMode = ref('form');

// ref setup like demonstrated in previous example
const showModal = (mode: string) => {
  activeMode = mode;

  modalRef.value?.showModal({
    title: 'Opened via code',
    size: 'wide',
    closeButton: true,
    headingIcon: 'ai' as IconName,
    actions: [
      {
        text: 'Cancel',
        component: 'AppButton',
        secondary: true,
        onClick: () => {
          console.log('Cancel');
          modalRef.value?.cancel();
        },
      },
      {
        text: 'Submit',
        component: 'AppButton',
        onClick: () => {
          console.log('Submit');
          modalRef.value?.submit();
        },
      },
    ]
  })
}
</script>

<template>
<Modal v-model="openModal">
  <div v-if="activeMode === 'form'">
    <TextInput v-model ... />
    ...
    <NumberInput ... />
  </div>

  <div v-if="activeMode === 'delete-user'">
    <Logo :company="'pohlcon'" />
    ...
  </div>
</Modal>

Tokens

SCSS Variable
Value
$bds-color-bg-brand
--color-lila-95
$bds-color-bg-button-primary
--color-lila-30
$bds-color-bg-button-primary--hover
--color-lila-20
$bds-color-bg-button-primary--locked
--color-lila-80
$bds-color-bg-button-primary--pressed
--color-lila-54
$bds-color-bg-button-secondary
--color-grey-93
$bds-color-bg-button-secondary--hover
--color-grey-90
$bds-color-bg-button-secondary--locked
--color-grey-95
$bds-color-bg-button-secondary--pressed
--color-grey-98
$bds-color-bg-button-tertiary
--color-transparent-0
$bds-color-bg-button-tertiary--hover
--color-grey-93
$bds-color-bg-button-tertiary--locked
--color-transparent-0
$bds-color-bg-button-tertiary--pressed
--color-grey-95
$bds-color-bg-error
--color-red-95
$bds-color-bg-generic
--color-grey-95
$bds-color-bg-info
--color-blue-95
$bds-color-bg-success
--color-green-95
$bds-color-bg-warning
--color-ochre-95
$bds-color-icon-brand
--color-lila-30
$bds-color-icon-button-primary
--color-grey-98
$bds-color-icon-button-primary--locked
--color-grey-98
$bds-color-icon-button-secondary
--color-lila-30
$bds-color-icon-button-secondary--locked
--color-lila-70
$bds-color-icon-button-tertiary-1
--color-lila-30
$bds-color-icon-button-tertiary-1--locked
--color-lila-70
$bds-color-icon-button-tertiary-2
--color-grey-40
$bds-color-icon-button-tertiary-2--locked
--color-grey-70
$bds-color-icon-error
--color-red-50
$bds-color-icon-generic
--color-grey-40
$bds-color-icon-info
--color-blue-45
$bds-color-icon-success
--color-green-50
$bds-color-icon-warning
--color-ochre-45
$bds-color-outline-brand
--color-lila-70
$bds-color-outline-button-secondary
--color-grey-90
$bds-color-outline-button-secondary--hover
--color-grey-86
$bds-color-outline-button-secondary--locked
--color-grey-93
$bds-color-outline-button-secondary--pressed
--color-grey-90
$bds-color-outline-error
--color-red-70
$bds-color-outline---focus
--color-azure-45
$bds-color-outline-generic
--color-grey-70
$bds-color-outline-info
--color-blue-67
$bds-color-outline-success
--color-green-75
$bds-color-outline-warning
--color-ochre-70
$bds-color-text-accent
--color-lila-30
$bds-color-text-brand
--color-lila-30
$bds-color-text-button-primary
--color-grey-98
$bds-color-text-button-primary--locked
--color-grey-98
$bds-color-text-button-secondary
--color-lila-30
$bds-color-text-button-secondary--locked
--color-lila-70
$bds-color-text-button-tertiary-1
--color-lila-30
$bds-color-text-button-tertiary-1--locked
--color-lila-70
$bds-color-text-button-tertiary-2
--color-grey-40
$bds-color-text-button-tertiary-2--locked
--color-grey-70
$bds-color-text-error
--color-red-50
$bds-color-text-generic
--color-grey-40
$bds-color-text-info
--color-blue-45
$bds-color-text-inverse
--color-grey-98
$bds-color-text-primary
--color-grey-25
$bds-color-text-secondary
--color-grey-40
$bds-color-text-success
--color-green-50
$bds-color-text-tertiary
--color-grey-54
$bds-color-text-warning
--color-ochre-45
$
export { modal-color
$modal-borderColor
--color-grey-90
$modal-color-text
--color-grey-25
$modal-color-text-locked
--color-grey-70

Anatomy

  1. Icon - Optional leading status icon that communicates context (info/success/warning/error).
  2. Heading - Concise, action-oriented title describing the purpose of the modal. Single line preferred.
  3. Icon Button (Close) - Dismiss control in the top-right corner.
  4. Slot (content area) - Flexible body region for any content. Adapts to content and becomes scrollable when height exceeds max (80vh).
  5. Footer with Buttons — Primary, Secondary, Tertiary (link).
Variants
Content

Use the slot property to inject content into a Modal without detaching. Simply turn the card content into a local component and swap it out with the slot.

Size
  • Modals are available in two fixed widths: 400 px (narrow) and 600 px (wide)
  • The modal’s height automatically adjusts to its content, up to a maximum of 80vh
  • When the content exceeds this height, the modal body becomes scrollable to ensure accessibility and usability on all screen sizes.
Heading Icon

Show or hide a heading Icon

Buttons
  • Controls which action buttons to be rendered in the modal footer.
  • The footer can display a primary button, an optional secondary button, and an optional link.
  • Each of these buttons can be shown or hidden depending on the use case.
Close Button

Show or hide a close button