Skip to main content
Components
Modal
pnpm add @winkintel/bootstrap-svelte
GitHub

Modal

Add dialogs to your site for lightboxes, user notifications, or completely custom content.


Interactive Playground

Controls

Event Log

  • No events logged

Preview

Code

            
            <script>
  let isShown = $state(false);
  
  function toggleModal() {
    isShown = !isShown;
  }
</script>

<Button onclick={toggleModal} colorVariant="primary">
  Open Modal
</Button>

<Modal.Root
  isShown={isShown}
  onShow={handleShow}
  onShown={handleShown}
  onHide={handleHide}
  onHidePrevented={handleHidePrevented}
  onHidden={handleHidden}>
  <Modal.Dialog>
    <Modal.Content>
      <Modal.Header>
        <Modal.Title>Modal Title</Modal.Title>
      </Modal.Header>
      <Modal.Body>
        <p>This is the modal body content. You can place any content here, including forms, text, or other components.</p>
      </Modal.Body>
      <Modal.Footer>
        <Button colorVariant="secondary" onclick={() => isShown = false}>Close</Button>
        <Button colorVariant="primary">Save changes</Button>
      </Modal.Footer>
    </Modal.Content>
  </Modal.Dialog>
</Modal.Root>
        

  • Modal.Title automatically labels the open dialog with aria-labelledby.
  • When the modal opens, focus moves to the first focusable control inside the dialog, usually the dismiss button.
  • Tab and Shift+Tab stay within the open dialog until it closes.
  • Escape closes the dialog when isKeyboardDismissible is enabled, and closing returns focus to the opener.
  • Use a visible title for every modal. If a custom title is not visible, pass an explicit accessible label to Modal.Root.

The Modal component creates a dialog box that overlays the current page. It consists of several components that work together: Modal.Root, Modal.Dialog, Modal.Content, Modal.Header, Modal.Title, Modal.Body, and Modal.Footer.

            
            <script>
    let isModalShown = $state(false);
</script>

<Button colorVariant="primary" onclick={() => isModalShown = true}>
    Launch demo modal
</Button>

<Modal.Root isShown={isModalShown} onHidden={() => isModalShown = false}>
    <Modal.Dialog>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Modal title</Modal.Title>
            </Modal.Header>
            <Modal.Body>
                <p>Modal body text goes here.</p>
            </Modal.Body>
            <Modal.Footer>
                <Button colorVariant="secondary" onclick={() => isModalShown = false}>Close</Button>
                <Button colorVariant="primary">Save changes</Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>
        

When you set useBackdrop="static", the modal won't close when clicking outside it. This forces the user to interact with the modal dialog explicitly.

            
            <script>
    let isModalShown = $state(false);
</script>

<Button colorVariant="primary" onclick={() => isModalShown = true}>
    Launch static backdrop modal
</Button>

<Modal.Root useBackdrop="static" isShown={isModalShown} onHidden={() => isModalShown = false}>
    <Modal.Dialog>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Static backdrop modal</Modal.Title>
            </Modal.Header>
            <Modal.Body>
                <p>This modal has a static backdrop, meaning it doesn't close when clicking outside it.</p>
            </Modal.Body>
            <Modal.Footer>
                <Button colorVariant="secondary" onclick={() => isModalShown = false}>Close</Button>
                <Button colorVariant="primary">Understood</Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>
        

When modals become too long for the user's viewport, they scroll independently. You can create scrollable modals by setting isScrollable=true on the Modal.Dialog component.

            
            <script>
    let isLongContentModalShown = $state(false);
    let isLongContentWithScrollableModalShown = $state(false);
</script>

<!-- Standard modal with long content -->
<Button colorVariant="primary" onclick={() => isLongContentModalShown = true}>
    Launch long content modal
</Button>

<Modal.Root isShown={isLongContentModalShown} onHidden={() => isLongContentModalShown = false}>
    <Modal.Dialog>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Long content modal</Modal.Title>
            </Modal.Header>
            <Modal.Body style="min-height: 100vh">
                <!-- Very long content here -->
                <p>This is some placeholder content to show the scrolling behavior for modals.</p>
            </Modal.Body>
            <Modal.Footer>
                <Button colorVariant="secondary" onclick={() => isLongContentModalShown = false}>Close</Button>
                <Button colorVariant="primary">Save changes</Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>

