Component

Dropdown Menu Radio Group

Mutually exclusive menu options (role=menuitemradio + aria-checked) for settings like theme or sort order — exactly one checked, controlled or uncontrolled.

  • Component
  • React
  • TSX
  • Tailwind CSS
  • MIT

What it does

Capabilities

  • Role=menu semantics
  • Focus management
  • Outside-pointer close
  • Viewport-aware placement
  • Role=menuitemradio
  • Aria-checked exclusivity
  • Controlled + uncontrolled
  • Aria-haspopup="menu" trigger
  • Role="menu" panel
  • Role="menuitem" items
  • Aria-expanded state
  • Focus restoration on close

Preview

Live component

Interactive preview
Open preview
9:41 100%
devsnips.site/library/react/components/dropdowns/dropdown-menu-radio/
fluid · no fixed width 100%
No dependencies Responsive · Light/dark · Focus-visible

Install

Add to your project

terminal
npx devsnips add React/Components/Dropdowns/dropdown-menu-radio
registry path: React/Components/Dropdowns/dropdown-menu-radio

Source

Implementation

code.tsx
import {
  createContext,
  useContext,
  useEffect,
  useId,
  useLayoutEffect,
  useRef,
  useState,
} from "react";
import type {
  ButtonHTMLAttributes,
  HTMLAttributes,
  KeyboardEvent as ReactKeyboardEvent,
  MouseEvent as ReactMouseEvent,
  ReactNode,
  RefObject,
} from "react";

/**
 * DevSnips React Dropdown Menu — Radio Group.
 *
 * The shared menu core plus `<DropdownMenuRadioGroup>` +
 * `<DropdownMenuRadioItem>`: mutually exclusive options
 * (`role="menuitemradio"` + `aria-checked`) for settings like theme or
 * sort order. Controlled (`value` + `onValueChange`) and uncontrolled
 * (`defaultValue`) modes; exactly one option is checked at a time.
 */


function cx(...parts: Array<string | false | null | undefined>): string {
  return parts.filter(Boolean).join(" ");
}

export type DropdownMenuPlacement = "bottom-start" | "bottom-end" | "top-start" | "top-end";

const TRIGGER_CLASSES =
  "inline-flex h-9 max-w-full items-center gap-2 rounded-[var(--ds-radius-sm)] border border-[var(--ds-color-border)] bg-[var(--ds-color-surface)] px-3 text-sm font-medium leading-5 text-[var(--ds-color-foreground)] shadow-[var(--ds-shadow-xs)] transition-colors duration-150 ease-out hover:bg-[var(--ds-color-surface-hover)] aria-expanded:bg-[var(--ds-color-surface-hover)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)] disabled:pointer-events-none disabled:opacity-50 motion-reduce:transition-none";
const MENU_CLASSES =
  "absolute z-40 max-h-[min(20rem,calc(100vh-2rem))] min-w-[12rem] max-w-[calc(100vw-1.5rem)] overflow-y-auto rounded-[var(--ds-radius-md)] border border-[var(--ds-color-border)] bg-[var(--ds-color-surface-elevated)] p-1 shadow-[var(--ds-shadow-md)]";
// Layout/interaction only — the text/hover/focus tone is composed per item
// kind so the destructive tone never conflicts with the default tone.
const ITEM_CLASSES =
  "flex w-full items-center gap-2 rounded-[var(--ds-radius-sm)] px-2 py-1.5 text-left text-[13px] leading-5 transition-colors duration-150 ease-out focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[var(--ds-color-focus-ring)] disabled:pointer-events-none disabled:opacity-50 motion-reduce:transition-none";
const ITEM_TONE_DEFAULT =
  "text-[var(--ds-color-foreground)] hover:bg-[var(--ds-color-surface-hover)] focus:bg-[var(--ds-color-surface-hover)]";
const ITEM_TONE_DESTRUCTIVE =
  "text-[var(--ds-color-destructive)] hover:bg-[var(--ds-color-destructive-soft)] focus:bg-[var(--ds-color-destructive-soft)]";
const ICON_SLOT_CLASSES = "inline-flex shrink-0 [&_svg]:size-4";
const SHORTCUT_CLASSES =
  "ml-auto shrink-0 pl-6 text-xs leading-5 text-[var(--ds-color-muted-foreground)]";
