From 1c66ec2a04c7daa8d78361e3fb1d636e4950ee1e Mon Sep 17 00:00:00 2001 From: Travis Reeder Date: Tue, 13 Oct 2026 22:36:07 -0700 Subject: [PATCH 1/3] fix(menu): align slotted icon sizing and gap with Material 3 specs --- demo/menu-demo.html | 37 ++++++++++++++++++++++++++++++++++++- menu/README.md | 16 ++++++++-------- menu/menu-item.js | 35 ++++++++++++++++++++++++++++++----- menu/menu.js | 41 ++++++++++++++++++++++++----------------- 4 files changed, 98 insertions(+), 31 deletions(-) diff --git a/demo/menu-demo.html b/demo/menu-demo.html index fa745b2..355c620 100644 --- a/demo/menu-demo.html +++ b/demo/menu-demo.html @@ -135,7 +135,7 @@

Material 3 Menus

Default (M3 Expressive)

Vertical Menu (Default)

- 44px item height, 16px container radius, 20px icons, inset 12px rounded pill selection, and shape-morphing + 48px item height, 16px container radius, 20px icons, inset 12px rounded pill selection, and shape-morphing motion. Applied by default to all <md-menu> elements.

@@ -280,6 +280,41 @@

Cascading Submenu

+ + +
+ Slotted Content +

Slotted Icons & Avatars

+

Demonstrates proper 20px spec containment for standard icons, slotted images/avatars, and SVG graphics.