<!-- Scrollable modal -->
<Button colorVariant="primary" onclick={() => isLongContentWithScrollableModalShown = true}>
    Launch scrollable modal
</Button>

<Modal.Root isShown={isLongContentWithScrollableModalShown} onHidden={() => isLongContentWithScrollableModalShown = false}>
    <Modal.Dialog isScrollable={true}>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Scrollable modal</Modal.Title>
            </Modal.Header>
            <Modal.Body>
                <!-- Content that will scroll within the modal -->
                <p>This content will scroll within the modal while the header and footer remain fixed.</p>
            </Modal.Body>
            <Modal.Footer>
                <Button colorVariant="secondary" onclick={() => isLongContentWithScrollableModalShown = false}>Close</Button>
                <Button colorVariant="primary">Save changes</Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>
        

Add isVerticallyCentered=true to the Modal.Dialog component to vertically center the modal in the viewport.

            
            <script>
    let isVerticallyCenteredModalShown = $state(false);
    let isVerticallyCenteredWithScrollableModalShown = $state(false);
</script>

<!-- Vertically centered modal -->
<Button colorVariant="primary" onclick={() => isVerticallyCenteredModalShown = true}>
    Vertically centered modal
</Button>

<Modal.Root isShown={isVerticallyCenteredModalShown} onHidden={() => isVerticallyCenteredModalShown = false}>
    <Modal.Dialog isVerticallyCentered={true}>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Vertically centered modal</Modal.Title>
            </Modal.Header>
            <Modal.Body>
                <p>This modal is vertically centered in the viewport.</p>
            </Modal.Body>
            <Modal.Footer>
                <Button colorVariant="secondary" onclick={() => isVerticallyCenteredModalShown = false}>Close</Button>
                <Button colorVariant="primary">Save changes</Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>

<!-- Vertically centered scrollable modal -->
<Button colorVariant="primary" onclick={() => isVerticallyCenteredWithScrollableModalShown = true}>
    Vertically centered scrollable modal
</Button>

<Modal.Root isShown={isVerticallyCenteredWithScrollableModalShown} onHidden={() => isVerticallyCenteredWithScrollableModalShown = false}>
    <Modal.Dialog isVerticallyCentered={true} isScrollable={true}>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Vertically centered scrollable modal</Modal.Title>
            </Modal.Header>
            <Modal.Body>
                <p>This modal is both vertically centered and scrollable.</p>
                <!-- Add more content to demonstrate scrolling -->
            </Modal.Body>
            <Modal.Footer>
                <Button colorVariant="secondary" onclick={() => isVerticallyCenteredWithScrollableModalShown = false}>Close</Button>
                <Button colorVariant="primary">Save changes</Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>
        

You can use the Bootstrap grid system within modals by nesting .container-fluid within the Modal.Body. This gives you a flexible way to lay out modal content using the same grid system used elsewhere in Bootstrap.

            
            <script>
    let isGridModalShown = $state(false);
</script>

<Button colorVariant="primary" onclick={() => isGridModalShown = true}>
    Launch modal with grid
</Button>

<Modal.Root isShown={isGridModalShown} onHidden={() => isGridModalShown = false}>
    <Modal.Dialog>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Grid in Modal</Modal.Title>
            </Modal.Header>
            <Modal.Body>
                <div class="container-fluid">
                    <div class="row">
                        <div class="col-md-4">.col-md-4</div>
                        <div class="col-md-4 ms-auto">.col-md-4 .ms-auto</div>
                    </div>
                    <div class="row">
                        <div class="col-md-3 ms-auto">.col-md-3 .ms-auto</div>
                        <div class="col-md-2 ms-auto">.col-md-2 .ms-auto</div>
                    </div>
                    <div class="row">
                        <div class="col-md-6 ms-auto">.col-md-6 .ms-auto</div>
                    </div>
                    <div class="row">
                        <div class="col-sm-9">
                            Level 1: .col-sm-9
                            <div class="row">
                                <div class="col-8 col-sm-6">Level 2: .col-8 .col-sm-6</div>
                                <div class="col-4 col-sm-6">Level 2: .col-4 .col-sm-6</div>
                            </div>
                        </div>
                    </div>
                </div>
            </Modal.Body>
            <Modal.Footer>
                <Button colorVariant="secondary" onclick={() => isGridModalShown = false}>Close</Button>
                <Button colorVariant="primary">Save changes</Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>
        

