Skip to content

Popover Menu

Rounded dropdown menu (Radix DropdownMenu) with emoji items, keyboard hints, soft highlight, separators and a red danger item; pops in with a bounce.

soft-flat/ui/popover-menu
Open ↗

Source

"use client";

import type { ReactNode } from "react";
import { DropdownMenu as DM } from "radix-ui";
import { cn } from "@/lib/utils";

export interface PopoverMenuItem {
  label?: string;
  emoji?: string;
  hint?: string;
  danger?: boolean;
  separator?: boolean;
  onSelect?: () => void;
}

export interface PopoverMenuProps {
  trigger: ReactNode;
  items?: PopoverMenuItem[];
  align?: "start" | "center" | "end";
  da?: string;
}

/** Rounded dropdown menu (Radix DropdownMenu): emoji items with hints, soft highlight, separators and a red danger item; pops in with a bounce. */
export function PopoverMenu({
  trigger,
  items = [
    { label: "Rename", emoji: "✏️", hint: "R" },
    { label: "Duplicate", emoji: "📄", hint: "D" },
    { label: "Change color", emoji: "🎨" },
    { label: "Move to…", emoji: "📦" },
    { separator: true },
    { label: "Archive", emoji: "🗄️" },
    { label: "Delete board", emoji: "🗑️", danger: true },
  ],
  align = "end",
  da = "soft-flat",
}: PopoverMenuProps) {
  return (
    <DM.Root>
      <DM.Trigger asChild>{trigger}</DM.Trigger>
      <DM.Portal>
        <DM.Content
          data-da={da}
          align={align}
          sideOffset={8}
          collisionPadding={12}
          className="z-50 min-w-56 rounded-da-lg bg-da-surface p-2 text-da-surface-fg shadow-da-lg transition-[opacity,scale] duration-(--da-duration) ease-da-emphasized starting:scale-90 starting:opacity-0"
        >
          {items.map((it, i) =>
            it.separator ? (
              <DM.Separator key={i} className="mx-2 my-1.5 h-px bg-da-border" />
            ) : (
              <DM.Item
                key={i}
                onSelect={it.onSelect}
                className={cn(
                  "flex cursor-default items-center gap-3 rounded-da-md px-3 py-2.5 text-[15px] font-medium outline-none select-none data-highlighted:bg-da-surface-2",
                  it.danger && "text-da-danger data-highlighted:bg-da-danger/10",
                )}
              >
                {it.emoji && <span aria-hidden>{it.emoji}</span>}
                {it.label}
                {it.hint && <kbd className="ml-auto rounded-da-sm bg-da-surface-2 px-1.5 font-da-mono text-xs text-da-muted-fg">{it.hint}</kbd>}
              </DM.Item>
            ),
          )}
        </DM.Content>
      </DM.Portal>
    </DM.Root>
  );
}

export default PopoverMenu;

modules/soft-flat/ui/popover-menu/index.tsx

Props

PropTypeDefaultDescription
trigger*ReactNode—Opener.
items{ label?, emoji?, hint?, danger?, separator?, onSelect? }[]—Items.
align"start" | "center" | "end"—Alignment.
dastring—DA scope for the portal.

Other modal variants in Soft Flat

Modal in other art directions

Confirm Dialog

Destructive confirmation (Radix AlertDialog) over a hazard-striped overlay: red 'Danger zone' band, consequences list, and a type-the-name field that arms the delete button; async pending state.

BrutalistModal

Drawer

Side sheet (Radix Dialog) sliding in from the right or left in stepped motion: yellow title band with close square, scrollable body for forms, ruled footer actions.

BrutalistModal

Modal

Accessible dialog on Radix Dialog: colored title band (primary or danger), close button, scrollable body, footer action bar. Re-applies the DA scope inside the portal. Stepped drop-in entrance.

BrutalistModal

Onboarding Modal

