maud-ui 0.2.1

64 headless, accessible UI components for Rust web apps — shadcn Base UI API parity. Plus block templates, a live theme customiser, and shell hooks for 15 third-party widgets (Monaco, xyflow, Excalidraw, Three.js, AG Grid, Leaflet, FullCalendar, SortableJS, and more). Built on maud + htmx, styled like shadcn/ui.
Documentation
.mui-menu {
  display: inline-block;
  position: relative;
}

.mui-menu__trigger {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.mui-menu__chevron {
  font-size: 0.75rem;
  line-height: 1;
  color: var(--mui-text-muted);
  transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.mui-menu__trigger[aria-expanded="true"] .mui-menu__chevron {
  transform: rotate(180deg);
}

/* --- Content panel ----------------------------------------------------- */

.mui-menu__content {
  position: absolute;
  top: calc(100% + 0.25rem);
  inset-inline-start: 0;
  min-width: 12rem;
  background: var(--mui-bg-card);
  border: 1px solid var(--mui-border);
  border-radius: var(--mui-radius-md);
  box-shadow: var(--mui-shadow-lg);
  padding: 0.25rem;
  z-index: 50;
}

.mui-menu__content[hidden] {
  display: none;
}

/* --- Label (group heading) --------------------------------------------- */

.mui-menu__label {
  padding: 0.375rem 0.5rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--mui-text);
  -webkit-user-select: none;
  user-select: none;
}

/* --- Items ------------------------------------------------------------- */

.mui-menu__item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.375rem 0.5rem;
  background: transparent;
  color: var(--mui-text);
  border: none;
  font-family: inherit;
  font-size: 0.875rem;
  line-height: 1.25rem;
  cursor: pointer;
  text-align: start;
  border-radius: var(--mui-radius-sm);
  transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1),
              color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.mui-menu__item:hover:not(:disabled) {
  background: color-mix(in srgb, var(--mui-accent) 12%, transparent);
  color: var(--mui-text);
}

.mui-menu__item:focus-visible {
  background: color-mix(in srgb, var(--mui-accent) 12%, transparent);
  outline: none;
}

/* --- Destructive item -------------------------------------------------- */

.mui-menu__item--danger {
  color: var(--mui-danger-text);
}

.mui-menu__item--danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--mui-danger) 12%, transparent);
  color: var(--mui-danger-text);
}

.mui-menu__item--danger:focus-visible {
  background: color-mix(in srgb, var(--mui-danger) 12%, transparent);
}

/* --- Shortcut text (right side) ---------------------------------------- */

.mui-menu__shortcut {
  margin-inline-start: auto;
  font-size: 0.75rem;
  font-family: var(--mui-font-mono, ui-monospace, monospace);
  color: var(--mui-text-muted);
  letter-spacing: 0.04em;
  pointer-events: none;
}

/* --- Disabled ---------------------------------------------------------- */

.mui-menu__item:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* --- Separator --------------------------------------------------------- */

.mui-menu__separator {
  height: 1px;
  background: var(--mui-border);
  margin: 0.25rem -0.25rem;
}

/* --- Sub-menu indicator ------------------------------------------------ */

.mui-menu__sub-indicator {
  margin-inline-start: auto;
  font-size: 0.75rem;
  color: var(--mui-text-muted);
  line-height: 1;
}

/* --- Checkbox indicator (menuitemcheckbox) ----------------------------- */

.mui-menu__checkbox-indicator {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  font-size: 0.75rem;
  line-height: 1;
  color: var(--mui-text);
}

.mui-menu__item[aria-checked="true"] .mui-menu__checkbox-indicator::before {
  content: "\2713"; /**/
}

/* --- Radio indicator (menuitemradio) ----------------------------------- */

.mui-menu__radio-group {
  display: flex;
  flex-direction: column;
}

.mui-menu__radio-indicator {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  line-height: 1;
}

.mui-menu__item[aria-checked="true"] .mui-menu__radio-indicator::before {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 9999px;
  background: currentColor;
}

/* --- Group wrapper ----------------------------------------------------- */

.mui-menu__group {
  display: flex;
  flex-direction: column;
}

/* --- Submenu (nested content) ------------------------------------------ */

.mui-menu__sub-wrapper {
  position: relative;
}

.mui-menu__sub-trigger::after {
  content: "\203a"; /**/
  margin-inline-start: auto;
  padding-inline-start: 0.75rem;
  color: var(--mui-text-muted);
  font-size: 0.875rem;
  line-height: 1;
}

.mui-menu__sub {
  position: absolute;
  top: -0.25rem;
  inset-inline-start: 100%;
  min-width: 12rem;
  background: var(--mui-bg-card);
  border: 1px solid var(--mui-border);
  border-radius: var(--mui-radius-md);
  box-shadow: var(--mui-shadow-lg);
  padding: 0.25rem;
  z-index: 51;
}

.mui-menu__sub[hidden] {
  display: none;
}

/* Reveal submenu on hover or when the trigger is expanded via JS */
.mui-menu__sub-wrapper:hover > .mui-menu__sub,
.mui-menu__sub-wrapper:focus-within > .mui-menu__sub,
.mui-menu__sub-trigger[aria-expanded="true"] + .mui-menu__sub {
  display: block;
}

/* Flip submenus to the opposite side in RTL contexts (Radix-style data-side) */
[data-side="inline-end"] .mui-menu__sub {
  inset-inline-start: auto;
  inset-inline-end: 100%;
}