Toggle between multiple modals with some clever JavaScript by stacking them. Make sure to place the modal HTML in your code in the same order you'd like it to appear.

            
            <script>
    let isToggleBetweenTwoModals1Shown = $state(false);
    let isToggleBetweenTwoModals2Shown = $state(false);
</script>

<Button colorVariant="primary" onclick={() => isToggleBetweenTwoModals1Shown = true}>
    Open first modal
</Button>

<Modal.Root isShown={isToggleBetweenTwoModals1Shown} onHidden={() => isToggleBetweenTwoModals1Shown = false}>
    <Modal.Dialog isVerticallyCentered={true}>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Modal 1</Modal.Title>
            </Modal.Header>
            <Modal.Body>Show a second modal and hide this one with the button below.</Modal.Body>
            <Modal.Footer>
                <Button colorVariant="primary" onclick={() => { isToggleBetweenTwoModals1Shown = false; setTimeout(() => { isToggleBetweenTwoModals2Shown = true; }, 150); }}>
                    Open second modal
                </Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>

<Modal.Root isShown={isToggleBetweenTwoModals2Shown} onHidden={() => isToggleBetweenTwoModals2Shown = false}>
    <Modal.Dialog isVerticallyCentered={true}>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Modal 2</Modal.Title>
            </Modal.Header>
            <Modal.Body>Hide this modal and show the first with the button below.</Modal.Body>
            <Modal.Footer>
                <Button colorVariant="primary" onclick={() => { isToggleBetweenTwoModals2Shown = false; setTimeout(() => { isToggleBetweenTwoModals1Shown = true; }, 150); }}>
                    Back to first
                </Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>
        

For modals that simply appear rather than fade in, set the useFade property to false.

            
            <script>
    let isWithoutFadeModalShown = $state(false);
</script>

<Button colorVariant="primary" onclick={() => isWithoutFadeModalShown = true}>
    Launch modal without fade
</Button>

<Modal.Root isShown={isWithoutFadeModalShown} useFade={false} onHidden={() => isWithoutFadeModalShown = false}>
    <Modal.Dialog>
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Modal without fade animation</Modal.Title>
            </Modal.Header>
            <Modal.Body>
                <p>This modal appears without a fade animation.</p>
            </Modal.Body>
            <Modal.Footer>
                <Button colorVariant="secondary" onclick={() => isWithoutFadeModalShown = false}>Close</Button>
                <Button colorVariant="primary">Save changes</Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>
        

Another override is the option to have a modal fullscreen at particular breakpoints with the fullscreenOnBreakpoint property.

Available fullscreenOnBreakpoint options:

  • "always" - Always fullscreen on all screens
  • "sm" - Fullscreen below sm breakpoint
  • "md" - Fullscreen below md breakpoint
  • "lg" - Fullscreen below lg breakpoint
  • "xl" - Fullscreen below xl breakpoint
  • "xxl" - Fullscreen below xxl breakpoint
            
            <script>
    let isFullscreenModalShown = $state(false);
</script>

<Button colorVariant="primary" onclick={() => isFullscreenModalShown = true}>
    Launch fullscreen modal
</Button>

<Modal.Root isShown={isFullscreenModalShown} onHidden={() => isFullscreenModalShown = false}>
    <Modal.Dialog fullscreenOnBreakpoint="lg">
        <Modal.Content>
            <Modal.Header isDismissible={true}>
                <Modal.Title level={1} class="fs-5">Full screen below lg</Modal.Title>
            </Modal.Header>
            <Modal.Body><p>This modal is fullscreen on viewports smaller than the lg breakpoint.</p></Modal.Body>
            <Modal.Footer>
                <Button colorVariant="secondary" onclick={() => isFullscreenModalShown = false}>Close</Button>
                <Button colorVariant="primary">Save changes</Button>
            </Modal.Footer>
        </Modal.Content>
    </Modal.Dialog>
