Usage
Use a Button or any other component in the default slot of the Modal.
Then, use the #content slot to add the content displayed when the Modal is open.
<template>
<B24Modal>
<B24Button label="Open" />
<template #content>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
You can also use the #header, #body and #footer slots to customize the Modal's content.
Title
Use the title prop to set the title of the Modal's header.
<template>
<B24Modal title="Modal with title">
<B24Button label="Open" />
<template #body>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
Description
Use the description prop to set the description of the Modal's header.
<template>
<B24Modal
title="Modal with description"
description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
>
<B24Button label="Open" />
<template #body>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
Close
Use the close prop to customize or hide the close button (with false value) displayed in the Modal's header.
You can pass any property from the Button component to customize it.
<template>
<B24Modal
title="Modal with close button"
:close="{
color: 'air-secondary-accent-2',
class: 'rounded-full'
}"
>
<B24Button label="Open" />
<template #body>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
#content slot is used as it's a part of the header.Close Icon
Use the close-icon prop to customize the close button Icon.
<script setup lang="ts">
import RocketIcon from '@bitrix24/b24icons-vue/main/RocketIcon'
</script>
<template>
<B24Modal title="Modal with close button" :close-icon="RocketIcon">
<B24Button label="Open" />
<template #body>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
Transition
Use the transition prop to control whether the Modal is animated or not. Defaults to true.
<template>
<B24Modal :transition="false" title="Modal without transition">
<B24Button label="Open" />
<template #body>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
Overlay
Use the overlay prop to control whether the Modal has an overlay or not. Defaults to true.
<template>
<B24Modal :overlay="false" title="Modal without overlay">
<B24Button label="Open" />
<template #body>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
Overlay blur
If you want to disable background blur, you should use the overlayBlur prop.
The overlayBlur prop has 3 options:
auto: (default) when the user has not requested reduced motionon: always use bluroff: do not use blur
<template>
<B24Modal overlay-blur="auto" title="Overlay blur">
<B24Button label="Open" />
<template #body>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
Modal
Use the modal prop to control whether the Modal blocks interaction with outside content. Defaults to true.
modal is set to false, the overlay is automatically disabled and outside content becomes interactive.<template>
<B24Modal :modal="false" title="Modal interactive">
<B24Button label="Open" />
<template #body>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
Dismissible
Use the dismissible prop to control whether the Modal is dismissible when clicking outside of it or pressing escape. Defaults to true.
close:prevent event will be emitted when the user tries to close it.modal: false with dismissible: false to make the Modal's background interactive without closing it.<template>
<B24Modal :dismissible="false" title="Modal non-dismissible">
<B24Button label="Open" />
<template #body>
<Placeholder class="h-48" />
</template>
</B24Modal>
</template>
Scrollable
Use the scrollable prop to make the Modal's content scrollable within the overlay.
modal: false is not compatible and overlay: false only removes the background.<template>
<B24Modal scrollable title="Modal scrollable">
<B24Button label="Open" />
<template #body>
<Placeholder class="h-full" />
</template>
</B24Modal>
</template>
Fullscreen
Use the fullscreen prop to make the Modal fullscreen.
<template>
<B24Modal fullscreen title="Modal fullscreen">
<B24Button label="Open" />
<template #body>
<Placeholder class="h-full" />
</template>
</B24Modal>
</template>
Examples
Control open state
You can control the open state by using the default-open prop or the v-model:open directive.
defineShortcuts, you can toggle the Modal by pressing O.Programmatic usage
You can use the useOverlay composable to open a Modal programmatically.
App component which uses the OverlayProvider component.First, create a modal component that will be opened programmatically:
close event when the modal is closed or dismissed here. You can emit any data through the close event, however, the event must be emitted in order to capture the return value.Then, use it in your app:
emit('close').Nested modals
You can nest modals within each other.
With footer slot
Use the #footer slot to add content after the Modal's body.
Marketing / promo composition Soon
Compose a promo/upgrade modal from existing components — no new component needed. The body uses a responsive 2-column flex (heading + description on the left, a feature card on the right), the footer pairs a primary CTA with a tertiary "remind me later" action. The decorative background is a Tailwind gradient utility passed through b24ui.body.
Build a marketing / promo modal for any business or app.
You are helping me build a marketing / promo modal in an app that uses @bitrix24/b24ui-nuxt. Load the b24-ui-nuxt skill and follow the Marketing / promo modal recipe in skills/b24-ui-nuxt/references/recipes/overlays.md.
Goal: a focused, single-purpose modal for any marketing activity — trial ending, plan upsell, seasonal offer, feature launch, re-engagement campaign — with one primary CTA and one dismiss action, that nudges the user without blocking the main workflow.
Before writing any code, ask me clarifying questions about:
- the activity itself — what is being promoted (offer, plan, feature, event), the value proposition in one sentence, and any quantified benefit worth highlighting;
- the audience — which user segment, role, or lifecycle stage the modal targets;
- the trigger and re-show cadence — page load, route guard, dismiss-aware banner, scheduled campaign window;
- the primary action — where the CTA leads (in-app flow, deep link, external page) and any permissions or feature flags required;
- the dismiss action — its copy, whether it just closes or snoozes for N days, and whether ESC / overlay click also dismiss;
- copy and locale — tone, language, and whether i18n keys are required from day one;
- analytics — which events to fire (
promo_shown,promo_dismissed,promo_clicked) and which user roles count; - constraints — accessibility, dark-mode preview, mobile viewport priority.
Once we agree on those, compose the modal from existing b24ui components only (do not introduce a new component), paint the brand boost gradient on b24ui.content, pin the CTA row to the bottom of the body, and keep any side card as a short teaser of headline benefits — not a feature matrix.
Sales dynamics widget
A real-world example of surfacing a stats card behind a Modal. The widget itself is documented on the Card page; here it sits inside B24Modal so a trigger button opens it as a focused overlay — handy when the page chrome is busy and the dashboard needs a peek surface without a navigation jump.
Wrap the Sales dynamics widget in a Modal so a trigger button opens it as a focused overlay.
Take the Sales dynamics widget recipe (see references/recipes/overlays.md → "Stats widget (KPI summary)") and surface it through B24Modal so a button or in-page link opens the metrics as a focused overlay — useful when the surrounding dashboard is too busy to host the full card inline.
Before writing any code, gather the missing context:
- What's the trigger — a
B24Button(discrete control), aB24Link is-actionmid-paragraph, or a programmatic open viauseOverlay? - Should the modal be dismissible by ESC / overlay click, or only via the widget's own actions (in which case
:dismissible="false")? - Does the modal need to block the page (default
modal: true) or float as a non-modal overlay (modal: false) so the user can still scroll the dashboard behind it? - Should the widget's own
Configure/Feedbackbuttons close the modal, navigate away, or stay open while triggering side-effects? - What's the data shape behind the rows (and the optional highlight row), and where does it come from — props on the page, a store, or an async fetch fired only when the modal opens?
- Locale and analytics: track modal opens and CTA clicks separately, since this is a "peek" surface, not the canonical metrics page.
Once those answers are in, assemble the modal from stock components only:
- Strip the modal's content chrome with
:b24ui="{ content: 'sm:max-w-md bg-transparent shadow-none border-0', body: 'p-0' }"so the widget paints its own surface (it carries the gradient, rounded corners and shadow internally). - Disable the built-in close (
:close="false") so the X doesn't clash with the widget's header — the modal is dismissed by ESC, overlay click, or one of the widget's footer buttons. - Place the trigger in the default slot (
B24ButtonorB24Link is-action— never both in the same modal) and put the widget itself inside#body. - Render the widget exactly as documented in the recipe — the
edge-darkroot, the purple radial gradient (--ui-color-copilot-bg-content-3 → -2 → -1), thestyle-filled-boosthighlight row and the threeair-secondary-accentaction buttons.
Keep all copy in the requested locale and surface the data through the same shape (title, totalLine, todayLine, rangeLabel, rows, highlight) used by the standalone recipe.
With command palette
You can use a CommandPalette component inside the Modal's content.
useLazyFetch with immediate: false to only fetch data when the Modal opens.API
Props
Slots
Emits
Theme
export default {
slots: {
overlay: 'fixed inset-0',
content: 'base-mode bg-(--ui-color-bg-content-primary) flex flex-col gap-0 focus:outline-none',
contentWrapper: 'flex-1 flex flex-col gap-0',
header: 'px-4 py-4 sm:px-6 flex items-stretch justify-between gap-1.5 min-h-(--b24ui-header-height)',
wrapper: 'min-h-full flex-1 flex flex-col items-start justify-center gap-0',
body: 'px-4 sm:px-6 flex-1 text-(length:--ui-font-size-md) leading-normal text-(--ui-color-base-2) text-(length:--ui-font-size-md)',
footer: 'px-4 py-4 sm:px-6 sm:py-6 flex items-center justify-between gap-3',
title: 'font-[family-name:var(--ui-font-family-primary)] text-(--ui-color-base-1) font-(--ui-font-weight-bold) mb-0 text-[calc(var(--ui-font-size-2xl)+1px)]/(--ui-font-size-2xl) wrap-break-word',
description: 'text-description text-(length:--ui-font-size-sm) wrap-break-word',
close: '[--ui-btn-height:24px]'
},
variants: {
overlayBlur: {
auto: {
overlay: 'motion-safe:backdrop-blur-0.5'
},
on: {
overlay: 'backdrop-blur-0.5'
},
off: {
overlay: ''
}
},
transition: {
true: {
overlay: 'motion-safe:data-[state=open]:animate-[fade-in_200ms_ease-out] motion-safe:data-[state=closed]:animate-[fade-out_200ms_ease-in]',
content: 'motion-safe:data-[state=open]:animate-[scale-in_200ms_ease-out] motion-safe:data-[state=closed]:animate-[scale-out_200ms_ease-in]'
}
},
fullscreen: {
true: {
content: 'inset-0',
body: 'py-4'
},
false: {
content: 'w-[calc(100vw-2rem)] max-w-[32rem] rounded-[calc(var(--ui-border-radius-2xl)+2px)] shadow-lg',
contentWrapper: ''
}
},
overlay: {
true: {
overlay: 'bg-[#003366]/20'
}
},
scrollable: {
true: {
overlay: 'overflow-y-auto',
content: 'relative'
},
false: {
content: 'fixed',
body: 'overflow-y-auto'
}
},
scrollbarThin: {
true: {
body: 'scrollbar-thin'
}
}
},
compoundVariants: [
{
scrollable: true,
fullscreen: false,
class: {
overlay: 'grid place-items-center p-4 sm:py-8'
}
},
{
scrollable: false,
fullscreen: false,
class: {
content: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-4rem)]',
contentWrapper: 'overflow-hidden'
}
},
{
scrollable: false,
fullscreen: true,
class: {
content: 'max-h-[calc(100dvh)]',
contentWrapper: 'overflow-hidden'
}
}
],
defaultVariants: {
scrollbarThin: true,
overlayBlur: 'auto'
}
}