+ +
+ + Open Slotted Menu + + + +
Travis Reeder
+ Avatar +
+ +
Standard Icon
+ settings +
+ +
SVG Graphic
+ + + +
+ + +
Trailing Indicator
+ folder + chevron_right +
+
+
+
diff --git a/menu/README.md b/menu/README.md index def76a0..885c91d 100644 --- a/menu/README.md +++ b/menu/README.md @@ -43,9 +43,9 @@ menu.close() The modern Material 3 Expressive vertical menu is enabled by default: -- **Item height**: 44px -- **Horizontal padding**: 16px -- **Element gap**: 12px +- **Item height**: 48px +- **Horizontal padding**: 12px +- **Element gap**: 8px - **Container corner radius**: 16px (`md.sys.shape.corner.large`) - **Item corner radius**: Inset pill with 12px outer corners (`md.sys.shape.corner.medium`) and 4px inner corners - **Icon size**: 20px (`md.comp.menus.item.leading/trailing-icon-size`) @@ -189,10 +189,10 @@ Submenus open next to their parent item without overlapping it. Submenus automat | `--md-menu-container-shape` | `4px` (baseline) / `16px` (vertical) | Menu surface corner radius | | `--md-menu-container-color` | `surface-container` (baseline) / `surface-container-low` (vertical) | Background color of menu container | | `--md-menu-container-max-width` | `280px` | Maximum container width | -| `--md-menu-item-one-line-container-height` | `48px` (baseline) / `44px` (vertical) | Height of a single-line menu item | -| `--md-menu-item-leading-space` | `12px` (baseline) / `16px` (vertical) | Left/leading padding inside menu item | -| `--md-menu-item-trailing-space` | `12px` (baseline) / `16px` (vertical) | Right/trailing padding inside menu item | -| `--md-menu-item-between-space` | `12px` | Spacing between icons and text inside item | -| `--md-menu-item-icon-size` | `24px` (baseline) / `20px` (vertical) | Sizing of slotted leading and trailing icons | +| `--md-menu-item-one-line-container-height` | `48px` | Height of a single-line menu item | +| `--md-menu-item-leading-space` | `12px` | Left/leading padding inside menu item | +| `--md-menu-item-trailing-space` | `12px` | Right/trailing padding inside menu item | +| `--md-menu-item-between-space` | `8px` | Spacing between icons and text inside item | +| `--md-menu-item-icon-size` | `20px` | Sizing of slotted leading and trailing icons | | `--md-menu-item-selected-container-color` | `secondary-container` (baseline/standard) / `tertiary` (vibrant) | Background color of selected item | | `--md-menu-item-selected-label-text-color` | `on-secondary-container` / `on-tertiary` (vibrant) | Label color of selected item | diff --git a/menu/menu-item.js b/menu/menu-item.js index dce9490..f0f9b71 100644 --- a/menu/menu-item.js +++ b/menu/menu-item.js @@ -96,7 +96,7 @@ export const menuItemStyles = css` ); letter-spacing: var(--md-menu-item-label-text-tracking, var(--md-sys-typescale-label-large-tracking, 0.1px)); min-height: var(--md-menu-item-one-line-container-height, 48px); - gap: var(--md-menu-item-between-space, 12px); + gap: var(--md-menu-item-between-space, 8px); padding-top: var(--md-menu-item-top-space, 8px); padding-bottom: var(--md-menu-item-bottom-space, 8px); padding-inline-start: var(--md-menu-item-leading-space, 12px); @@ -137,24 +137,49 @@ export const menuItemStyles = css` } :is([slot='start'], [slot='end'])::slotted(*) { fill: currentColor; - font-size: var(--md-menu-item-icon-size, 24px); - width: var(--md-menu-item-icon-size, 24px); - height: var(--md-menu-item-icon-size, 24px); + font-size: var(--md-menu-item-icon-size, 20px); + width: var(--md-menu-item-icon-size, 20px); + height: var(--md-menu-item-icon-size, 20px); + max-width: var(--md-menu-item-icon-size, 20px); + max-height: var(--md-menu-item-icon-size, 20px); + box-sizing: border-box; + object-fit: contain; + flex-shrink: 0; + } + :is([slot='start'], [slot='end'])::slotted(img) { + border-radius: inherit; + object-fit: cover; } :is([slot='start'], [slot='end'])::slotted(md-icon) { - --md-icon-size: var(--md-menu-item-icon-size, 24px); + --md-icon-size: var(--md-menu-item-icon-size, 20px); + font-size: var(--md-menu-item-icon-size, 20px); + font-variation-settings: 'opsz' 20; } [slot='start'] { color: var(--md-menu-item-leading-icon-color, var(--md-sys-color-on-surface-variant, #49454f)); display: inline-flex; align-items: center; justify-content: center; + width: var(--md-menu-item-icon-size, 20px); + height: var(--md-menu-item-icon-size, 20px); + min-width: var(--md-menu-item-icon-size, 20px); + min-height: var(--md-menu-item-icon-size, 20px); + max-width: var(--md-menu-item-icon-size, 20px); + max-height: var(--md-menu-item-icon-size, 20px); + flex-shrink: 0; } [slot='end'] { color: var(--md-menu-item-trailing-icon-color, var(--md-sys-color-on-surface-variant, #49454f)); display: inline-flex; align-items: center; justify-content: center; + width: var(--md-menu-item-icon-size, 20px); + height: var(--md-menu-item-icon-size, 20px); + min-width: var(--md-menu-item-icon-size, 20px); + min-height: var(--md-menu-item-icon-size, 20px); + max-width: var(--md-menu-item-icon-size, 20px); + max-height: var(--md-menu-item-icon-size, 20px); + flex-shrink: 0; } .list-item.selected { background-color: var(--md-menu-item-selected-container-color, var(--md-sys-color-secondary-container, #e8def8)); diff --git a/menu/menu.js b/menu/menu.js index 9279a11..421ad46 100644 --- a/menu/menu.js +++ b/menu/menu.js @@ -1027,12 +1027,12 @@ export class Menu extends LitElement { --md-elevation-shadow-color: var(--md-menu-container-shadow-color, var(--md-sys-color-shadow, #000)); --md-menu-container-shape: var(--md-sys-shape-corner-large, 16px); --md-menu-container-color: var(--md-sys-color-surface-container-low, #f7f2fa); - --md-menu-item-one-line-container-height: 44px; - --md-menu-item-leading-space: 16px; - --md-menu-item-trailing-space: 16px; + --md-menu-item-one-line-container-height: 48px; + --md-menu-item-leading-space: 12px; + --md-menu-item-trailing-space: 12px; --md-menu-item-top-space: 8px; --md-menu-item-bottom-space: 8px; - --md-menu-item-between-space: 12px; + --md-menu-item-between-space: 8px; --md-menu-item-icon-size: 20px; --md-menu-item-shape: var(--md-sys-shape-corner-extra-small, 4px); --md-menu-item-selected-shape: var(--md-sys-shape-corner-medium, 12px); @@ -1117,8 +1117,8 @@ export class Menu extends LitElement { --md-menu-item-trailing-space: 12px; --md-menu-item-top-space: 8px; --md-menu-item-bottom-space: 8px; - --md-menu-item-between-space: 12px; - --md-menu-item-icon-size: 24px; + --md-menu-item-between-space: 8px; + --md-menu-item-icon-size: 20px; --md-menu-item-shape: 0px; --md-menu-item-selected-shape: 0px; } @@ -1157,12 +1157,12 @@ export class Menu extends LitElement { :host([vertical]) { --md-menu-container-shape: var(--md-sys-shape-corner-large, 16px); --md-menu-container-color: var(--md-sys-color-surface-container-low, #f7f2fa); - --md-menu-item-one-line-container-height: 44px; - --md-menu-item-leading-space: 16px; - --md-menu-item-trailing-space: 16px; + --md-menu-item-one-line-container-height: 48px; + --md-menu-item-leading-space: 12px; + --md-menu-item-trailing-space: 12px; --md-menu-item-top-space: 8px; --md-menu-item-bottom-space: 8px; - --md-menu-item-between-space: 12px; + --md-menu-item-between-space: 8px; --md-menu-item-icon-size: 20px; --md-menu-item-shape: var(--md-sys-shape-corner-extra-small, 4px); --md-menu-item-selected-shape: var(--md-sys-shape-corner-medium, 12px); @@ -1221,8 +1221,10 @@ export class Menu extends LitElement { } /* First child in vertical menu */ - :host(:not([variant='baseline']):not([baseline])) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='first']), - :host(:not([variant='baseline']):not([baseline])) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):first-of-type), + :host(:not([variant='baseline']):not([baseline])) + ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='first']), + :host(:not([variant='baseline']):not([baseline])) + ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):first-of-type), :host([variant='vertical']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='first']), :host([variant='vertical']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):first-of-type), :host([variant='expressive']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='first']), @@ -1236,8 +1238,10 @@ export class Menu extends LitElement { } /* Last child in vertical menu */ - :host(:not([variant='baseline']):not([baseline])) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='last']), - :host(:not([variant='baseline']):not([baseline])) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):last-of-type), + :host(:not([variant='baseline']):not([baseline])) + ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='last']), + :host(:not([variant='baseline']):not([baseline])) + ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):last-of-type), :host([variant='vertical']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='last']), :host([variant='vertical']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):last-of-type), :host([variant='expressive']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='last']), @@ -1251,8 +1255,10 @@ export class Menu extends LitElement { } /* Single child in vertical menu */ - :host(:not([variant='baseline']):not([baseline])) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='single']), - :host(:not([variant='baseline']):not([baseline])) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):only-of-type), + :host(:not([variant='baseline']):not([baseline])) + ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='single']), + :host(:not([variant='baseline']):not([baseline])) + ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):only-of-type), :host([variant='vertical']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='single']), :host([variant='vertical']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):only-of-type), :host([variant='expressive']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item])[menu-position='single']), @@ -1264,7 +1270,8 @@ export class Menu extends LitElement { /* Hover and focus morphing in vertical menu */ :host(:not([variant='baseline']):not([baseline])) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):hover), - :host(:not([variant='baseline']):not([baseline])) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):focus-within), + :host(:not([variant='baseline']):not([baseline])) + ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):focus-within), :host([variant='vertical']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):hover), :host([variant='vertical']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):focus-within), :host([variant='expressive']) ::slotted(:is(md-menu-item, md-sub-menu, [md-menu-item]):hover), From cb32de38d7d6537e424e537ac2e0d31470a192d3 Mon Sep 17 00:00:00 2001 From: Travis Reeder Date: Tue, 13 Oct 2026 22:39:33 -0700 Subject: [PATCH 2/3] fix: avoid overriding font-variation-settings on slotted md-icon --- menu/menu-item.js | 1 - 1 file changed, 1 deletion(-) diff --git a/menu/menu-item.js b/menu/menu-item.js index f0f9b71..6b66a46 100644 --- a/menu/menu-item.js +++ b/menu/menu-item.js @@ -153,7 +153,6 @@ export const menuItemStyles = css` :is([slot='start'], [slot='end'])::slotted(md-icon) { --md-icon-size: var(--md-menu-item-icon-size, 20px); font-size: var(--md-menu-item-icon-size, 20px); - font-variation-settings: 'opsz' 20; } [slot='start'] { color: var(--md-menu-item-leading-icon-color, var(--md-sys-color-on-surface-variant, #49454f)); From 357275a08a94ba31dee52ef3d427500bffd6914f Mon Sep 17 00:00:00 2001 From: Travis Reeder Date: Tue, 13 Oct 2026 22:49:10 -0700 Subject: [PATCH 3/3] fix: remove explicit dimensions on slot start and end to avoid empty indents --- menu/menu-item.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/menu/menu-item.js b/menu/menu-item.js index 6b66a46..a7079ed 100644 --- a/menu/menu-item.js +++ b/menu/menu-item.js @@ -159,26 +159,12 @@ export const menuItemStyles = css` display: inline-flex; align-items: center; justify-content: center; - width: var(--md-menu-item-icon-size, 20px); - height: var(--md-menu-item-icon-size, 20px); - min-width: var(--md-menu-item-icon-size, 20px); - min-height: var(--md-menu-item-icon-size, 20px); - max-width: var(--md-menu-item-icon-size, 20px); - max-height: var(--md-menu-item-icon-size, 20px); - flex-shrink: 0; } [slot='end'] { color: var(--md-menu-item-trailing-icon-color, var(--md-sys-color-on-surface-variant, #49454f)); display: inline-flex; align-items: center; justify-content: center; - width: var(--md-menu-item-icon-size, 20px); - height: var(--md-menu-item-icon-size, 20px); - min-width: var(--md-menu-item-icon-size, 20px); - min-height: var(--md-menu-item-icon-size, 20px); - max-width: var(--md-menu-item-icon-size, 20px); - max-height: var(--md-menu-item-icon-size, 20px); - flex-shrink: 0; } .list-item.selected { background-color: var(--md-menu-item-selected-container-color, var(--md-sys-color-secondary-container, #e8def8));