const LABEL_CLASSES =
  "px-2 pb-1 pt-1.5 text-[11px] font-medium uppercase tracking-[0.05em] text-[var(--ds-color-muted-foreground)]";
const SEPARATOR_CLASSES = "mx-1 my-1 h-px bg-[var(--ds-color-border)]";

const PLACEMENT_CLASSES: Record<DropdownMenuPlacement, string> = {
  "bottom-start": "left-0 top-full mt-1.5",
  "bottom-end": "right-0 top-full mt-1.5",
  "top-start": "bottom-full left-0 mb-1.5",
  "top-end": "bottom-full right-0 mb-1.5",
};

/** Items owned directly by `content` (excludes items of nested open submenus). */
function menuItems(content: HTMLElement): HTMLElement[] {
  return Array.from(
    content.querySelectorAll<HTMLElement>(
      '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]',
    ),
  ).filter(
    (el) =>
      el.closest('[role="menu"]') === content &&
      !el.hasAttribute("disabled") &&
      el.getAttribute("aria-disabled") !== "true",
  );
}

function focusItem(items: HTMLElement[], index: number): void {
  if (items.length === 0) return;
  const wrapped = ((index % items.length) + items.length) % items.length;
  items[wrapped].focus();
}

function ChevronDown({ className }: { className?: string }) {
  return (
    <svg
      className={className}
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.75}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      focusable="false"
    >
      <path d="m6 9 6 6 6-6" />
    </svg>
  );
}

/* ------------------------------------------------------------------------ */
/* Root context                                                              */
/* ------------------------------------------------------------------------ */

interface DropdownMenuContextValue {
  open: boolean;
  initialFocus: "first" | "last";
  requestOpen(next: boolean, focusTarget?: "first" | "last"): void;
  closeMenu(options?: { refocus?: boolean }): void;
  triggerRef: RefObject<HTMLButtonElement>;
  rootRef: RefObject<HTMLDivElement>;
  triggerId: string;
  contentId: string;
  placement: DropdownMenuPlacement;
}

const DropdownMenuContext = createContext<DropdownMenuContextValue | null>(null);

function useDropdownMenu(component: string): DropdownMenuContextValue {
  const context = useContext(DropdownMenuContext);
  if (!context) {
    throw new Error(`<${component}> must be rendered inside <DropdownMenu>.`);
  }
  return context;
}

/**
 * Registry of open submenus at one menu level, so pointer interaction with a
 * sibling item can close the open submenu at that level. Provided by every
 * menu panel (`DropdownMenuContent`, and `DropdownMenuSubContent` in the
 * submenu variant).
 */
interface MenuLevelContextValue {
  registerSub(close: () => void): () => void;
  closeSubs(except?: () => void): void;
}

const MenuLevelContext = createContext<MenuLevelContextValue | null>(null);

function useMenuLevel(): MenuLevelContextValue {
  const subsRef = useRef<Set<() => void> | null>(null);
  const levelRef = useRef<MenuLevelContextValue | null>(null);
  if (!subsRef.current) subsRef.current = new Set();
  if (!levelRef.current) {
    const subs = subsRef.current;
    levelRef.current = {
      registerSub(close) {
        subs.add(close);
        return () => {
          subs.delete(close);
        };
      },
      closeSubs(except) {
        subs.forEach((close) => {
          if (close !== except) close();
        });
      },
    };
  }
  return levelRef.current;
}

/* ------------------------------------------------------------------------ */
/* DropdownMenu (root)                                                       */
/* ------------------------------------------------------------------------ */

export interface DropdownMenuProps {
  /** Open state (controlled). */
  open?: boolean;
  /** Initial open state (uncontrolled). */
  defaultOpen?: boolean;
  /** Called whenever the menu requests to open or close. */
  onOpenChange?: (open: boolean) => void;
  /** Preferred placement relative to the trigger; flips to stay in the viewport. */
  placement?: DropdownMenuPlacement;
  className?: string;
  children?: ReactNode;
}

