what

Modals & Drawers

Overlay content with modals and slide-in panels. What includes built-in components for dialogs, confirmation prompts, and drawers — all with keyboard support and smooth transitions.

Opening a Modal

Add w-modal-trigger to any element. The value is the id of the modal to open.

<button w-modal-trigger="edit-modal" class="btn btn-primary">
  Edit Profile
</button>

When clicked, the modal with id="edit-modal" becomes visible. The trigger works on any element — buttons, links, table rows, or cards.

Use the <what-modal> component to create a modal. It accepts id, title, and an optional size prop.

<what-modal id="edit-modal" title="Edit Profile">
  <form action="/w-action/update-profile" method="post">
    <div class="form-group">
      <label class="form-label">Name</label>
      <input type="text" name="name" class="form-input" value="#user.full_name#">
    </div>
    <div class="form-group">
      <label class="form-label">Email</label>
      <input type="email" name="email" class="form-input" value="#user.email#">
    </div>
    <div class="modal-footer">
      <button type="button" class="btn btn-outline" w-modal-close>Cancel</button>
      <button type="submit" class="btn btn-primary">Save</button>
    </div>
  </form>
</what-modal>

Everything inside the component tags becomes the modal body via <slot/>. The component renders the backdrop, header with close button, and body wrapper automatically.

Modal sizes

Pass a size prop to control the modal width:

SizeMax Width
modal-sm360px
(default)500px
modal-lg720px
modal-xl960px
<what-modal id="preview-modal" title="Preview" size="modal-lg">
  <div class="p-4">
    <img src="/uploads/image.jpg" style="width:100%">
  </div>
</what-modal>

Closing Modals

Modals can be closed in three ways:

  1. w-modal-close attribute — Add to any element inside the modal. Clicking it closes the nearest modal or drawer.
  2. Close button — The <what-modal> component includes a close button (×) in the header automatically.
  3. Programmatic — Toggle the active class on the backdrop element from JavaScript.
<!-- Close button inside modal content -->
<button class="btn btn-outline" w-modal-close>Cancel</button>

<!-- Close link -->
<a href="#" w-modal-close>Dismiss</a>

When a modal closes, overflow: hidden is removed from the body, restoring scroll.

Tip: The w-modal-close attribute works the same way in modals, confirm modals, and drawers. It finds the nearest .modal-backdrop or .drawer-backdrop ancestor and removes its active class.

Confirm Modals

Use <what-confirm-modal> for destructive actions that need explicit user confirmation. It renders a centered dialog with icon, message, and action buttons.

<button w-modal-trigger="delete-confirm" class="btn btn-danger">
  Delete Project
</button>

<what-confirm-modal
  id="delete-confirm"
  title="Delete Project?"
  message="This will permanently remove the project and all its data."
  icon="warning"
  confirmText="Delete"
  cancelText="Keep It"
  confirmClass="btn-danger"
/>

Confirm modal props

PropDefaultDescription
idUnique identifier for the modal
titleDialog heading
messageDescriptive text below the title
iconwarningIcon style for the dialog
confirmTextConfirmLabel for the confirm button
cancelTextCancelLabel for the cancel button
confirmClassbtn-primaryCSS class for the confirm button
Note: The confirm modal's buttons both use w-modal-close, so clicking either one closes the dialog. To wire the confirm button to an action, add w-post and w-target attributes to the button or wrap the modal in a form.

Drawers

Drawers are slide-in panels anchored to a screen edge. Use the <what-drawer> component with the same trigger pattern as modals.

<button w-modal-trigger="settings-drawer" class="btn btn-outline">
  Settings
</button>

<what-drawer id="settings-drawer" title="Settings" position="right">
  <div class="p-4 space-y-4">
    <div class="form-group">
      <label class="form-label">Language</label>
      <select class="form-select">
        <option>English</option>
        <option>Spanish</option>
      </select>
    </div>
    <div class="form-group">
      <label class="form-label">Notifications</label>
      <input type="checkbox" class="form-switch" checked>
    </div>
  </div>
</what-drawer>

Drawer positions

The position prop controls which edge the drawer slides from:

PositionBehavior
rightSlides from the right edge (default)
leftSlides from the left edge
topSlides down from the top
bottomSlides up from the bottom

Drawer sizes

Pass a size prop to control the drawer width (for left/right) or height (for top/bottom):

SizeDimension
drawer-sm280px
(default)400px
drawer-lg560px
drawer-xl720px
<what-drawer id="detail-drawer" title="Details" position="right" size="drawer-lg">
  <div class="p-4">
    <p>Detailed content here...</p>
  </div>
</what-drawer>

Blurred Backdrop

By default, modals use a semi-transparent dark overlay. Add the modal-backdrop-blur class to use a frosted glass effect instead.

components/modal.html
<div id="#id#" class="modal-backdrop modal-backdrop-blur">
  <div class="modal">
    ...
  </div>
</div>

The blur effect combines a lighter overlay (rgba(0, 0, 0, 0.3)) with backdrop-filter: blur(8px). You can customize both values through CSS variables:

static/styles.css
.modal-backdrop-blur {
  --w-modal-backdrop: rgba(0, 0, 0, 0.2);
  backdrop-filter: blur(12px);
}

For drawers, the same technique applies on the .drawer-backdrop element:

.drawer-backdrop {
  background-color: rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(8px);
}

Dynamic Content

Combine modal triggers with w-get to load modal content from the server on demand. This avoids rendering heavy content upfront.

<button
  w-get="/w-partial/user-detail?id=42"
  w-target="#detail-body"
  w-modal-trigger="detail-modal"
  class="btn btn-outline"
>
  View Details
</button>

<what-modal id="detail-modal" title="User Details">
  <div id="detail-body">
    <p class="text-muted">Loading...</p>
  </div>
</what-modal>

When the button is clicked, What fetches the partial, injects it into #detail-body, and opens the modal. The modal starts with a loading placeholder that gets replaced by the server response.

The same pattern works with drawers:

<button
  w-get="/w-partial/activity-log"
  w-target="#log-content"
  w-modal-trigger="log-drawer"
  class="btn btn-ghost"
>
  Activity Log
</button>

<what-drawer id="log-drawer" title="Recent Activity" position="right" size="drawer-lg">
  <div id="log-content" class="p-4">
    <p class="text-muted">Loading...</p>
  </div>
</what-drawer>
Tip: Add w-loading="is-loading" to the trigger button to show a visual indicator while the content loads. The modal opens immediately, and the content swaps in when the response arrives.

CSS Reference

Key CSS classes for modals and drawers:

ClassPurpose
modal-backdropFull-screen overlay container
modal-backdrop-blurFrosted glass backdrop variant
modalThe modal panel itself
modal-headerTop section with title and close button
modal-bodyScrollable content area
modal-footerBottom section for action buttons
modal-sm / modal-lg / modal-xlSize variants
drawer-backdropFull-screen overlay for drawers
drawerThe drawer panel
drawer-right / drawer-left / drawer-top / drawer-bottomPosition variants
drawer-sm / drawer-lg / drawer-xlSize variants
activeAdded to backdrop to show modal/drawer