Skip to content

@vibe-labs/design-components-modals

Modal and drawer component tokens, styles, and TypeScript types for the Vibe Design System.

Installation

css
@import "@vibe-labs/design-components-modals";
ts
import { ModalSizes, ModalVariants } from "@vibe-labs/design-components-modals/types";
import type { ModalStyleProps, ModalBackdropStyleProps } from "@vibe-labs/design-components-modals/types";

Contents

Tokens (modal.css)

CSS custom properties defined in @layer vibe.tokens:

Backdrop

TokenDefaultDescription
--modal-backdrop-bg--overlay-scrimBackdrop color
--modal-backdrop-blur4pxBackdrop blur
--modal-z--z-modal (400)Z-index

Container

TokenDefault
--modal-bg--surface-elevated
--modal-border-color--border-subtle
--modal-border-width--border-width-1
--modal-radius--radius-xl (1rem)
--modal-shadow--shadow-xl

Sizing

TokenValue
--modal-width-sm24rem
--modal-width-md32rem
--modal-width-lg42rem
--modal-width-xl56rem
--modal-width-fullcalc(100vw - var(--space-8))
--modal-max-heightcalc(100vh - var(--space-8))
--modal-margin--space-4

Sections

TokenDefault
--modal-padding-x--space-6
--modal-padding-y--space-5
--modal-header-gap--space-4
--modal-footer-gap--space-3
--modal-section-border--border-subtle
--modal-title-font-size--text-lg
--modal-title-font-weight--font-semibold
--modal-speed--duration-normal

Generated Styles (modal.g.css)

Component classes generated into @layer vibe.components.

Backdrop

Fixed fullscreen overlay with scrim color and backdrop blur. Centers the modal with overflow-y scrolling. Enter (fade-in) and exit (fade-out) animations.

Flex column with max-height constraint. Animates with scale-in/scale-out alongside the backdrop.

Sizes

sm · md · lg · xl · full (default: md)

Sections

  • modal-header — flex row with bottom border, contains title and close button
  • modal-title — semibold heading, flex-grow with min-width: 0
  • modal-description — muted subtext below title
  • modal-body — flex-grow scrollable content area
  • modal-footer — right-aligned flex row with top border; split modifier for space-between layout

Variants

  • danger — danger-subtle header border

Modifiers

  • seamless — removes header/footer internal borders
  • centered — centers body content with text-align center
  • drawer — converts to a right-side slide-in panel (fixed, full height, rounded left corners only, slide-in/out animations)

TypeScript Types (types/)

ts
ModalSizes    // ["sm", "md", "lg", "xl", "full"]
ModalVariants // ["danger"]

type ModalSize
type ModalVariant

interface ModalBackdropStyleProps { entering?: boolean; exiting?: boolean }
interface ModalStyleProps {
  size?: ModalSize
  variant?: ModalVariant
  seamless?: boolean
  centered?: boolean
  drawer?: boolean
}
interface ModalHeaderStyleProps {}
interface ModalTitleStyleProps {}
interface ModalDescriptionStyleProps {}
interface ModalBodyStyleProps {}
interface ModalFooterStyleProps { split?: boolean }

Dist Structure

FileDescription
index.cssBarrel — imports tokens + generated styles
modal.cssToken definitions
modal.g.cssGenerated component styles
index.js / index.d.tsTypeScript types + runtime constants

Dependencies

Requires tokens from @vibe-labs/design (surfaces, borders, spacing, typography, elevation, transitions, overlays, z-index).

Build

bash
npm run build

Usage Guide

Import

css
@import "@vibe-labs/design-components-modals";
ts
import { ModalSizes, ModalVariants } from "@vibe-labs/design-components-modals/types";
import type { ModalStyleProps } from "@vibe-labs/design-components-modals/types";

Variants

Attribute / FlagValuesNotes
data-sizesm md lg xl fullDefault: md
data-variantdangerDanger-tinted header border
data-seamlessboolean flagRemoves header/footer borders
data-centeredboolean flagCenters body content
data-drawerboolean flagRight-side slide-in drawer
data-enteringon backdropTrigger fade-in animation
data-exitingon backdropTrigger fade-out animation
data-spliton modal-footerSpace-between footer layout

Examples

Basic modal (medium size)

html
<div class="modal-backdrop">
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
    <div class="modal-header">
      <span class="modal-title" id="modal-title">Confirm Action</span>
      <button type="button" aria-label="Close">×</button>
    </div>
    <div class="modal-body">
      <p>Are you sure you want to continue?</p>
    </div>
    <div class="modal-footer">
      <button type="button">Cancel</button>
      <button type="button">Confirm</button>
    </div>
  </div>
</div>

Large modal with description

html
<div class="modal-backdrop">
  <div class="modal" data-size="lg" role="dialog" aria-modal="true">
    <div class="modal-header">
      <div>
        <span class="modal-title">Edit Profile</span>
        <span class="modal-description">Changes are saved automatically.</span>
      </div>
      <button type="button" aria-label="Close">×</button>
    </div>
    <div class="modal-body">
      <!-- form content -->
    </div>
    <div class="modal-footer" data-split>
      <button type="button">Delete Account</button>
      <button type="button">Save Changes</button>
    </div>
  </div>
</div>

Danger variant

html
<div class="modal-backdrop">
  <div class="modal" data-variant="danger" data-size="sm" role="dialog" aria-modal="true">
    <div class="modal-header">
      <span class="modal-title">Delete Item</span>
    </div>
    <div class="modal-body" data-centered>
      <p>This action cannot be undone.</p>
    </div>
    <div class="modal-footer">
      <button type="button">Cancel</button>
      <button type="button">Delete</button>
    </div>
  </div>
</div>

Seamless modal (no section borders)

html
<div class="modal-backdrop">
  <div class="modal" data-seamless role="dialog" aria-modal="true">
    <div class="modal-header">
      <span class="modal-title">Welcome</span>
    </div>
    <div class="modal-body">
      <p>No visible dividers between sections.</p>
    </div>
    <div class="modal-footer">
      <button type="button">Get Started</button>
    </div>
  </div>
</div>

Drawer (right-side panel)

html
<div class="modal-backdrop">
  <div class="modal" data-drawer role="dialog" aria-modal="true">
    <div class="modal-header">
      <span class="modal-title">Settings</span>
      <button type="button" aria-label="Close">×</button>
    </div>
    <div class="modal-body">
      <!-- settings content -->
    </div>
    <div class="modal-footer">
      <button type="button">Apply</button>
    </div>
  </div>
</div>

Entering / exiting animation states

html
<!-- Fade in -->
<div class="modal-backdrop" data-entering>
  <div class="modal">...</div>
</div>

<!-- Fade out -->
<div class="modal-backdrop" data-exiting>
  <div class="modal">...</div>
</div>

With Vue

vue
<template>
  <div v-if="open" class="modal-backdrop" :data-entering="entering || undefined" :data-exiting="exiting || undefined">
    <div class="modal" :data-size="size" :data-variant="variant || undefined" role="dialog" aria-modal="true">
      <div class="modal-header">
        <span class="modal-title">{{ title }}</span>
        <button @click="$emit('close')">×</button>
      </div>
      <div class="modal-body">
        <slot />
      </div>
      <div class="modal-footer">
        <slot name="footer" />
      </div>
    </div>
  </div>
</template>

Vibe