export function DropdownMenu({
  open,
  defaultOpen = false,
  onOpenChange,
  placement = "bottom-start",
  className,
  children,
}: DropdownMenuProps) {
  const [internalOpen, setInternalOpen] = useState(defaultOpen);
  const [initialFocus, setInitialFocus] = useState<"first" | "last">("first");
  const isControlled = open !== undefined;
  const actualOpen = isControlled ? open : internalOpen;
  const triggerRef = useRef<HTMLButtonElement>(null);
  const rootRef = useRef<HTMLDivElement>(null);
  const reactId = useId();
  const triggerId = `ds-menu-trigger${reactId}`;
  const contentId = `ds-menu${reactId}`;

  function requestOpen(next: boolean, focusTarget: "first" | "last" = "first") {
    setInitialFocus(focusTarget);
    if (!isControlled) setInternalOpen(next);
    onOpenChange?.(next);
  }

  function closeMenu({ refocus = true }: { refocus?: boolean } = {}) {
    requestOpen(false);
    if (refocus) triggerRef.current?.focus();
  }

  // Outside pointer interaction closes the tree; listeners exist only while
  // open and are removed as soon as it closes.
  useEffect(() => {
    if (!actualOpen) return;
    function onPointerDown(event: PointerEvent) {
      if (rootRef.current && !rootRef.current.contains(event.target as Node)) {
        if (!isControlled) setInternalOpen(false);
        onOpenChange?.(false);
      }
    }
    document.addEventListener("pointerdown", onPointerDown);
    return () => document.removeEventListener("pointerdown", onPointerDown);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [actualOpen]);

  const value: DropdownMenuContextValue = {
    open: actualOpen,
    initialFocus,
    requestOpen,
    closeMenu,
    triggerRef,
    rootRef,
    triggerId,
    contentId,
    placement,
  };

  return (
    <DropdownMenuContext.Provider value={value}>
      <div ref={rootRef} className={cx("relative inline-flex", className)}>
        {children}
      </div>
    </DropdownMenuContext.Provider>
  );
}

export default DropdownMenu;

/* ------------------------------------------------------------------------ */
/* DropdownMenuTrigger                                                       */
/* ------------------------------------------------------------------------ */

export interface DropdownMenuTriggerProps
  extends ButtonHTMLAttributes<HTMLButtonElement> {
  children?: ReactNode;
}

export function DropdownMenuTrigger({
  children,
  className,
  onClick,
  onKeyDown,
  ...rest
}: DropdownMenuTriggerProps) {
  const context = useDropdownMenu("DropdownMenuTrigger");

  function handleClick(event: ReactMouseEvent<HTMLButtonElement>) {
    onClick?.(event);
    if (event.defaultPrevented) return;
    if (context.open) {
      context.closeMenu();
    } else {
      context.requestOpen(true, "first");
    }
  }

  function handleKeyDown(event: ReactKeyboardEvent<HTMLButtonElement>) {
    onKeyDown?.(event);
    if (event.defaultPrevented) return;
    if (event.key === "ArrowDown") {
      event.preventDefault();
      context.requestOpen(true, "first");
    } else if (event.key === "ArrowUp") {
      event.preventDefault();
      context.requestOpen(true, "last");
    }
  }

  return (
    <button
      type="button"
      ref={context.triggerRef}
      id={context.triggerId}
      aria-haspopup="menu"
      aria-expanded={context.open}
      aria-controls={context.open ? context.contentId : undefined}
      data-state={context.open ? "open" : "closed"}
      onClick={handleClick}
      onKeyDown={handleKeyDown}
      className={cx(TRIGGER_CLASSES, className)}
      {...rest}
    >
      <span className="min-w-0 truncate">{children}</span>
      <ChevronDown
        className={cx(
          "size-4 shrink-0 text-[var(--ds-color-muted-foreground)] transition-transform duration-150 ease-out motion-reduce:transition-none",
          context.open && "rotate-180",
        )}
      />
    </button>
  );
}

/* ------------------------------------------------------------------------ */
/* DropdownMenuContent                                                       */
/* ------------------------------------------------------------------------ */

export interface DropdownMenuContentProps extends HTMLAttributes<HTMLDivElement> {
  children?: ReactNode;
}

export function DropdownMenuContent({
  children,
  className,
  onKeyDown,
  ...rest
}: DropdownMenuContentProps) {
  const context = useDropdownMenu("DropdownMenuContent");
  const level = useMenuLevel();
  const contentRef = useRef<HTMLDivElement>(null);
  const [resolved, setResolved] = useState<DropdownMenuPlacement>(context.placement);
  const [measured, setMeasured] = useState(false);

  const open = context.open;

  // Move focus into the menu when it opens (first item, or last when the
  // trigger was invoked with ArrowUp).
  useEffect(() => {
    if (!open) return;
    const node = contentRef.current;
    if (!node) return;
    const items = menuItems(node);
    focusItem(items, context.initialFocus === "last" ? items.length - 1 : 0);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open]);

  // Measure against the viewport and flip the placement when the preferred
  // side/alignment would overflow. Runs before paint, so the flip never
  // flashes; the panel stays `invisible` until the first measurement.
  useLayoutEffect(() => {
    if (!open) {
      setMeasured(false);
      setResolved(context.placement);
      return;
    }
    const node = contentRef.current;
    const trigger = context.triggerRef.current;
    if (!node || !trigger) return;
    const t = trigger.getBoundingClientRect();
    const c = node.getBoundingClientRect();
    const margin = 8;
    const below = window.innerHeight - t.bottom;
    const above = t.top;
    let side: "top" | "bottom" = context.placement.startsWith("top") ? "top" : "bottom";
    if (side === "bottom" && below < c.height + margin && above > below) side = "top";
    else if (side === "top" && above < c.height + margin && below > above) side = "bottom";
    let align: "start" | "end" = context.placement.endsWith("end") ? "end" : "start";
    if (align === "start" && t.left + c.width > window.innerWidth - margin && t.right - c.width >= margin) {
      align = "end";
    } else if (align === "end" && t.right - c.width < margin && t.left + c.width <= window.innerWidth - margin) {
      align = "start";
    }
    setResolved(`${side}-${align}`);
    setMeasured(true);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, context.placement]);

  function handleKeyDown(event: ReactKeyboardEvent<HTMLDivElement>) {
    onKeyDown?.(event);
    if (event.defaultPrevented) return;
    const node = contentRef.current;
    if (!node) return;
    const items = menuItems(node);
    const current = items.indexOf(document.activeElement as HTMLElement);
    switch (event.key) {
      case "ArrowDown":
        event.preventDefault();
        event.stopPropagation();
        focusItem(items, current + 1);
        break;
      case "ArrowUp":
        event.preventDefault();
        event.stopPropagation();
        focusItem(items, current - 1);
        break;
      case "Home":
        event.preventDefault();
        event.stopPropagation();
        focusItem(items, 0);
        break;
      case "End":
        event.preventDefault();
        event.stopPropagation();
        focusItem(items, items.length - 1);
        break;
      case "Escape":
        event.preventDefault();
        event.stopPropagation();
        context.closeMenu();
        break;
      case "Tab":
        // Let focus leave naturally; close without stealing it back.
        context.closeMenu({ refocus: false });
        break;
      default:
        break;
    }
  }

  // Hovering a plain item closes the open submenu at this level.
  function handlePointerOver(event: ReactMouseEvent<HTMLDivElement>) {
    const node = contentRef.current;
    if (!node) return;
    const item = (event.target as HTMLElement).closest<HTMLElement>(
      '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]',
    );
    if (!item || item.closest('[role="menu"]') !== node) return;
    if (!item.hasAttribute("data-ds-subtrigger")) level.closeSubs();
  }

  if (!open) return null;

  return (
    <MenuLevelContext.Provider value={level}>
      <div
        ref={contentRef}
        id={context.contentId}
        role="menu"
        aria-labelledby={context.triggerId}
        tabIndex={-1}
        onKeyDown={handleKeyDown}
        onPointerOver={handlePointerOver}
        className={cx(MENU_CLASSES, PLACEMENT_CLASSES[resolved], !measured && "invisible", className)}
        {...rest}
      >
        {children}
      </div>
    </MenuLevelContext.Provider>
  );
}

/* ------------------------------------------------------------------------ */
/* DropdownMenuItem                                                          */
/* ------------------------------------------------------------------------ */

export interface DropdownMenuItemProps
  extends ButtonHTMLAttributes<HTMLButtonElement> {
  /** Meaningful leading icon (rendered aria-hidden). */
  icon?: ReactNode;
  /** Informational keyboard shortcut shown at the trailing edge (aria-hidden; exposed via aria-keyshortcuts). */
  shortcut?: string;
  /** Destructive action styling via the semantic destructive token. */
  destructive?: boolean;
  /** Whether activating the item closes the menu (default true). */
  closeOnSelect?: boolean;
  /** Called when the item is activated, before the menu closes. Call `event.preventDefault()` to keep the menu open. */
  onSelect?: (event: ReactMouseEvent<HTMLButtonElement>) => void;
  children?: ReactNode;
}

export function DropdownMenuItem({
  icon,
  shortcut,
  destructive = false,
  closeOnSelect = true,
  onSelect,
  onClick,
  onMouseEnter,
  className,
  children,
  ...rest
}: DropdownMenuItemProps) {
  const context = useDropdownMenu("DropdownMenuItem");

  function handleClick(event: ReactMouseEvent<HTMLButtonElement>) {
    onClick?.(event);
    if (event.defaultPrevented) return;
    onSelect?.(event);
    if (event.defaultPrevented) return;
    if (closeOnSelect) context.closeMenu();
  }

  function handleMouseEnter(event: ReactMouseEvent<HTMLButtonElement>) {
    onMouseEnter?.(event);
    event.currentTarget.focus();
  }

  return (
    <button
      type="button"
      role="menuitem"
      tabIndex={-1}
      aria-keyshortcuts={shortcut}
      onClick={handleClick}
      onMouseEnter={handleMouseEnter}
      className={cx(ITEM_CLASSES, destructive ? ITEM_TONE_DESTRUCTIVE : ITEM_TONE_DEFAULT, className)}
      {...rest}
    >
      {icon ? (
        <span
          aria-hidden="true"
          className={cx(
            ICON_SLOT_CLASSES,
            destructive ? "text-current" : "text-[var(--ds-color-muted-foreground)]",
          )}
        >
          {icon}
        </span>
      ) : null}
      <span className="min-w-0 flex-1 truncate">{children}</span>
      {shortcut ? (
        <span aria-hidden="true" className={SHORTCUT_CLASSES}>
          {shortcut}
        </span>
      ) : null}
    </button>
  );
}

/* ------------------------------------------------------------------------ */
/* DropdownMenuLabel / DropdownMenuGroup / DropdownMenuSeparator             */
/* ------------------------------------------------------------------------ */

export interface DropdownMenuLabelProps extends HTMLAttributes<HTMLDivElement> {
  children?: ReactNode;
}

export function DropdownMenuLabel({ className, children, ...rest }: DropdownMenuLabelProps) {
  return (
    <div className={cx(LABEL_CLASSES, className)} {...rest}>
      {children}
    </div>
  );
}

export interface DropdownMenuGroupProps extends HTMLAttributes<HTMLDivElement> {
  children?: ReactNode;
}

export function DropdownMenuGroup({ className, children, ...rest }: DropdownMenuGroupProps) {
  return (
    <div role="group" className={className} {...rest}>
      {children}
    </div>
  );
}

export function DropdownMenuSeparator({
  className,
  ...rest
}: HTMLAttributes<HTMLDivElement>) {
  return (
    <div
      role="separator"
      aria-orientation="horizontal"
      className={cx(SEPARATOR_CLASSES, className)}
      {...rest}
    />
  );
}

/* ------------------------------------------------------------------------ */
/* DropdownMenuRadioGroup / DropdownMenuRadioItem                            */
/* ------------------------------------------------------------------------ */

interface DropdownMenuRadioGroupContextValue {
  value: string;
  select(next: string): void;
}

const DropdownMenuRadioGroupContext =
  createContext<DropdownMenuRadioGroupContextValue | null>(null);

export interface DropdownMenuRadioGroupProps extends HTMLAttributes<HTMLDivElement> {
  /** Selected value (controlled). */
  value?: string;
  /** Initial selected value (uncontrolled). */
  defaultValue?: string;
  /** Called with the newly selected value. */
  onValueChange?: (value: string) => void;
  children?: ReactNode;
}

/** Groups mutually exclusive `DropdownMenuRadioItem` options (`role="group"`). */
export function DropdownMenuRadioGroup({
  value,
  defaultValue = "",
  onValueChange,
  children,
  ...rest
}: DropdownMenuRadioGroupProps) {
  const [internalValue, setInternalValue] = useState(defaultValue);
  const isControlled = value !== undefined;
  const actualValue = isControlled ? value : internalValue;

  function select(next: string) {
    if (!isControlled) setInternalValue(next);
    onValueChange?.(next);
  }

  return (
    <DropdownMenuRadioGroupContext.Provider value={{ value: actualValue, select }}>
      <div role="group" {...rest}>
        {children}
      </div>
    </DropdownMenuRadioGroupContext.Provider>
  );
}

export interface DropdownMenuRadioItemProps
  extends ButtonHTMLAttributes<HTMLButtonElement> {
  /** The value this option represents inside the surrounding `DropdownMenuRadioGroup`. */
  value: string;
  /** Whether activating the item closes the menu (default false). */
  closeOnSelect?: boolean;
  /** Called when the item is activated, after the group value updates. Call `event.preventDefault()` to keep the menu open. */
  onSelect?: (event: ReactMouseEvent<HTMLButtonElement>) => void;
  children?: ReactNode;
}

/**
 * One option in a radio group (`role="menuitemradio"` + `aria-checked`).
 * Exactly one item in the group is checked; the dot indicator is aria-hidden
 * decoration tracking that state.
 */
export function DropdownMenuRadioItem({
  value,
  closeOnSelect = false,
  onSelect,
  onClick,
  onMouseEnter,
  className,
  children,
  ...rest
}: DropdownMenuRadioItemProps) {
  const menu = useDropdownMenu("DropdownMenuRadioItem");
  const group = useContext(DropdownMenuRadioGroupContext);
  if (!group) {
    throw new Error("<DropdownMenuRadioItem> must be rendered inside <DropdownMenuRadioGroup>.");
  }
  const checked = group.value === value;

  function handleClick(event: ReactMouseEvent<HTMLButtonElement>) {
    onClick?.(event);
    if (event.defaultPrevented) return;
    group?.select(value);
    onSelect?.(event);
    if (event.defaultPrevented) return;
    if (closeOnSelect) menu.closeMenu();
  }

  function handleMouseEnter(event: ReactMouseEvent<HTMLButtonElement>) {
    onMouseEnter?.(event);
    event.currentTarget.focus();
  }

  return (
    <button
      type="button"
      role="menuitemradio"
      aria-checked={checked}
      tabIndex={-1}
      onClick={handleClick}
      onMouseEnter={handleMouseEnter}
      className={cx(ITEM_CLASSES, ITEM_TONE_DEFAULT, className)}
      {...rest}
    >
      <span
        aria-hidden="true"
        className={cx(
          "inline-flex size-4 shrink-0 items-center justify-center text-[var(--ds-color-primary)] transition-opacity duration-150 ease-out motion-reduce:transition-none [&_svg]:size-3",
          checked ? "opacity-100" : "opacity-0",
        )}
      >
        <svg viewBox="0 0 24 24" fill="currentColor">
          <circle cx="12" cy="12" r="5" />
        </svg>
      </span>
      <span className="min-w-0 flex-1 truncate">{children}</span>
    </button>
  );
}
689 lines UTF-8 · LF · Spaces: 2
DevSnips AI ready Claude · Cursor · Windsurf

Agent skill & Model Context Protocol

Pass this component straight into your AI workflow. DevSnips exposes each resource as a structured, addressable registry entry, so your agent installs the real files instead of approximating them.

devsnips://components/dropdowns/dropdown-menu-radio
Prompt instruction
I want to use this DevSnips resource in production:

Page:
https://devsnips.site/library/react/components/dropdowns/dropdown-menu-radio/

Install: npx devsnips add
React/Components/Dropdowns/dropdown-menu-radio

This is the Dropdown Menu Radio Group — Mutually exclusive menu options (role=menuitemradio + aria-checked) for settings like theme or sort order — exactly one checked, controlled or uncontrolled.

Use this DevSnips resource as the starting point for my project.
Adapt it to my project's stack and conventions, fix any issues,
and make it production-ready while preserving its intended design
and behavior.

Paste into ChatGPT, Claude or your agent — it already carries the page URL and the exact install path.

Resource URI
devsnips://components/dropdowns/dropdown-menu-radio