Skip to content

[Left nav refactor][6/6] Add tooltips for the sidebar icons when collapsed#20699

Merged
daniellok-db merged 7 commits intomlflow:masterfrom
daniellok-db:stack/tooltips
Feb 11, 2026
Merged

[Left nav refactor][6/6] Add tooltips for the sidebar icons when collapsed#20699
daniellok-db merged 7 commits intomlflow:masterfrom
daniellok-db:stack/tooltips

Conversation

@daniellok-db
Copy link
Collaborator

@daniellok-db daniellok-db commented Feb 10, 2026

🥞 Stacked PR

Use this link to review incremental changes.


Related Issues/PRs

#xxx

What changes are proposed in this pull request?

As title, i missed this previously but when the sidebar is collapsed it's hard to know what the icons mean. This PR adds tooltips.

How is this PR tested?

  • Existing unit/integration tests
  • New unit/integration tests
  • Manual tests
Screen.Recording.2026-02-11.at.2.00.27.PM.mov

With workspace mode enabled (currently genai / ML preference is not scoped to workspace, but this can be followed up between RC and stable):

Screen.Recording.2026-02-11.at.2.08.53.PM.mov

Does this PR require documentation update?

  • No. You can skip the rest of this section.
  • Yes. I've updated:
    • Examples
    • API references
    • Instructions

Does this PR require updating the MLflow Skills repository?

  • No. You can skip the rest of this section.
  • Yes. Please link the corresponding PR or explain how you plan to update it.

Release Notes

Is this a user-facing change?

  • No. You can skip the rest of this section.
  • Yes. Give a description of this change to be included in the release notes for MLflow users.

What component(s), interfaces, languages, and integrations does this PR affect?

Components

  • area/tracking: Tracking Service, tracking client APIs, autologging
  • area/models: MLmodel format, model serialization/deserialization, flavors
  • area/model-registry: Model Registry service, APIs, and the fluent client calls for Model Registry
  • area/scoring: MLflow Model server, model deployment tools, Spark UDFs
  • area/evaluation: MLflow model evaluation features, evaluation metrics, and evaluation workflows
  • area/gateway: MLflow AI Gateway client APIs, server, and third-party integrations
  • area/prompts: MLflow prompt engineering features, prompt templates, and prompt management
  • area/tracing: MLflow Tracing features, tracing APIs, and LLM tracing functionality
  • area/projects: MLproject format, project running backends
  • area/uiux: Front-end, user experience, plotting, JavaScript, JavaScript dev server
  • area/build: Build and test infrastructure for MLflow
  • area/docs: MLflow documentation pages

How should the PR be classified in the release notes? Choose one:

  • rn/none - No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" section
  • rn/breaking-change - The PR will be mentioned in the "Breaking Changes" section
  • rn/feature - A new user-facing feature worth mentioning in the release notes
  • rn/bug-fix - A user-facing bug fix worth mentioning in the release notes
  • rn/documentation - A user-facing documentation change worth mentioning in the release notes

Should this PR be included in the next patch release?

Yes should be selected for bug fixes, documentation updates, and other small changes. No should be selected for new features and larger changes. If you're unsure about the release classification of this PR, leave this unchecked to let the maintainers decide.

What is a minor/patch release?
  • Minor release: a release that increments the second part of the version number (e.g., 1.2.0 -> 1.3.0).
    Bug fixes, doc updates and new features usually go into minor releases.
  • Patch release: a release that increments the third part of the version number (e.g., 1.2.0 -> 1.2.1).
    Bug fixes and doc updates usually go into patch releases.
  • Yes (this PR will be cherry-picked and included in the next patch release)
  • No (this PR will be included in the next minor release)

@github-actions
Copy link
Contributor

🛠 DevTools 🛠

Install mlflow from this PR

# mlflow
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/20699/merge
# mlflow-skinny
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/20699/merge#subdirectory=libs/skinny

For Databricks, use the following command:

%sh curl -LsSf https://raw.githubusercontent.com/mlflow/mlflow/HEAD/dev/install-skinny.sh | sh -s pull/20699/merge

@github-actions
Copy link
Contributor

