Monsoon v0.2.0
Made by Alexandre Kohler Star

Navigation and overlays

Drawer

A side panel toggled by a hidden checkbox and its labels, with a dimmed overlay that closes it. Works without JavaScript and without a dialog.

Navigation and overlays6 classesNo JavaScriptLight and dark

Example

Full

Classes

6 classes. Click a name to copy it. Utilities on the same element always win over component defaults, so any of them can be adjusted with plain utility classes.

ClassTypeWhat it does
Base Base class of the drawer. On a <div> in the example. Sets overflow, grid display, grid tracks and width, and 8 custom properties.
CSS
.drawer {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  width: 100%;
}

:root:has(:is(.modal, .drawer-dialog, .sheet, .lightbox):modal, .modal:target, .lightbox:target, .drawer:not(.drawer-open-lg) > .drawer-toggle:checked) {
  overflow: hidden;
}
Part Part of the drawer. On an <input> in the example. Sets fixed position, width, height, opacity, native control styling and transition.
CSS
.drawer-toggle:checked ~ .drawer-side > .drawer-overlay {
  opacity: 1;
}

.drawer-toggle:checked ~ .drawer-side > :where(:not(.drawer-overlay)) {
  transform: none;
}
Part Part of the drawer. On a <div> in the example. Sets grid tracks and width.
CSS
.drawer-content {
  grid-area: 1 / 1;
  min-width: 0;
}

@media (min-width: 1024px) {
  .drawer-open-lg > .drawer-content {
    grid-column: 2;
  }
}
Part Part of the drawer. On a <div> in the example. Sets fixed position, offsets, stacking order, grid display, grid tracks and overflow.
CSS
.drawer-side > * {
  grid-area: 1 / 1;
}

.drawer-side {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: grid;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  overflow: hidden;
  visibility: hidden;
  pointer-events: none;
  transition: visibility 0s linear var(--drawer-speed);
}
Part Part of the drawer. On a <label> in the example. Sets block display, background, opacity, pointer cursor and transition.
CSS
.drawer-overlay {
  display: block;
  background: oklch(0 0 0 / calc(.4 + .2 * var(--mode)));
  opacity: 0;
  cursor: pointer;
  transition: opacity var(--drawer-speed) var(--m-ease);
}

.drawer-toggle:checked ~ .drawer-side > .drawer-overlay {
  opacity: 1;
}
Part Part of the drawer. On a <nav> in the example. Sets flex display, alignment, gap, width, height and padding.
CSS
.drawer-panel, .drawer-dialog {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: var(--drawer-w);
  height: var(--drawer-h);
  padding: 1rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  color: var(--foreground);
  border-radius: var(--drawer-radius);
  box-shadow: var(--m-shadow-xl);
}

@media (min-width: 1024px) {
  .drawer-open-lg.drawer-end > .drawer-side > .drawer-panel {
    border-inline-end: 0;
    border-inline-start: var(--m-border-width) solid var(--border);
  }
}

Usage

Components ship inside the same stylesheet as the utilities. There is nothing else to install and no script to load: link Monsoon and write the markup.

index.html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/monsooncss">

<!-- Drawer: start from the base class, then add modifiers -->
<div class="drawer"><input id="nav-drawer" type="checkbox" class="drawer-toggle"><div class="drawer-content"><label for="nav-drawer" class="inline-flex items-center justify-center gap-2 rounded-lg border border-border bg-surface px-4 py-2 text-sm font-medium hover:bg-muted cursor-pointer">Open menu</label></div><div class="drawer-side"><label for="nav-drawer" class="drawer-overlay" aria-label="Close menu"></label><nav class="drawer-panel" aria-label="Main"><p class="px-3 text-base font-semibold">Acme Studio</p><ul class="menu"><li><a href="#" aria-current="page"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z"/></svg>Dashboard</a></li><li><a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>Projects</a></li><li><a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="8" r="4"/><path d="M2 21a7 7 0 0 1 14 0M16 4a4 4 0 0 1 0 8M22 21a7 7 0 0 0-4-6.3"/></svg>Team</a></li><li><a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M4.9 4.9 7 7M17 17l2.1 2.1M2 12h3M19 12h3M4.9 19.1 7 17M17 7l2.1-2.1"/></svg>Settings</a></li></ul></nav></div></div>

Theme it with --seed on any ancestor, for example style="--seed: #0d9488", and force a scheme with class="dark" or class="light" on a wrapper.

Accessibility

  • Give every control a visible label linked with for, or an aria-label when the purpose is obvious from context. Link help and error text with aria-describedby; invalid styles use :user-invalid, so they appear only after the user interacts.
  • Checkboxes and radios stay real inputs, so they keep native keyboard behavior (Space toggles, arrow keys move within a radio group) and form submission.
  • Wrap navigation in nav with an aria-label when there is more than one on the page, and mark the current link with aria-current="page".
  • Every interactive part shows a visible focus ring with :focus-visible, and tone colors pair with -foreground tokens that keep text at 4.5:1 or better in light and dark mode.

No recent searches

Try , , or .

Recent

Loading the search index

No results for ""

Check the spelling, or search a class name such as px-4.