Multi-step onboarding dialog: a colored visual panel (giant step number by default) next to title, description, segmented progress bar and Back/Next buttons; resets on open.

BrutalistModal

Bottom Sheet

Mobile-style bottom sheet (Radix Dialog): heavily frosted panel sliding up with a grab handle, title and description; becomes a floating centered card on wider screens.

GlassModal

Dropdown Menu

Frosted dropdown menu (Radix DropdownMenu) built from an items array: labels, icons, shortcuts, separators, checkbox items, nested submenus and a danger item.

GlassModal

Modal

Frosted glass sheet dialog (Radix): 40px backdrop blur, optional icon bubble, rise-and-pop entrance with overshoot, blurred overlay, round close button and footer actions.

GlassModal

Share Dialog

Glass share dialog: invite-by-email field with role select, people-with-access list, a general access switch (invited only / anyone with link) and a copy-link button with confirmation.

GlassModal

Command Palette

⌘K command palette (Radix Dialog): search input, results filtered as you type and grouped (actions, incidents, schedules), icons and shortcuts, arrow-key highlight, Enter to run, footer hints.

MinimalModal

Modal

Radix-based dialog with fade + subtle scale entrance, optional icon, title/description, scrollable body and a tinted footer action bar. Re-applies the DA scope inside the portal.

MinimalModal

Popover Card

Anchored popover (Radix Popover) with arrow, optional title + close button and any content — quick forms (snooze), definitions or details. Collision-aware placement.

MinimalModal

Sheet

Side sheet (Radix Dialog) sliding in from the right or left over a dimmed overlay: title and description header with close button, scrollable body and footer actions.

MinimalModal

Command Dialog

Command palette (Radix Dialog + listbox): a square panel near the top with a large borderless search, grouped results under mono headings, arrow-key navigation with an inverted active row and shortcut hints.

Mono CleanModal

Drawer

Right side panel (Radix Dialog) sliding in over a dimmed page: mono top bar with label and “Close”, large title, scrollable body with ruled rows and a sticky footer.

Mono CleanModal

Lightbox

Full-screen image viewer (Radix Dialog) on ink (stays dark in dark mode): the image frame centered, mono “03 / 04” counter and caption, Prev/Next text buttons and arrow-key navigation, thumbnails strip.

Mono CleanModal

Modal

Square dialog (Radix): a mono label + text “Close” in the top bar, a full-ink rule, a large title, body and right-aligned actions; opens with a quick fade and 8px rise.

Mono CleanModal

Confirm Dialog

Destructive confirmation (Radix AlertDialog): red warning icon, title, consequence text, optional “type VOID to confirm” guard, cancel + red confirm.

Neo CorporateModal

Modal

Corporate dialog (Radix): white card with header (title, description, close), divided scrollable body and a tinted footer bar for actions; quick fade + slight scale.

Neo CorporateModal

Payment Modal

Pay-invoice dialog: amount summary, card/bank method radio cards, method-specific fields, secure note and a pay button that switches to a success state.

Neo CorporateModal

Side Sheet

Detail drawer (Radix Dialog) sliding in from the right: full-height bordered panel with sticky header, scrollable body and footer actions — for record details.

Neo CorporateModal

Bottom Sheet

Bottom sheet (Radix Dialog): slides up from the bottom with a grab handle and very rounded top corners; becomes a centered card from sm up.

Organic SoftModal

Confirm Dialog

Gentle confirmation (Radix AlertDialog): a wilting-leaf illustration in a clay circle, serif question, kind consequence text, a sage “keep” action and a quiet clay confirm.

Organic SoftModal

Modal

Soft dialog (Radix): large-radius cream card that rises gently with a slight overshoot, serif title, round close button and pill actions.

Organic SoftModal

Welcome Modal

Onboarding dialog: an illustrated tinted header (sprout growing with each step), serif title and copy, leaf-shaped step dots, Back/Next pills and “Let’s begin” on the last step.

Organic SoftModal