@daniellok-db Thank you for the contribution! Could you fix the following issue(s)?

⚠ DCO check

The DCO check failed. Please sign off your commit(s) by following the instructions here. See https://github.com/mlflow/mlflow/blob/master/CONTRIBUTING.md#sign-your-work for more details.

@github-actions github-actions bot added area/uiux Front-end, user experience, plotting, JavaScript, JavaScript dev server rn/none List under Small Changes in Changelogs. labels Feb 11, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR is the final step in the left-nav refactor stack and focuses on improving usability of the collapsed sidebar by adding tooltips for icon-only navigation, along with related sidebar structure updates.

Changes:

  • Introduces a reusable MlflowSidebarLink that wraps sidebar links with tooltips when the sidebar is collapsed.
  • Refactors the main MlflowSidebar to support a collapsed/expanded mode, nested experiment navigation, and nested gateway items.
  • Moves theme preference control into the Settings page (and removes the old header-based theme switch).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
mlflow/server/js/src/workspaces/components/WorkspaceSelector.tsx Updates workspace selector behavior (adds clear handling + sizing tweaks).
mlflow/server/js/src/settings/SettingsPage.tsx Adds a theme preference toggle and improves switch labels.
mlflow/server/js/src/lang/default/en.json Updates extracted i18n strings to match new/updated UI text.
mlflow/server/js/src/common/utils/FeatureUtils.ts Enables workflow-based navigation flag.
mlflow/server/js/src/common/components/MlflowSidebarWorkflowSwitch.tsx New workflow “pill” switch UI in the sidebar.
mlflow/server/js/src/common/components/MlflowSidebarLink.tsx New sidebar link wrapper with collapsed-mode tooltips + telemetry logging.
mlflow/server/js/src/common/components/MlflowSidebarGatewayItems.tsx New nested gateway section items for the sidebar.
mlflow/server/js/src/common/components/MlflowSidebarExperimentItems.tsx New nested experiment section items for the sidebar.
mlflow/server/js/src/common/components/MlflowSidebar.tsx Refactors sidebar layout to support collapse + tooltips + nested sections.
mlflow/server/js/src/common/components/MlflowHeader.tsx Removes the old header component.
mlflow/server/js/src/MlflowRouter.tsx Removes header usage and always renders the sidebar component.

