Appearance
@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
| Token | Default | Description |
|---|---|---|
--modal-backdrop-bg | --overlay-scrim | Backdrop color |
--modal-backdrop-blur | 4px | Backdrop blur |
--modal-z | --z-modal (400) | Z-index |
Container
| Token | Default |
|---|---|
--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
| Token | Value |
|---|---|
--modal-width-sm | 24rem |
--modal-width-md | 32rem |
--modal-width-lg | 42rem |
--modal-width-xl | 56rem |
--modal-width-full | calc(100vw - var(--space-8)) |
--modal-max-height | calc(100vh - var(--space-8)) |
--modal-margin | --space-4 |
Sections
| Token | Default |
|---|---|
--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.
Modal Container
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;
splitmodifier 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
| File | Description |
|---|---|
index.css | Barrel — imports tokens + generated styles |
modal.css | Token definitions |
modal.g.css | Generated component styles |
index.js / index.d.ts | TypeScript types + runtime constants |
Dependencies
Requires tokens from @vibe-labs/design (surfaces, borders, spacing, typography, elevation, transitions, overlays, z-index).
Build
bash
npm run buildUsage 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 / Flag | Values | Notes |
|---|---|---|
data-size | sm md lg xl full | Default: md |
data-variant | danger | Danger-tinted header border |
data-seamless | boolean flag | Removes header/footer borders |
data-centered | boolean flag | Centers body content |
data-drawer | boolean flag | Right-side slide-in drawer |
data-entering | on backdrop | Trigger fade-in animation |
data-exiting | on backdrop | Trigger fade-out animation |
data-split | on modal-footer | Space-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>