</Modal.Root>

<!-- Other fullscreen options -->
<!-- fullscreenOnBreakpoint="always" - Always fullscreen on all screens -->
<!-- fullscreenOnBreakpoint="sm" - Fullscreen below sm breakpoint -->
<!-- fullscreenOnBreakpoint="md" - Fullscreen below md breakpoint -->
<!-- fullscreenOnBreakpoint="xl" - Fullscreen below xl breakpoint -->
<!-- fullscreenOnBreakpoint="xxl" - Fullscreen below xxl breakpoint -->
        

API Reference

Modal.Root Props

PropTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
elementRefHTMLElement | nullnullReference to the DOM element
idstringAuto-generatedUnique identifier for the offcanvas
isFocusablebooleantrueDetermines if the modal should receive focus when shown.
isKeyboardDismissiblebooleantrueDetermines if the modal can be dismissed using the Escape key.
isShownbooleanfalseControls whether the modal is displayed or hidden.
onHideEventListener-Callback function to execute when the modal begins to hide.
onHidePreventedEventListener-Callback function to execute when the modal hide is prevented.
onHiddenEventListener-Callback function to execute when the modal has been hidden.
onShowEventListener-Callback function to execute when the modal begins to show.
onShownEventListener-Callback function to execute when the modal has been shown.
useBackdrop'static' | booleantrueControls the modal backdrop behavior. When set to 'static', the modal won't close when clicking outside it.
useFadebooleantrueControls whether the modal uses a fade animation when opening and closing.

Modal.Content Props

PropTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
elementRefHTMLElement | nullnullReference to the DOM element
idstringAuto-generatedUnique identifier for the offcanvas

Modal.Dialog Props

PropTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
elementRefHTMLElement | nullnullReference to the DOM element
fullscreenOnBreakpoint'always' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'-Makes the modal fullscreen below the specified breakpoint.
idstringAuto-generatedUnique identifier for the offcanvas
isScrollablebooleanfalseWhen set to true, the modal body becomes scrollable while the header and footer remain fixed.
isVerticallyCenteredbooleanfalseWhen set to true, the modal is vertically centered in the viewport.
size'sm' | 'lg' | 'xl'-Controls the size of the modal. Default is medium size.

Modal.Footer Props

PropTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
elementRefHTMLElement | nullnullReference to the DOM element
idstringAuto-generatedUnique identifier for the offcanvas

Modal.Header Props

PropTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
elementRefHTMLElement | nullnullReference to the DOM element
idstringAuto-generatedUnique identifier for the offcanvas
isDismissiblebooleanfalseWhen set to true, a close button (×) is added to the header that will close the modal.

Modal.Title Props

PropTypeDefaultDescription
classstring-Additional CSS classes to apply to the component
elementRefHTMLElement | nullnullReference to the DOM element
idstringAuto-generatedUnique identifier for the offcanvas
level1 | 2 | 3 | 4 | 5 | 64The heading level to use (h1-h6).

CSS Classes

These classes can be used to customize the modal components:

ClassApplied toDescription
.modalModal.RootMain container for the modal.
.modal-dialogModal.DialogContainer for modal content that positions the modal in the viewport.
.modal-contentModal.ContentContainer for the modal's header, body, and footer.
.modal-headerModal.HeaderContainer for the modal title and close button.
.modal-titleModal.TitleStyles the modal title.
.modal-bodyModal.BodyContainer for modal content.
.modal-footerModal.FooterContainer for modal actions (usually buttons).
.modal-dialog-scrollableModal.DialogMakes the modal body scrollable.
.modal-dialog-centeredModal.DialogVertically centers the modal in the viewport.
.modal-smModal.DialogCreates a small modal.
.modal-lgModal.DialogCreates a large modal.
.modal-xlModal.DialogCreates an extra-large modal.
.modal-fullscreenModal.DialogCreates a fullscreen modal.
.modal-fullscreen-[breakpoint]-downModal.DialogCreates a modal that's fullscreen below a specific breakpoint.