Dropdown Menu

@sushindustries/ui

A menu hung off a button: the popover API for the layer, arrow keys for the list, and light-dismiss for free.

7 min read
View as MarkdownEdit on GitHub

Examples are the tab where the component is shown doing a job, not demonstrating a prop. The API tab already lists the props.

Viewport width
Phone22remthe floor, the narrowest this promises to work at
tanstacktanstack add https://adamjurek.com/r/tanstack/dropdown-menu.jsonshadcnpnpm dlx shadcn@latest add https://adamjurek.com/r/shadcn/dropdown-menu.json

Press Compare. The frames are real viewports, so a layout that breaks at 320 breaks here too rather than in somebody's hands.

The row menu in a table

This is the job the component was written for. The menu lives in a scrolling cell, which is exactly where a portal-free menu gets clipped - here it does not, because the top layer is positioned against the viewport and not against any ancestor.

import { DataTable, DropdownMenu } from "@sushindustries/ui";

export function Documents({ rows, run }) {
	return (
		<DataTable
			label="Documents"
			rows={rows}
			columns={[
				{ id: "title", header: "Title" },
				{
					id: "id",
					header: "",
					cell: (row) => (
						<DropdownMenu
							label="Actions"
							buttonClassName="btn btn-quiet btn-icon"
							align="end"
							items={[
								{ id: "open", label: "Open on the site", icon: "link" },
								{ id: "retitle", label: "Change title…", icon: "text" },
								{
									id: "remove",
									label: "Remove…",
									icon: "close",
									destructive: true,
									disabled: row.published,
								},
							]}
							onSelect={(action) => run(action, row)}
						/>
					),
				},
			]}
		/>
	);
}
tsx

What this example is not

Remove… is red and disabled on a published row, and neither of those is a confirmation - selecting it calls run immediately. Put a DialogDialogA native dialog driven by props: top layer, focus trap and Escape from the element, click-outside from here.@sushindustries/ui · layout behind that action if it needs a second look. align="end" is right because this is the last column; in the first column it would push the menu off the left edge, and the clamp would drag it back rather than align it.

UsesIconIconThe glyph set, generated from a Markdown table where every drawing carries its reason. One component, typed names, no icon font.@sushindustries/ui · content