Comment on lines +59 to +73
<MlflowSidebarLink
css={{ border: `1px solid ${theme.colors.actionDefaultBorderDefault}`, marginBottom: theme.spacing.sm }}
to={ExperimentTrackingRoutes.experimentsObservatoryRoute}
componentId="mlflow.experiment-sidebar.back-button"
isActive={isExperimentsActive}
onClick={onBackClick}
icon={<ArrowLeftIcon />}
collapsed={collapsed}
tooltipContent={
<FormattedMessage defaultMessage="Back to experiment list" description="Tooltip for experiments button" />
}
>
<BeakerIcon />
{loading ? <Spinner /> : <Typography.Text ellipsis>{experiment?.name}</Typography.Text>}
</MlflowSidebarLink>
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The back/experiment header link passes icon={<ArrowLeftIcon />} but also includes <BeakerIcon /> as the first child. Since MlflowSidebarLink already renders icon before children, this will render two icons in the expanded state; consider removing the extra icon from children (or moving it into the icon prop if that’s the intended leading icon).

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +77
{Object.entries(config).map(([sectionKey, items]) => (
<>
{sectionKey !== 'top-level' && collapsed ? (
<div
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object.entries(config).map(...) returns a fragment (<>...</>) without a key, which will trigger React warnings and can cause unstable reconciliation. Use React.Fragment key={sectionKey} (or wrap in an element with key).

Copilot uses AI. Check for mistakes.
logTelemetryEvent({
componentId,
componentViewId: viewId,
componentType: DesignSystemEventProviderComponentTypes.Button,
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Telemetry for this component is logged as DesignSystemEventProviderComponentTypes.Button, but the interactive element is a navigation Link. To keep analytics semantics consistent with other link telemetry in the codebase, this should likely be TypographyLink (or the appropriate link component type) instead of Button.

Suggested change
componentType: DesignSystemEventProviderComponentTypes.Button,
componentType: DesignSystemEventProviderComponentTypes.TypographyLink,

Copilot uses AI. Check for mistakes.
Comment on lines +282 to +296
{showWorkspaceMenuItems &&
menuItems.map(
({ key, icon, linkProps, componentId, nestedItems }) =>
nestedItems ?? (
<MlflowSidebarLink
to={linkProps.to}
aria-current={linkProps.isActive(location) ? 'page' : undefined}
css={{
display: 'flex',
alignItems: 'center',
gap: theme.spacing.sm,
color: theme.colors.textPrimary,
padding: `${theme.spacing.xs}px ${theme.spacing.sm}px`,
borderRadius: theme.borders.borderRadiusSm,
'&:hover': {
color: theme.colors.actionLinkHover,
backgroundColor: theme.colors.actionDefaultBackgroundHover,
},
'&[aria-current="page"]': {
backgroundColor: theme.colors.actionDefaultBackgroundPress,
color: theme.isDarkMode ? theme.colors.blue300 : theme.colors.blue700,
fontWeight: theme.typography.typographyBoldFontWeight,
},
}}
onClick={() =>
logTelemetryEvent({
componentId,
componentViewId: viewId,
componentType: DesignSystemEventProviderComponentTypes.TypographyLink,
componentSubType: null,
eventType: DesignSystemEventProviderAnalyticsEventTypes.OnClick,
})
}
componentId={componentId}
isActive={linkProps.isActive}
icon={icon}
collapsed={!showSidebar}
>
{icon}
{linkProps.children}
</Link>
{nestedItemsGroups && nestedItemsGroups.length > 0 && (
<ul css={NESTED_ITEMS_UL_CSS}>
{nestedItemsGroups.map((group) => (
<Fragment key={group.sectionKey}>
{group.sectionKey !== 'top-level' && (
<li
css={{
display: 'flex',
marginTop: theme.spacing.xs,
marginBottom: theme.spacing.xs,
position: 'relative',
height: theme.typography.lineHeightBase,
paddingLeft: 40,
}}
>
<Typography.Text size="sm" color="secondary">
{getExperimentPageSideNavSectionLabel(group.sectionKey, [])}
</Typography.Text>
</li>
)}
{group.items.map((nestedItem) => {
const isDisabled = !experimentId && key === 'experiments';
return <li key={nestedItem.key}>{renderNestedItemLink(nestedItem, isDisabled)}</li>;
})}
</Fragment>
))}
</ul>
)}
{nestedItems && nestedItems.length > 0 && (
<ul css={NESTED_ITEMS_UL_CSS}>
{nestedItems.map((nestedItem) => (
<li key={nestedItem.key}>{renderNestedItemLink(nestedItem, false)}</li>
))}
</ul>
)}
</li>
))}
</>
)}
</MlflowSidebarLink>
),
)}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The <ul> here maps menuItems to elements without providing a key on the returned top-level element, which will cause React key warnings (keys inside MlflowSidebarLink don't help because they are not on the array elements). Also, nestedItems can be a <div>/fragment, which can lead to invalid <ul> children (only <li> is valid). Wrap each entry in a keyed <li> (or a keyed fragment) and ensure the rendered children under <ul> are list items.

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +53
<div css={{ display: 'flex', flexDirection: 'column' }}>
<div
css={{
display: 'flex',
alignItems: 'center',
gap: theme.spacing.sm,
justifyContent: collapsed ? 'center' : 'flex-start',
paddingLeft: collapsed ? 0 : theme.spacing.sm,
paddingBlock: theme.spacing.xs,
border: collapsed ? `1px solid ${theme.colors.actionDefaultBorderDefault}` : 'none',
borderRadius: theme.borders.borderRadiusSm,
marginTop: collapsed ? theme.spacing.sm : 0,
marginBottom: collapsed ? theme.spacing.sm : 0,
}}
>
<CloudModelIcon />
{!collapsed && <FormattedMessage defaultMessage="AI Gateway" description="Sidebar link for gateway" />}
</div>
<MlflowSidebarLink
css={{ paddingLeft: collapsed ? undefined : theme.spacing.lg }}
to={GatewayRoutes.gatewayPageRoute}
componentId="mlflow.sidebar.gateway_endpoints_tab_link"
isActive={isEndpointsActive}
icon={<ChainIcon />}
collapsed={collapsed}
>
<FormattedMessage defaultMessage="Endpoints" description="Sidebar link for gateway endpoints" />
</MlflowSidebarLink>
<MlflowSidebarLink
css={{ paddingLeft: collapsed ? undefined : theme.spacing.lg }}
to={GatewayRoutes.apiKeysPageRoute}
componentId="mlflow.sidebar.gateway_api_keys_tab_link"
isActive={isApiKeysActive}
icon={<KeyIcon />}
collapsed={collapsed}
>
<FormattedMessage defaultMessage="API Keys" description="Sidebar link for gateway API keys" />
</MlflowSidebarLink>
</div>
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MlflowSidebarGatewayItems renders a top-level <div> (and nested <div> header) which will be inserted directly under the sidebar’s <ul> when nestedItems is used. That produces invalid list markup and can hurt accessibility/styling. Consider returning <li> elements (or wrapping the whole block in a single <li> containing an inner <ul>), and add an accessible label/tooltip for the collapsed header icon since it otherwise has no text.

Suggested change
<div css={{ display: 'flex', flexDirection: 'column' }}>
<div
css={{
display: 'flex',
alignItems: 'center',
gap: theme.spacing.sm,
justifyContent: collapsed ? 'center' : 'flex-start',
paddingLeft: collapsed ? 0 : theme.spacing.sm,
paddingBlock: theme.spacing.xs,
border: collapsed ? `1px solid ${theme.colors.actionDefaultBorderDefault}` : 'none',
borderRadius: theme.borders.borderRadiusSm,
marginTop: collapsed ? theme.spacing.sm : 0,
marginBottom: collapsed ? theme.spacing.sm : 0,
}}
>
<CloudModelIcon />
{!collapsed && <FormattedMessage defaultMessage="AI Gateway" description="Sidebar link for gateway" />}
</div>
<MlflowSidebarLink
css={{ paddingLeft: collapsed ? undefined : theme.spacing.lg }}
to={GatewayRoutes.gatewayPageRoute}
componentId="mlflow.sidebar.gateway_endpoints_tab_link"
isActive={isEndpointsActive}
icon={<ChainIcon />}
collapsed={collapsed}
>
<FormattedMessage defaultMessage="Endpoints" description="Sidebar link for gateway endpoints" />
</MlflowSidebarLink>
<MlflowSidebarLink
css={{ paddingLeft: collapsed ? undefined : theme.spacing.lg }}
to={GatewayRoutes.apiKeysPageRoute}
componentId="mlflow.sidebar.gateway_api_keys_tab_link"
isActive={isApiKeysActive}
icon={<KeyIcon />}
collapsed={collapsed}
>
<FormattedMessage defaultMessage="API Keys" description="Sidebar link for gateway API keys" />
</MlflowSidebarLink>
</div>
<FormattedMessage defaultMessage="AI Gateway" description="Sidebar link for gateway">
{(gatewayLabel) => (
<li css={{ display: 'flex', flexDirection: 'column' }}>
<div
css={{
display: 'flex',
alignItems: 'center',
gap: theme.spacing.sm,
justifyContent: collapsed ? 'center' : 'flex-start',
paddingLeft: collapsed ? 0 : theme.spacing.sm,
paddingBlock: theme.spacing.xs,
border: collapsed ? `1px solid ${theme.colors.actionDefaultBorderDefault}` : 'none',
borderRadius: theme.borders.borderRadiusSm,
marginTop: collapsed ? theme.spacing.sm : 0,
marginBottom: collapsed ? theme.spacing.sm : 0,
}}
aria-label={collapsed ? String(gatewayLabel) : undefined}
title={String(gatewayLabel)}
>
<CloudModelIcon aria-hidden="true" />
{!collapsed && gatewayLabel}
</div>
<MlflowSidebarLink
css={{ paddingLeft: collapsed ? undefined : theme.spacing.lg }}
to={GatewayRoutes.gatewayPageRoute}
componentId="mlflow.sidebar.gateway_endpoints_tab_link"
isActive={isEndpointsActive}
icon={<ChainIcon />}
collapsed={collapsed}
>
<FormattedMessage defaultMessage="Endpoints" description="Sidebar link for gateway endpoints" />
</MlflowSidebarLink>
<MlflowSidebarLink
css={{ paddingLeft: collapsed ? undefined : theme.spacing.lg }}
to={GatewayRoutes.apiKeysPageRoute}
componentId="mlflow.sidebar.gateway_api_keys_tab_link"
isActive={isApiKeysActive}
icon={<KeyIcon />}
collapsed={collapsed}
>
<FormattedMessage defaultMessage="API Keys" description="Sidebar link for gateway API keys" />
</MlflowSidebarLink>
</li>
)}
</FormattedMessage>

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +58
<>
<div
css={{
borderTop: `1px solid ${theme.colors.actionDefaultBorderDefault}`,
width: '100%',
paddingBottom: theme.spacing.sm,
marginTop: theme.spacing.xs,
}}
/>
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component returns a fragment containing <div> separators and <li> elements but does not provide an enclosing <ul>/<ol>. When it’s rendered as nestedItems inside the main sidebar <ul>, the <div>s become invalid direct children of a <ul>. Consider restructuring so that everything under the sidebar’s <ul> is <li> (e.g., wrap the whole nested block in a single <li> containing its own inner <ul>).

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +36
<div
css={{
position: 'relative',
background: borderColor,
margin: -1,
borderRadius: theme.borders.borderRadiusFull,
padding: 1,
cursor: 'pointer',
}}
onClick={() => setWorkflowType(workflowType)}
>
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pill is implemented as a clickable <div> without role, tabIndex, or keyboard handlers, so it’s not keyboard-accessible and won’t be announced correctly by assistive tech. Use a semantic <button> (preferred) or add role="button", tabIndex={0}, and handle Enter/Space, plus appropriate aria-pressed for the active state.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +45
return (
<li key={componentId}>
<Tooltip
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MlflowSidebarLink renders a <li> internally. This makes the component invalid to use outside of a <ul>/<ol> (e.g., the Docs/GitHub/Settings links are rendered inside a <div> in MlflowSidebar). Consider removing the <li> wrapper from MlflowSidebarLink and letting call sites wrap it in <li> only where appropriate (or add an asListItem/wrapper option).

Copilot uses AI. Check for mistakes.
<Tooltip
componentId="mlflow.sidebar.assistant_tooltip"
content={<FormattedMessage defaultMessage="Assistant" description="Tooltip for assistant button" />}
open={isAssistantHovered && !showSidebar}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tooltip is controlled via open={isAssistantHovered && !showSidebar}, so it won’t appear on keyboard focus when the sidebar is collapsed (and it also duplicates the design-system’s own hover/focus handling). Consider using open={!showSidebar ? undefined : false} (and letting the tooltip handle focus/hover) so collapsed-icon tooltips work for keyboard users too.

Suggested change
open={isAssistantHovered && !showSidebar}
open={!showSidebar ? undefined : false}

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

github-actions bot commented Feb 11, 2026

Documentation preview for 3cdf599 is available at:

More info
  • Ignore this comment if this PR does not change the documentation.
  • The preview is updated when a new commit is pushed to this PR.
  • This comment was created by this workflow run.
  • The documentation was built by this workflow run.

Copy link
Collaborator

@serena-ruan serena-ruan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@daniellok-db daniellok-db force-pushed the stack/tooltips branch 2 times, most recently from ffddb47 to 9115a35 Compare February 11, 2026 06:59
@daniellok-db daniellok-db added this pull request to the merge queue Feb 11, 2026
Merged via the queue into mlflow:master with commit d653d29 Feb 11, 2026
17 of 18 checks passed
@daniellok-db daniellok-db deleted the stack/tooltips branch February 11, 2026 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/uiux Front-end, user experience, plotting, JavaScript, JavaScript dev server rn/none List under Small Changes in Changelogs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants