diff --git a/packages/demo/src/components/demo/dropdown-menu.tsx b/packages/demo/src/components/demo/dropdown-menu.tsx
index 8cc5e5dc..01b14d7d 100644
--- a/packages/demo/src/components/demo/dropdown-menu.tsx
+++ b/packages/demo/src/components/demo/dropdown-menu.tsx
@@ -37,203 +37,222 @@ export const DropdownMenuDemo = ({
if (variant === "default") {
return (
-
-
-
-
-
- Profile
- Settings
- Logout
-
-
+
+
+
+
+
+
+ Profile
+ Settings
+ Logout
+
+
+
);
}
if (variant === "with-separators") {
return (
-
-
-
-
-
- My Account
-
-
-
- Profile
-
-
-
- Settings
-
-
-
-
- Logout
-
-
-
+
+
+
+
+
+
+ My Account
+
+
+
+ Profile
+
+
+
+ Settings
+
+
+
+
+ Logout
+
+
+
+
);
}
if (variant === "with-checkboxes") {
return (
-
-
-
-
-
- Appearance
-
-
- Status Bar
-
-
- Activity Bar
-
-
- Panel
-
-
-
+
+
+
+
+
+
+ Appearance
+
+
+ Status Bar
+
+
+ Activity Bar
+
+
+ Panel
+
+
+
+
);
}
if (variant === "with-radio") {
return (
-
-
-
-
-
- Panel Position
-
-
- Top
- Bottom
- Right
-
-
-
+
+
+
+
+
+
+ Panel Position
+
+
+ Top
+
+ Bottom
+
+ Right
+
+
+
+
);
}
if (variant === "with-shortcuts") {
return (
-
-
-
-
-
-
- New Tab
- ⌘T
-
-
- New Window
- ⌘N
-
-
- Close Tab
- ⌘W
-
-
-
- Print
- ⌘P
-
-
-
+
+
+
+
+
+
+
+ New Tab
+ ⌘T
+
+
+ New Window
+ ⌘N
+
+
+ Close Tab
+ ⌘W
+
+
+
+ Print
+ ⌘P
+
+
+
+
);
}
if (variant === "with-submenu") {
return (
-
-
-
-
-
- Back
- Forward
- Reload
-
-
-
- More Tools
-
-
- Save Page As...
- Create Shortcut...
- Name Window...
-
- Developer Tools
-
-
-
- Settings
-
-
+
+
+
+
+
+
+ Back
+ Forward
+ Reload
+
+
+
+ More Tools
+
+
+ Save Page As...
+ Create Shortcut...
+ Name Window...
+
+ Developer Tools
+
+
+
+ Settings
+
+
+
);
}
if (variant === "with-groups") {
return (
-
-
-
-
-
-
- File
-
- New File
- ⌘N
-
-
- Open File
- ⌘O
-
-
- Save
- ⌘S
-
-
-
-
- Edit
- Undo
- Redo
- Cut
- Copy
- Paste
-
-
-
+
+
+
+
+
+
+
+ File
+
+ New File
+ ⌘N
+
+
+ Open File
+ ⌘O
+
+
+ Save
+ ⌘S
+
+
+
+
+ Edit
+ Undo
+ Redo
+ Cut
+ Copy
+ Paste
+
+
+
+
);
}
diff --git a/packages/demo/src/content/components/dropdown-menu.mdx b/packages/demo/src/content/components/dropdown-menu.mdx
index 4a19b570..2d6fcc5c 100644
--- a/packages/demo/src/content/components/dropdown-menu.mdx
+++ b/packages/demo/src/content/components/dropdown-menu.mdx
@@ -1,38 +1,237 @@
---
layout: "@demo/layouts/mdx-layout.astro"
heading: "Dropdown Menu"
-description: "Dropdown Menu with sizes and variants"
+description: "A menu of actions or options triggered by a button"
---
import { DropdownMenuDemo } from "@demo/components/demo/dropdown-menu";
-import { ELEVATION } from "@eqtylab/equality";
-## Variants
+## Overview
+
+A dropdown menu displays a list of actions or options in a floating panel anchored to a trigger. Use it for contextual actions, account menus, view options, and settings. It supports labels, separators, checkboxes, radio groups, keyboard shortcuts, grouping, and nested submenus, and is fully keyboard navigable.
+
+## Usage
+
+Import the components:
+
+```ts
+import {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+} from "@eqtylab/equality";
+```
+
+Basic usage:
-### Default
+```jsx
+
+
+
+
+
+ Profile
+ Settings
+ Logout
+
+
+```
+## Variants
+
### With Separators and Labels
+Use `DropdownMenuLabel` to title a section and `DropdownMenuSeparator` to divide groups of items. Items accept icons as children alongside a `` label.
+
+```jsx
+
+ My Account
+
+
+
+ Profile
+
+
+
+ Settings
+
+
+
+
+ Logout
+
+
+```
+
### With Checkboxes
+Use `DropdownMenuCheckboxItem` for options that toggle on and off independently. Control each item with `checked` and `onCheckedChange`.
+
+```jsx
+const [showStatusBar, setShowStatusBar] = useState(true);
+
+
+ Status Bar
+;
+```
+
### With Radio Items
+Use `DropdownMenuRadioGroup` with `DropdownMenuRadioItem` to select a single option from a set. The group is controlled with `value` and `onValueChange`.
+
+```jsx
+const [position, setPosition] = useState("bottom");
+
+
+ Top
+ Bottom
+ Right
+;
+```
+
### With Shortcuts
+Use `DropdownMenuShortcut` to display a keyboard shortcut hint aligned to the end of an item.
+
+```jsx
+
+ New Tab
+ ⌘T
+
+```
+
### With Submenu
+Use `DropdownMenuSub`, `DropdownMenuSubTrigger`, and `DropdownMenuSubContent` to nest a menu inside an item. The submenu opens on hover or keyboard focus.
+
+```jsx
+
+
+ More Tools
+
+
+ Save Page As...
+ Create Shortcut...
+
+ Developer Tools
+
+
+```
+
### With Groups
+Wrap related items in `DropdownMenuGroup` to associate a label with its items for assistive technology.
+
+
+```jsx
+
+ File
+
+ New File
+ ⌘N
+
+
+ Open File
+ ⌘O
+
+
+```
+
+## Slots
+
+| Name | Description |
+| -------------------------- | ------------------------------------------------ |
+| `DropdownMenu` | Root component, manages open state |
+| `DropdownMenuTrigger` | Element that opens the menu on click |
+| `DropdownMenuContent` | Floating panel containing the menu items |
+| `DropdownMenuItem` | A single actionable menu item |
+| `DropdownMenuCheckboxItem` | A toggleable item with a checkmark indicator |
+| `DropdownMenuRadioGroup` | Groups radio items into a single-select set |
+| `DropdownMenuRadioItem` | A single-select item within a radio group |
+| `DropdownMenuLabel` | Non-interactive section title |
+| `DropdownMenuSeparator` | Divider between groups of items |
+| `DropdownMenuShortcut` | Keyboard shortcut hint aligned to the item's end |
+| `DropdownMenuGroup` | Groups related items for assistive technology |
+| `DropdownMenuSub` | Root for a nested submenu |
+| `DropdownMenuSubTrigger` | Item that opens a nested submenu |
+| `DropdownMenuSubContent` | Floating panel for a nested submenu |
+
+## Props
+
+### DropdownMenu
+
+| Name | Description | Type | Default | Required |
+| -------------- | ---------------------------------- | ------------------------- | ------- | -------- |
+| `open` | Controls the open state | `boolean` | - | ❌ |
+| `defaultOpen` | Open state when initially rendered | `boolean` | - | ❌ |
+| `onOpenChange` | Called when the open state changes | `(open: boolean) => void` | - | ❌ |
+
+### DropdownMenuTrigger
+
+| Name | Description | Type | Default | Required |
+| --------- | -------------------------------------------------------- | --------- | ------- | -------- |
+| `asChild` | Merge props onto the child instead of rendering a button | `boolean` | `false` | ❌ |
+
+### DropdownMenuContent
+
+| Name | Description | Type | Default | Required |
+| ------------ | ----------------------------------- | -------------------------------- | -------- | -------- |
+| `align` | Alignment against the trigger | `start`, `center`, `end` | `center` | ❌ |
+| `side` | Side of the trigger to render on | `top`, `right`, `bottom`, `left` | `bottom` | ❌ |
+| `sideOffset` | Distance in pixels from the trigger | `number` | `4` | ❌ |
+
+### DropdownMenuItem
+
+| Name | Description | Type | Default | Required |
+| ---------- | ----------------------------------------------------- | ------------ | ------- | -------- |
+| `danger` | Styles the item as a destructive action | `boolean` | `false` | ❌ |
+| `inset` | Adds left padding to align with items that have icons | `boolean` | `false` | ❌ |
+| `disabled` | Prevents interaction and dims the item | `boolean` | `false` | ❌ |
+| `onSelect` | Called when the item is selected | `() => void` | - | ❌ |
+
+### DropdownMenuCheckboxItem
+
+| Name | Description | Type | Default | Required |
+| ----------------- | -------------------------------------- | ---------------------------- | ------- | -------- |
+| `checked` | Whether the item is checked | `boolean` | - | ❌ |
+| `onCheckedChange` | Called when the checked state changes | `(checked: boolean) => void` | - | ❌ |
+| `disabled` | Prevents interaction and dims the item | `boolean` | `false` | ❌ |
+
+### DropdownMenuRadioGroup
+
+| Name | Description | Type | Default | Required |
+| --------------- | -------------------------------------- | ------------------------- | ------- | -------- |
+| `value` | The value of the selected item | `string` | - | ❌ |
+| `onValueChange` | Called when the selected value changes | `(value: string) => void` | - | ❌ |
+
+### DropdownMenuRadioItem
+
+| Name | Description | Type | Default | Required |
+| ---------- | -------------------------------------- | --------- | ------- | -------- |
+| `value` | The unique value of the item | `string` | - | ✅ |
+| `disabled` | Prevents interaction and dims the item | `boolean` | `false` | ❌ |
+
+### DropdownMenuLabel & DropdownMenuSubTrigger
+
+| Name | Description | Type | Default | Required |
+| ------- | ----------------------------------------------------- | --------- | ------- | -------- |
+| `inset` | Adds left padding to align with items that have icons | `boolean` | `false` | ❌ |
diff --git a/packages/tokens/equality-tokens.json b/packages/tokens/equality-tokens.json
index 993250b6..b2ea0e4a 100644
--- a/packages/tokens/equality-tokens.json
+++ b/packages/tokens/equality-tokens.json
@@ -876,6 +876,14 @@
}
},
"text": {
+ "danger": {
+ "$type": "color",
+ "$value": {
+ "colorSpace": "display-p3",
+ "components": [0.8779, 0.5865, 0.5474],
+ "hex": "#E0968C"
+ }
+ },
"link": {
"$type": "color",
"$value": {
@@ -2926,6 +2934,14 @@
}
},
"text": {
+ "danger": {
+ "$type": "color",
+ "$value": {
+ "colorSpace": "display-p3",
+ "components": [0.5698, 0.1873, 0.1674],
+ "hex": "#91302B"
+ }
+ },
"link": {
"$type": "color",
"$value": {
diff --git a/packages/tokens/equality-tokens.pinwheel b/packages/tokens/equality-tokens.pinwheel
index 336a0b84..3f5dd983 100644
--- a/packages/tokens/equality-tokens.pinwheel
+++ b/packages/tokens/equality-tokens.pinwheel
@@ -1425,7 +1425,6 @@
3AF4332D-089A-4B0F-B382-199809A5D0F8
Badge
- 1
1
1B345E6F-7BFA-4A29-A8DE-903B2EB193A0
@@ -1611,7 +1610,6 @@
DA9E770F-29ED-4A78-9EDB-DD9F456BB566
Background
- 1
1
Primary
@@ -1794,7 +1792,6 @@
F4FC786D-8F79-4199-A3D3-35667F6E2DCD
Resource
- 1
1
Agent
@@ -2622,6 +2619,7 @@
4EC67953-4547-4883-B947-D73CF2D3DF18
Text
+ 1
1
Primary
@@ -2665,6 +2663,17 @@
2
86B9D7D8-F74A-4CAE-B006-65451BBEDAB9
+
+ Danger
+ 3
+ 3
+ lightanddark
+ CCD80E1D-24ED-4193-A845-891C968555AF
+ 5
+ CCD80E1D-24ED-4193-A845-891C968555AF
+ 2
+ 86B9D7D8-F74A-4CAE-B006-65451BBEDAB9
+
635BED27-D972-4FC5-BD7E-AE05C08B92FD
@@ -2696,7 +2705,6 @@
C2525A18-4A97-4EEC-BE78-A03DF3113E10
Brand
- 1
1
Primary
diff --git a/packages/ui/src/components/command/command.module.css b/packages/ui/src/components/command/command.module.css
index d0c28b54..a88d7762 100644
--- a/packages/ui/src/components/command/command.module.css
+++ b/packages/ui/src/components/command/command.module.css
@@ -74,7 +74,7 @@
@apply cursor-pointer select-none;
@apply [&_svg:not([class*='text-'])]:text-text-secondary [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0;
@apply data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50;
- @apply data-[selected=true]:bg-mixed-light;
+ @apply dark:data-[selected=true]:bg-mixed-light data-[selected=true]:bg-mixed-dark;
}
.command-shortcut {
diff --git a/packages/ui/src/components/dropdown-menu/dropdown-menu.module.css b/packages/ui/src/components/dropdown-menu/dropdown-menu.module.css
index b4a794b7..572b21cc 100644
--- a/packages/ui/src/components/dropdown-menu/dropdown-menu.module.css
+++ b/packages/ui/src/components/dropdown-menu/dropdown-menu.module.css
@@ -30,6 +30,25 @@
@apply dropdown-item;
}
+.dropdown-menu-trigger,
+.dropdown-menu-item,
+.dropdown-menu-checkbox-item,
+.dropdown-menu-radio-item {
+ @apply hover:not-disabled:text-lilac-700! focus:text-lilac-700!;
+ @apply hover:not-disabled:bg-lilac-300/50! focus:bg-lilac-300/50!;
+ @apply hover:not-disabled:shadow-sm;
+ @apply dark:hover:not-disabled:bg-lilac-600/50! dark:focus:bg-lilac-600/50!;
+ @apply dark:hover:not-disabled:text-lilac-100! dark:focus:text-lilac-100!;
+}
+
+.dropdown-menu-item-danger {
+ @apply text-text-danger!;
+ @apply hover:not-disabled:text-red-700! focus:text-red-700!;
+ @apply hover:not-disabled:bg-red-300/50! focus:bg-red-300/50!;
+ @apply dark:hover:not-disabled:bg-red-600/50! dark:focus:bg-red-600/50!;
+ @apply dark:hover:not-disabled:text-red-100! dark:focus:text-red-100!;
+}
+
.dropdown-menu-item-indicator-checkbox,
.dropdown-menu-item-indicator-radio {
@apply absolute left-2 size-3.5;
diff --git a/packages/ui/src/components/dropdown-menu/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu/dropdown-menu.tsx
index 7dc187f9..352d6995 100644
--- a/packages/ui/src/components/dropdown-menu/dropdown-menu.tsx
+++ b/packages/ui/src/components/dropdown-menu/dropdown-menu.tsx
@@ -77,11 +77,17 @@ const DropdownMenuItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
inset?: boolean;
+ danger?: boolean;
}
->(({ className, inset, ...props }, ref) => (
+>(({ className, inset, danger, ...props }, ref) => (
));
diff --git a/packages/ui/src/components/table/table-components.module.css b/packages/ui/src/components/table/table-components.module.css
index 7762c831..e264953d 100644
--- a/packages/ui/src/components/table/table-components.module.css
+++ b/packages/ui/src/components/table/table-components.module.css
@@ -61,8 +61,8 @@
.table-row--clickable:not([data-state='selected']) {
--mix-color: var(--color-brand-primary);
- --hover-darken: 50%;
- --hover-lighten: 50%;
+ --hover-darken: 55%;
+ --hover-lighten: 55%;
@apply cursor-pointer;
}
@@ -70,7 +70,7 @@
.table-row--clickable:not([data-state='selected']):hover:not(
:has(button:hover, a:not(.table-row-link):hover)
) {
- @apply bg-mixed-light! dark:bg-mixed-dark!;
+ @apply dark:bg-mixed-dark! bg-mixed-light!;
}
/* Linked Row (stretched link pattern) */
diff --git a/packages/ui/src/theme/generated/color-vars.css b/packages/ui/src/theme/generated/color-vars.css
index 9f517e8f..e4903062 100644
--- a/packages/ui/src/theme/generated/color-vars.css
+++ b/packages/ui/src/theme/generated/color-vars.css
@@ -245,6 +245,7 @@
--color-teal-700: color(display-p3 0.1475 0.2946 0.2434);
--color-teal-800: color(display-p3 0.09443 0.1941 0.159);
--color-teal-900: color(display-p3 0.04463 0.1013 0.08089);
+ --color-text-danger: color(display-p3 0.5698 0.1873 0.1674);
--color-text-link: color(display-p3 0.4045 0.2306 0.6077);
--color-text-primary: color(display-p3 0 0 0);
--color-text-secondary: color(display-p3 0.1792 0.1792 0.1792);
@@ -367,6 +368,7 @@
--dark-color-resource-text-token: color(display-p3 0.8033 0.8266 0.7044);
--dark-color-resource-text-tool: color(display-p3 0.9358 0.751 0.7228);
--dark-color-resource-text-unknown: color(display-p3 0.8698 0.8698 0.8698);
+ --dark-color-text-danger: color(display-p3 0.8779 0.5865 0.5474);
--dark-color-text-link: color(display-p3 0.7319 0.6108 0.8873);
--dark-color-text-primary: color(display-p3 1 1 1);
--dark-color-text-secondary: color(display-p3 0.7432 0.7432 0.7432);
diff --git a/packages/ui/src/theme/generated/dark-tokens.css b/packages/ui/src/theme/generated/dark-tokens.css
index f0035dd5..6e01fab5 100644
--- a/packages/ui/src/theme/generated/dark-tokens.css
+++ b/packages/ui/src/theme/generated/dark-tokens.css
@@ -106,6 +106,7 @@
--color-resource-text-token: color(display-p3 0.8033 0.8266 0.7044);
--color-resource-text-tool: color(display-p3 0.9358 0.751 0.7228);
--color-resource-text-unknown: color(display-p3 0.8698 0.8698 0.8698);
+ --color-text-danger: color(display-p3 0.8779 0.5865 0.5474);
--color-text-link: color(display-p3 0.7319 0.6108 0.8873);
--color-text-primary: color(display-p3 1 1 1);
--color-text-secondary: color(display-p3 0.7432 0.7432 0.7432);
diff --git a/packages/ui/src/theme/generated/global-dark-tokens.css b/packages/ui/src/theme/generated/global-dark-tokens.css
index 595b3fe5..f3b6df24 100644
--- a/packages/ui/src/theme/generated/global-dark-tokens.css
+++ b/packages/ui/src/theme/generated/global-dark-tokens.css
@@ -106,6 +106,7 @@ html[data-equality-theme='dark'] {
--color-resource-text-token: color(display-p3 0.8033 0.8266 0.7044);
--color-resource-text-tool: color(display-p3 0.9358 0.751 0.7228);
--color-resource-text-unknown: color(display-p3 0.8698 0.8698 0.8698);
+ --color-text-danger: color(display-p3 0.8779 0.5865 0.5474);
--color-text-link: color(display-p3 0.7319 0.6108 0.8873);
--color-text-primary: color(display-p3 1 1 1);
--color-text-secondary: color(display-p3 0.7432 0.7432 0.7432);
diff --git a/packages/ui/src/theme/generated/global-light-tokens.css b/packages/ui/src/theme/generated/global-light-tokens.css
index caec31a1..15f1e492 100644
--- a/packages/ui/src/theme/generated/global-light-tokens.css
+++ b/packages/ui/src/theme/generated/global-light-tokens.css
@@ -249,6 +249,7 @@ html {
--color-teal-700: color(display-p3 0.1475 0.2946 0.2434);
--color-teal-800: color(display-p3 0.09443 0.1941 0.159);
--color-teal-900: color(display-p3 0.04463 0.1013 0.08089);
+ --color-text-danger: color(display-p3 0.5698 0.1873 0.1674);
--color-text-link: color(display-p3 0.4045 0.2306 0.6077);
--color-text-primary: color(display-p3 0 0 0);
--color-text-secondary: color(display-p3 0.1792 0.1792 0.1792);
diff --git a/packages/ui/src/theme/generated/light-tokens.css b/packages/ui/src/theme/generated/light-tokens.css
index da66a0c1..a6210c56 100644
--- a/packages/ui/src/theme/generated/light-tokens.css
+++ b/packages/ui/src/theme/generated/light-tokens.css
@@ -249,6 +249,7 @@
--color-teal-700: color(display-p3 0.1475 0.2946 0.2434);
--color-teal-800: color(display-p3 0.09443 0.1941 0.159);
--color-teal-900: color(display-p3 0.04463 0.1013 0.08089);
+ --color-text-danger: color(display-p3 0.5698 0.1873 0.1674);
--color-text-link: color(display-p3 0.4045 0.2306 0.6077);
--color-text-primary: color(display-p3 0 0 0);
--color-text-secondary: color(display-p3 0.1792 0.1792 0.1792);
diff --git a/packages/ui/src/theme/generated/tailwind-tokens.css b/packages/ui/src/theme/generated/tailwind-tokens.css
index b84fb449..2d1aaf9f 100644
--- a/packages/ui/src/theme/generated/tailwind-tokens.css
+++ b/packages/ui/src/theme/generated/tailwind-tokens.css
@@ -247,6 +247,7 @@
--color-teal-700: var(--color-teal-700);
--color-teal-800: var(--color-teal-800);
--color-teal-900: var(--color-teal-900);
+ --color-text-danger: var(--color-text-danger);
--color-text-link: var(--color-text-link);
--color-text-primary: var(--color-text-primary);
--color-text-secondary: var(--color-text-secondary);
diff --git a/packages/ui/src/theme/theme-utilities.css b/packages/ui/src/theme/theme-utilities.css
index 8d9d4e70..79111847 100644
--- a/packages/ui/src/theme/theme-utilities.css
+++ b/packages/ui/src/theme/theme-utilities.css
@@ -39,7 +39,7 @@
@apply text-sm transition-colors;
@apply relative rounded-sm;
@apply outline-hidden select-none;
- @apply hover:not-disabled:bg-mixed-light focus:bg-mixed-light;
+ @apply dark:hover:not-disabled:bg-mixed-light dark:focus:bg-mixed-light hover:not-disabled:bg-mixed-dark focus:bg-mixed-dark;
@apply data-disabled:pointer-events-none data-disabled:opacity-50;
}