(null);
const reducedMotion = useReducedMotion(navRef);
- const resetThemeRef = useRef<(() => void) | null>(null);
-
- function handleHighlightedChange(value: boolean) {
- setHighlighted(value);
- if (value) {
- const { reset } = applyTheme({
- theme: {
- tokens: {
- colorBackgroundSideNavigationItemActive: { light: '{colorPrimary50}', dark: '#0099FF20' },
- colorTextSideNavigationItemActive: { light: '{colorPrimary600}', dark: '{colorPrimary300}' },
- },
- },
- });
- resetThemeRef.current = reset;
- } else {
- resetThemeRef.current?.();
- resetThemeRef.current = null;
- }
- }
-
function handleToggle() {
const willCollapse = !collapsed;
// Focus management: move focus to toggle if focused item will be hidden
@@ -107,7 +79,7 @@ export default function SideNavigationCollapsedPage() {
{/* Toggle button at top */}
Collapsed state demo
Active: {activeHref}
- handleHighlightedChange(detail.checked)}>
- Background highlight
-
Toggle the navigation panel using the button. Items without icons are hidden in collapsed mode. Sections
- show their icon-bearing children as a flat list.
+ show their icon-bearing children as a flat list, and the section title becomes a divider in its place.
diff --git a/src/__integ__/__snapshots__/themes.test.ts.snap b/src/__integ__/__snapshots__/themes.test.ts.snap
index ca1310c70a..268451d92c 100644
--- a/src/__integ__/__snapshots__/themes.test.ts.snap
+++ b/src/__integ__/__snapshots__/themes.test.ts.snap
@@ -167,6 +167,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] =
"color-background-segment-hover": "#fafafa",
"color-background-segment-wrapper": "#ffffff",
"color-background-side-navigation-item-active": "transparent",
+ "color-background-side-navigation-item-active-collapsed": "transparent",
"color-background-skeleton": "#eaeded",
"color-background-skeleton-wave": "#f6f6f9",
"color-background-slider-handle-active": "#0a4a74",
@@ -639,6 +640,8 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] =
"color-text-segment-default": "#545b64",
"color-text-segment-hover": "#16191f",
"color-text-side-navigation-item-active": "#0073bb",
+ "color-text-side-navigation-item-active-collapsed": "#0073bb",
+ "color-text-side-navigation-item-default": "#545b64",
"color-text-small": "#687078",
"color-text-status-error": "#d13212",
"color-text-status-inactive": "#687078",
@@ -1075,6 +1078,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = `
"color-background-segment-hover": "#21252c",
"color-background-segment-wrapper": "#2a2e33",
"color-background-side-navigation-item-active": "transparent",
+ "color-background-side-navigation-item-active-collapsed": "transparent",
"color-background-skeleton": "#2a2e33",
"color-background-skeleton-wave": "#414750",
"color-background-slider-handle-active": "#44b9d6",
@@ -1547,6 +1551,8 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = `
"color-text-segment-default": "#d5dbdb",
"color-text-segment-hover": "#fafafa",
"color-text-side-navigation-item-active": "#44b9d6",
+ "color-text-side-navigation-item-active-collapsed": "#44b9d6",
+ "color-text-side-navigation-item-default": "#d5dbdb",
"color-text-small": "#95a5a6",
"color-text-status-error": "#ff5d64",
"color-text-status-inactive": "#95a5a6",
@@ -1983,6 +1989,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = `
"color-background-segment-hover": "#fafafa",
"color-background-segment-wrapper": "#ffffff",
"color-background-side-navigation-item-active": "transparent",
+ "color-background-side-navigation-item-active-collapsed": "transparent",
"color-background-skeleton": "#eaeded",
"color-background-skeleton-wave": "#f6f6f9",
"color-background-slider-handle-active": "#0a4a74",
@@ -2455,6 +2462,8 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = `
"color-text-segment-default": "#545b64",
"color-text-segment-hover": "#16191f",
"color-text-side-navigation-item-active": "#0073bb",
+ "color-text-side-navigation-item-active-collapsed": "#0073bb",
+ "color-text-side-navigation-item-default": "#545b64",
"color-text-small": "#687078",
"color-text-status-error": "#d13212",
"color-text-status-inactive": "#687078",
@@ -2891,6 +2900,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion"
"color-background-segment-hover": "#fafafa",
"color-background-segment-wrapper": "#ffffff",
"color-background-side-navigation-item-active": "transparent",
+ "color-background-side-navigation-item-active-collapsed": "transparent",
"color-background-skeleton": "#eaeded",
"color-background-skeleton-wave": "#f6f6f9",
"color-background-slider-handle-active": "#0a4a74",
@@ -3363,6 +3373,8 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion"
"color-text-segment-default": "#545b64",
"color-text-segment-hover": "#16191f",
"color-text-side-navigation-item-active": "#0073bb",
+ "color-text-side-navigation-item-active-collapsed": "#0073bb",
+ "color-text-side-navigation-item-default": "#545b64",
"color-text-small": "#687078",
"color-text-status-error": "#d13212",
"color-text-status-inactive": "#687078",
@@ -3799,6 +3811,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh"
"color-background-segment-hover": "#f0fbff",
"color-background-segment-wrapper": "#ffffff",
"color-background-side-navigation-item-active": "transparent",
+ "color-background-side-navigation-item-active-collapsed": "transparent",
"color-background-skeleton": "#ebebf0",
"color-background-skeleton-wave": "#f6f6f9",
"color-background-slider-handle-active": "#004a9e",
@@ -4271,6 +4284,8 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh"
"color-text-segment-default": "#424650",
"color-text-segment-hover": "#002b66",
"color-text-side-navigation-item-active": "#006ce0",
+ "color-text-side-navigation-item-active-collapsed": "#006ce0",
+ "color-text-side-navigation-item-default": "#424650",
"color-text-small": "#656871",
"color-text-status-error": "#db0000",
"color-text-status-inactive": "#656871",
@@ -4707,6 +4722,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh-
"color-background-segment-hover": "#f0fbff",
"color-background-segment-wrapper": "#ffffff",
"color-background-side-navigation-item-active": "transparent",
+ "color-background-side-navigation-item-active-collapsed": "transparent",
"color-background-skeleton": "#ebebf0",
"color-background-skeleton-wave": "#f6f6f9",
"color-background-slider-handle-active": "#004a9e",
@@ -5179,6 +5195,8 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh-
"color-text-segment-default": "#424650",
"color-text-segment-hover": "#002b66",
"color-text-side-navigation-item-active": "#006ce0",
+ "color-text-side-navigation-item-active-collapsed": "#006ce0",
+ "color-text-side-navigation-item-default": "#424650",
"color-text-small": "#656871",
"color-text-status-error": "#db0000",
"color-text-status-inactive": "#656871",
@@ -5615,6 +5633,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh-
"color-background-segment-hover": "#1b232d",
"color-background-segment-wrapper": "#0f141a",
"color-background-side-navigation-item-active": "transparent",
+ "color-background-side-navigation-item-active-collapsed": "transparent",
"color-background-skeleton": "#232b37",
"color-background-skeleton-wave": "#333843",
"color-background-slider-handle-active": "#75cfff",
@@ -6087,6 +6106,8 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh-
"color-text-segment-default": "#dedee3",
"color-text-segment-hover": "#75cfff",
"color-text-side-navigation-item-active": "#42b4ff",
+ "color-text-side-navigation-item-active-collapsed": "#42b4ff",
+ "color-text-side-navigation-item-default": "#c6c6cd",
"color-text-small": "#a4a4ad",
"color-text-status-error": "#ff7a7a",
"color-text-status-inactive": "#a4a4ad",
@@ -6523,6 +6544,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh-
"color-background-segment-hover": "#1b232d",
"color-background-segment-wrapper": "#161d26",
"color-background-side-navigation-item-active": "transparent",
+ "color-background-side-navigation-item-active-collapsed": "transparent",
"color-background-skeleton": "#232b37",
"color-background-skeleton-wave": "#333843",
"color-background-slider-handle-active": "#75cfff",
@@ -6995,6 +7017,8 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh-
"color-text-segment-default": "#dedee3",
"color-text-segment-hover": "#75cfff",
"color-text-side-navigation-item-active": "#42b4ff",
+ "color-text-side-navigation-item-active-collapsed": "#42b4ff",
+ "color-text-side-navigation-item-default": "#c6c6cd",
"color-text-small": "#a4a4ad",
"color-text-status-error": "#ff7a7a",
"color-text-status-inactive": "#a4a4ad",
diff --git a/src/side-navigation/__tests__/collapsed-side-navigation.test.tsx b/src/side-navigation/__tests__/collapsed-side-navigation.test.tsx
index 60149bfdb6..20b0570f3d 100644
--- a/src/side-navigation/__tests__/collapsed-side-navigation.test.tsx
+++ b/src/side-navigation/__tests__/collapsed-side-navigation.test.tsx
@@ -137,6 +137,30 @@ describe('SideNavigation collapsed mode', () => {
const wrapper = renderSideNavigation({ collapsed: true, items });
expect(wrapper.findAll('a')).toHaveLength(0);
});
+
+ it('renders a divider in place of the section title when collapsed', () => {
+ const wrapper = renderSideNavigation({
+ collapsed: true,
+ items: [
+ iconLink('Top', '#/top'),
+ {
+ type: 'section',
+ text: 'Resources',
+ items: [iconLink('Compute', '#/compute'), iconLink('Storage', '#/storage')],
+ },
+ ],
+ });
+ expect(wrapper.findAll('hr')).toHaveLength(1);
+ expect(wrapper.find('ul[aria-label="Resources"]')).not.toBeNull();
+ });
+
+ it('does not render a section divider when the section has no icon-bearing children', () => {
+ const wrapper = renderSideNavigation({
+ collapsed: true,
+ items: [iconLink('Top', '#/top'), { type: 'section', text: 'Empty', items: [plainLink('A', '#/a')] }],
+ });
+ expect(wrapper.findAll('hr')).toHaveLength(0);
+ });
});
describe('expandable link groups', () => {
diff --git a/src/side-navigation/parts.tsx b/src/side-navigation/parts.tsx
index a851da0f24..54eb95c776 100644
--- a/src/side-navigation/parts.tsx
+++ b/src/side-navigation/parts.tsx
@@ -135,14 +135,39 @@ export function NavigationItemsList({
const itemid = index + 1;
const itemPosition = `${position ? `${position},` : ''}${itemid}`;
+ // Emits a divider as its own list segment (dividers break the grouping).
+ function pushDivider() {
+ lists[lists.length] = {
+ listVariant: variant,
+ element: (
+
+ ),
+ };
+ currentListIndex = lists.length;
+ lists[currentListIndex] = {
+ listVariant: variant,
+ items: [],
+ };
+ }
+
// Renders icon-bearing children of a container item as a collapsed group.
// The inner carries the group label so list semantics are preserved
// for screen readers even when the visual header is hidden.
- function pushCollapsedGroup(children: ReadonlyArray, label: string) {
+ function pushCollapsedGroup(
+ children: ReadonlyArray,
+ label: string,
+ { leadingDivider = false }: { leadingDivider?: boolean } = {}
+ ) {
const iconChildren = children.filter(child => (child as SideNavigationProps.Link).icon);
if (iconChildren.length === 0) {
return;
}
+ // A section's title is hidden when collapsed; render a divider in its place
+ if (leadingDivider) {
+ pushDivider();
+ }
const groupElements = iconChildren.map((child, childIndex) => {
const childPosition = `${position ? `${position},` : ''}${itemid},${childIndex + 1}`;
return (
@@ -166,7 +191,7 @@ export function NavigationItemsList({
key={`group-${itemid}`}
className={clsx(
styles['list-item--group'],
- prevItem?.type === 'divider' && styles['list-item--group-no-padding-start'],
+ (leadingDivider || prevItem?.type === 'divider') && styles['list-item--group-no-padding-start'],
nextItem?.type === 'divider' && styles['list-item--group-no-padding-end']
)}
>
@@ -196,7 +221,7 @@ export function NavigationItemsList({
: (item as SideNavigationProps.SectionGroup).items.flatMap(child =>
child.type === 'section' ? (child as SideNavigationProps.Section).items : [child]
);
- pushCollapsedGroup(childItems, sectionLabel);
+ pushCollapsedGroup(childItems, sectionLabel, { leadingDivider: true });
return;
}
if (collapsed && item.type !== 'divider' && !(item as SideNavigationProps.Link).icon) {
@@ -204,20 +229,7 @@ export function NavigationItemsList({
}
switch (item.type) {
case 'divider': {
- const dividerIndex = lists.length;
- lists[dividerIndex] = {
- listVariant: variant,
- element: (
-
- ),
- };
- currentListIndex = lists.length;
- lists[currentListIndex] = {
- listVariant: variant,
- items: [],
- };
+ pushDivider();
return;
}
case 'link': {
diff --git a/src/side-navigation/styles.scss b/src/side-navigation/styles.scss
index 721e49ed75..e391eaeda1 100644
--- a/src/side-navigation/styles.scss
+++ b/src/side-navigation/styles.scss
@@ -154,9 +154,6 @@ $expandable-icon-negative-margin: awsui.$space-l;
padding-block: 0;
padding-inline: $item-padding-inline;
list-style: none;
- @include styles.with-motion {
- transition: margin awsui.$motion-duration-expressive awsui.$motion-easing-responsive;
- }
// Remove margin from first item in side nav, outer block margins are covered by list-container
.list-variant-root--first > &:first-child {
margin-block-start: 0px;
@@ -168,9 +165,6 @@ $expandable-icon-negative-margin: awsui.$space-l;
justify-content: center;
padding-block: 0;
padding-inline: 0;
- @include styles.with-motion {
- transition: margin awsui.$motion-duration-expressive awsui.$motion-easing-responsive;
- }
}
&--group {
@@ -284,8 +278,8 @@ $expandable-icon-negative-margin: awsui.$space-l;
// ==========================================================================
.link {
@include styles.font-body-m;
- color: awsui.$color-text-body-secondary;
- display: inline-flex;
+ color: awsui.$color-text-side-navigation-item-default;
+ display: flex;
padding-block: $item-padding-block;
min-inline-size: awsui.$size-side-navigation-item-height;
padding-inline: $item-padding-inline;
@@ -295,17 +289,20 @@ $expandable-icon-negative-margin: awsui.$space-l;
font-weight: styles.$font-weight-normal;
-webkit-font-smoothing: auto;
-moz-osx-font-smoothing: auto;
- @include styles.with-motion {
- transition:
- background-color awsui.$motion-duration-expressive awsui.$motion-easing-responsive,
- color awsui.$motion-duration-expressive awsui.$motion-easing-expressive;
- }
&-active {
font-weight: awsui.$font-wayfinding-link-active-weight;
@include styles.font-smoothing;
color: awsui.$color-text-side-navigation-item-active;
background-color: awsui.$color-background-side-navigation-item-active;
+
+ &.link--collapsed {
+ color: awsui.$color-text-side-navigation-item-active-collapsed;
+ background-color: awsui.$color-background-side-navigation-item-active-collapsed;
+ &:hover {
+ color: awsui.$color-text-side-navigation-item-active-collapsed;
+ }
+ }
}
&--collapsed {
diff --git a/style-dictionary/one-theme/colors.ts b/style-dictionary/one-theme/colors.ts
index e95130f580..2f3ea53d39 100644
--- a/style-dictionary/one-theme/colors.ts
+++ b/style-dictionary/one-theme/colors.ts
@@ -164,7 +164,10 @@ const tokens: StyleDictionary.ColorsDictionary = {
// ── Side Nav ─────────────────────────────────────────────────────────────────
colorTextSideNavigationItemActive: { light: '{colorPrimary600}', dark: '{colorPrimary300}' },
- colorBackgroundSideNavigationItemActive: { light: '{colorPrimary50}', dark: 'rgba(0, 153, 255, 0.13)' },
+ colorTextSideNavigationItemActiveCollapsed: { light: '{colorNeutral100}', dark: '{colorNeutral1000}' },
+ colorTextSideNavigationItemDefault: { light: '{colorTextBodyDefault}', dark: '{colorTextBodyDefault}' },
+ colorBackgroundSideNavigationItemActive: { light: 'transparent', dark: 'transparent' },
+ colorBackgroundSideNavigationItemActiveCollapsed: { light: '{colorPrimary500}', dark: '{colorPrimary500}' },
// ── Dropzone ──────────────────────────────────────────────────────────────
colorDropzoneBackgroundDefault: { light: '{colorWhite}', dark: '{colorNeutral850}' },
diff --git a/style-dictionary/one-theme/sizes.ts b/style-dictionary/one-theme/sizes.ts
index feb9d6ed01..134a391460 100644
--- a/style-dictionary/one-theme/sizes.ts
+++ b/style-dictionary/one-theme/sizes.ts
@@ -8,6 +8,7 @@ import { tokens as parentTokens } from '../visual-refresh/sizes.js';
const tokens: StyleDictionary.SizesDictionary = {
sizeVerticalInput: { comfortable: '30px', compact: '26px' },
+ sizeSideNavigationItemHeight: { comfortable: '30px', compact: '26px' },
};
const expandedTokens: StyleDictionary.ExpandedDensityScopeDictionary = merge(
diff --git a/style-dictionary/one-theme/spacing.ts b/style-dictionary/one-theme/spacing.ts
index 1dcdfcc0cf..a6a3ddb679 100644
--- a/style-dictionary/one-theme/spacing.ts
+++ b/style-dictionary/one-theme/spacing.ts
@@ -14,6 +14,8 @@ const tokens: StyleDictionary.SpacingDictionary = {
spaceTokenVertical: '1px',
spaceFieldVertical: { comfortable: '4px', compact: '2px' },
spaceStatusIndicatorPaddingHorizontal: '2px',
+ spaceSideNavigationItemGap: '5px',
+ spaceSideNavigationItemCollapsedGap: '5px',
};
const expandedTokens: StyleDictionary.ExpandedDensityScopeDictionary = merge(
diff --git a/style-dictionary/utils/token-names.ts b/style-dictionary/utils/token-names.ts
index a7e65153ab..57f06daeb1 100644
--- a/style-dictionary/utils/token-names.ts
+++ b/style-dictionary/utils/token-names.ts
@@ -580,6 +580,7 @@ export type ColorsTokenName =
| 'colorBackgroundInputDisabled'
| 'colorBackgroundItemSelected'
| 'colorBackgroundSideNavigationItemActive'
+ | 'colorBackgroundSideNavigationItemActiveCollapsed'
| 'colorBackgroundLayoutMain'
| 'colorBackgroundDrawer'
| 'colorBackgroundDrawerBackdrop'
@@ -852,6 +853,8 @@ export type ColorsTokenName =
| 'colorGapGlobalDrawer'
| 'colorItemSelected'
| 'colorTextSideNavigationItemActive'
+ | 'colorTextSideNavigationItemActiveCollapsed'
+ | 'colorTextSideNavigationItemDefault'
| 'colorBackgroundActionCardDefault'
| 'colorBackgroundActionCardHover'
| 'colorBackgroundActionCardActive'
diff --git a/style-dictionary/visual-refresh/colors.ts b/style-dictionary/visual-refresh/colors.ts
index f8758c8bb5..5d3ea7eaaa 100644
--- a/style-dictionary/visual-refresh/colors.ts
+++ b/style-dictionary/visual-refresh/colors.ts
@@ -62,6 +62,7 @@ const tokens: StyleDictionary.ColorsDictionary = {
colorBackgroundInputDisabled: { light: '{colorNeutral250}', dark: '{colorNeutral800}' },
colorBackgroundItemSelected: { light: '{colorPrimary50}', dark: '{colorPrimary1000}' },
colorBackgroundSideNavigationItemActive: 'transparent',
+ colorBackgroundSideNavigationItemActiveCollapsed: '{colorBackgroundSideNavigationItemActive}',
colorBackgroundLayoutMain: { light: '{colorWhite}', dark: '{colorNeutral850}' },
colorBackgroundDrawer: '{colorBackgroundLayoutPanelContent}',
colorBackgroundBackdrop: '{colorGreyOpaque70}',
@@ -149,6 +150,8 @@ const tokens: StyleDictionary.ColorsDictionary = {
colorTextButtonPrimaryDisabled: { light: '{colorNeutral500}', dark: '{colorNeutral500}' },
colorItemSelected: { light: '{colorPrimary600}', dark: '{colorPrimary400}' },
colorTextSideNavigationItemActive: '{colorTextAccent}',
+ colorTextSideNavigationItemActiveCollapsed: '{colorTextSideNavigationItemActive}',
+ colorTextSideNavigationItemDefault: '{colorTextBodySecondary}',
colorBorderCalendarGrid: 'transparent',
colorBorderCalendarGridSelectedFocusRing: { light: '{colorNeutral100}', dark: '{colorNeutral850}' },
colorBorderCellShaded: { light: '{colorNeutral300}', dark: '{colorNeutral700}' },
diff --git a/style-dictionary/visual-refresh/metadata/colors.ts b/style-dictionary/visual-refresh/metadata/colors.ts
index c52b6282a1..ae94283197 100644
--- a/style-dictionary/visual-refresh/metadata/colors.ts
+++ b/style-dictionary/visual-refresh/metadata/colors.ts
@@ -175,6 +175,11 @@ const metadata: StyleDictionary.MetadataIndex = {
public: true,
themeable: true,
},
+ colorBackgroundSideNavigationItemActiveCollapsed: {
+ description: 'The background color of an active side navigation item while the navigation is collapsed.',
+ public: false,
+ themeable: true,
+ },
colorBackgroundLayoutMain: {
description: 'The background color of the main content area on a page. For example: content area in app layout.',
public: true,
@@ -488,6 +493,16 @@ const metadata: StyleDictionary.MetadataIndex = {
public: true,
themeable: true,
},
+ colorTextSideNavigationItemActiveCollapsed: {
+ description: 'The text (and icon) color of an active side navigation item while the navigation is collapsed.',
+ public: false,
+ themeable: true,
+ },
+ colorTextSideNavigationItemDefault: {
+ description: 'The text (and icon) color of a non-active side navigation item.',
+ public: false,
+ themeable: true,
+ },
colorBorderCard: {
description: 'The border color of a card.',
public: true,