Modal

Lockness UI Component

VIEW

DOCUMENTATION

Modal

Modal dialog component built on the native HTML <dialog> element. Supports both native dialog and Unpoly layer modes. Includes header, body, footer sections with close button support.

Installation

bash
deno run -A jsr:@lockness/ui add modal

Usage

tsx
import { 
  Modal, 
  ModalTrigger, 
  ModalContent, 
  ModalHeader, 
  ModalTitle, 
  ModalDescription,
  ModalBody, 
  ModalFooter, 
  ModalClose,
  ModalCloseIcon 
} from '@lockness/ui/components'

<ModalTrigger targetId="my-modal">Open Modal</ModalTrigger>

<Modal id="my-modal">
  <ModalContent>
    <ModalHeader>
      <ModalTitle>Modal Title</ModalTitle>
      <ModalCloseIcon />
    </ModalHeader>
    <ModalBody>
      <p>Modal content goes here.</p>
    </ModalBody>
    <ModalFooter>
      <ModalClose>Cancel</ModalClose>
      <Button>Confirm</Button>
    </ModalFooter>
  </ModalContent>
</Modal>

Components

Modal

The main dialog container using the native <dialog> element.

tsx
<Modal id='unique-modal-id'>
    {/* Modal content */}
</Modal>

ModalTrigger

Button or link that opens the modal. Supports both native dialog and Unpoly modes.

tsx
// Native dialog trigger
<ModalTrigger targetId="my-modal">Open Modal</ModalTrigger>

// Unpoly layer trigger (loads content from URL)
<ModalTrigger href="/modal-content">Load Modal</ModalTrigger>

// With variant
<ModalTrigger targetId="my-modal" variant="outline">Open</ModalTrigger>

ModalContent

Wrapper for modal sections (header, body, footer).

tsx
<ModalContent>
    {/* Modal sections */}
</ModalContent>

ModalHeader

Top section containing title and close button.

tsx
<ModalHeader>
    <ModalTitle>Title</ModalTitle>
    <ModalCloseIcon />
</ModalHeader>

ModalTitle

Main heading text for the modal.

tsx
<ModalTitle>Confirm Action</ModalTitle>

ModalDescription

Subtitle or additional context text.

tsx
<ModalDescription>This action cannot be undone.</ModalDescription>

ModalBody

Scrollable main content area.

tsx
<ModalBody>
    <p>Your modal content here...</p>
</ModalBody>

ModalFooter

Bottom section for action buttons.

tsx
<ModalFooter>
    <ModalClose>Cancel</ModalClose>
    <Button>Confirm</Button>
</ModalFooter>

ModalClose

Button that closes the modal.

tsx
<ModalClose>Cancel</ModalClose>
<ModalClose size="md">Done</ModalClose>

ModalCloseIcon

X button for the header area.

tsx
<ModalCloseIcon />

Props

ModalProps

PropTypeDefaultDescription
idstringrequiredUnique identifier for the modal dialog
childrenunknown-Modal content
classstring-Additional CSS class names

ModalTriggerProps

PropTypeDefaultDescription
targetIdstring-ID of the target modal (for native dialog)
hrefstring-URL to load in Unpoly layer (for Unpoly mode)
variant'primary' | 'secondary' | 'outline' | 'ghost''primary'Visual style variant
childrenunknown-Button content
classstring-Additional CSS class names

ModalCloseProps

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''sm'Button size
childrenunknown'Close'Button content
classstring-Additional CSS class names

Complete Example

tsx
<ModalTrigger targetId="delete-modal" variant="destructive">
  Delete Item
</ModalTrigger>

<Modal id="delete-modal">
  <ModalContent>
    <ModalHeader>
      <div>
        <ModalTitle>Delete Item</ModalTitle>
        <ModalDescription>
          This action cannot be undone.
        </ModalDescription>
      </div>
      <ModalCloseIcon />
    </ModalHeader>
    <ModalBody>
      <p>
        Are you sure you want to delete this item? 
        All associated data will be permanently removed.
      </p>
    </ModalBody>
    <ModalFooter>
      <ModalClose>Cancel</ModalClose>
      <Button variant="destructive">Delete</Button>
    </ModalFooter>
  </ModalContent>
</Modal>

Unpoly Integration

For dynamic content loading, use the href prop on ModalTrigger:

tsx
<ModalTrigger href='/api/user/edit' variant='outline'>
    Edit Profile
</ModalTrigger>

This creates an Unpoly layer with:

  • up-layer="new" - Opens as new layer
  • up-size="medium" - Medium-sized modal
  • up-dismissable="button" - Close via button only

Features

  • Native Dialog: Uses the HTML <dialog> element for proper accessibility
  • Backdrop Click: Closes when clicking outside the modal
  • Keyboard Support: Escape key closes the modal
  • Focus Management: Proper focus trapping within modal
  • Animations: Fade-in and zoom animations on open
  • Scrollable Content: Body section scrolls for long content
  • Max Height: Limited to 90vh with overflow handling

CSS Variables

css
@theme {
    --modal-header-padding-x: 1.5rem;
    --modal-header-padding-y: 1rem;
    --modal-body-padding-x: 1.5rem;
    --modal-body-padding-y: 1rem;
    --modal-footer-padding-x: 1.5rem;
    --modal-footer-padding-y: 1rem;
    --radius: 0.5rem;
}

LIVE EXAMPLES

Basic Modal

This is a basic modal dialog. It uses the native HTML <dialog> element with pure CSS styling.

Press ESC or click outside to close.

Modal with Description

This modal includes a description below the title

The description provides additional context about what the modal is for.

Confirm Action

Are you sure you want to proceed with this action? This cannot be undone.

Contact Form

Fill out the form below to send us a message

FEATURES

Pure CSS

Uses native HTML <dialog> element with zero custom JavaScript

🎨

Animated

Smooth fade-in and zoom-in animations using Tailwind CSS

⌨️

Accessible

ESC key closes modal, click outside to dismiss, focus trapping

🔗

Unpoly Support

Can also use Unpoly layers for server-rendered modals

BASIC USAGE

tsx
import {
  Modal,
  ModalTrigger,
  ModalContent,
  ModalHeader,
  ModalTitle,
  ModalCloseIcon,
  ModalBody,
  ModalFooter,
  ModalClose,
} from '@lockness/ui/components'

// Trigger button
<ModalTrigger targetId="my-modal">Open Modal</ModalTrigger>

// Modal dialog
<Modal id="my-modal">
  <ModalContent>
    <ModalHeader>
      <ModalTitle>Modal Title</ModalTitle>
      <ModalCloseIcon />
    </ModalHeader>
    <ModalBody>
      <p>Your modal content goes here.</p>
    </ModalBody>
    <ModalFooter>
      <ModalClose>Close</ModalClose>
    </ModalFooter>
  </ModalContent>
</Modal>

TRIGGER VARIANTS

tsx
<ModalTrigger targetId="my-modal" variant="primary">Primary</ModalTrigger>
<ModalTrigger targetId="my-modal" variant="secondary">Secondary</ModalTrigger>
<ModalTrigger targetId="my-modal" variant="outline">Outline</ModalTrigger>
<ModalTrigger targetId="my-modal" variant="ghost">Ghost</ModalTrigger>

UNPOLY LAYER MODE

tsx
// Opens a server-rendered page in an Unpoly layer
<ModalTrigger 
  href="/some-page" 
  variant="primary"
>
  Open Unpoly Layer
</ModalTrigger>