Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add highlight prop / highlight ring to Button, Tab, and MenuItem",
"packageName": "@fluentui-react-native/button",
"email": "winlarry@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add highlight prop / highlight ring to Button, Tab, and MenuItem",
"packageName": "@fluentui-react-native/menu",
"email": "winlarry@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Add highlight prop / highlight ring to Button, Tab, and MenuItem",
"packageName": "@fluentui-react-native/tablist",
"email": "winlarry@microsoft.com",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions packages/components/Button/src/Button.styling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const buttonStates: (keyof ButtonTokens)[] = [
'square',
'hovered',
'focused',
'highlighted',
'pressed',
'disabled',
];
Expand Down
7 changes: 7 additions & 0 deletions packages/components/Button/src/Button.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export interface ButtonTokens extends ButtonCoreTokens {
*/
hovered?: ButtonTokens;
focused?: ButtonTokens;
highlighted?: ButtonTokens;
pressed?: ButtonTokens;
disabled?: ButtonTokens;
hasContent?: ButtonTokens;
Expand Down Expand Up @@ -164,9 +165,15 @@ export interface ButtonProps extends ButtonCoreProps {
* @default false
*/
loading?: boolean;

/**
* Whether the button should render a highlighted ring.
*/
highlighted?: boolean;
Copy link
Contributor

@PPatBoyd PPatBoyd Jan 8, 2026

Choose a reason for hiding this comment

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

[discussion] specifically intrigued here at how you're feeling about the separation of state, styles, and props 🙂 and how we want to manage the separation long-term.

If I'm connecting my dots correctly -- and let me know if I'm not! -- the high-level scenarios we're currently wanting highlighted state are "controlled" state scenarios, particularly controlled lists for Win32 ControllerFor or plausibly MacOS general List purposes (where focus is on the entire List component, not the ListItem).

Do we want to separate highlight and have it be line-item added as a prop for components that want to offer controlled and uncontrolled styling patterns (e.g. button), where we'll have to carefully mind precedence and how multiple event sources (e.g. keyboard and mouse) interact?

Is there potentially value in more directly tying the component's available states to be available as a controlled set (prop) that has specific precedence/interaction with the uncontrolled behaviors -- potentially useful when there's no visual distinction between say, highlight and hover?

Copy link
Contributor

Choose a reason for hiding this comment

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

@JasonVMo in case you have any thoughts or opinions here or want to follow-up w/ offline group chat

}

interface ButtonState extends PressableState {
highlighted?: boolean;
measuredHeight?: number;
measuredWidth?: number;

Expand Down
18 changes: 18 additions & 0 deletions packages/components/Button/src/ButtonColorTokens.macos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export const defaultButtonColorTokens: TokenSettings<ButtonTokens, Theme> = (t:
borderColor: t.colors.neutralStroke2,
icon: t.colors.neutralForeground2,
},
highlighted: {
backgroundColor: t.colors.neutralBackground2,
color: t.colors.neutralForeground2,
borderColor: t.colors.neutralStroke2,
icon: t.colors.neutralForeground2,
},
primary: {
backgroundColor: t.colors.brandBackground,
color: t.colors.neutralForegroundOnBrand,
Expand Down Expand Up @@ -71,6 +77,12 @@ export const defaultButtonColorTokens: TokenSettings<ButtonTokens, Theme> = (t:
borderColor: t.colors.brandBackground,
iconColor: t.colors.neutralForegroundOnBrand,
},
highlighted: {
backgroundColor: t.colors.brandBackground,
color: t.colors.neutralForegroundOnBrand,
borderColor: t.colors.brandBackground,
iconColor: t.colors.neutralForegroundOnBrand,
},
},
// https://github.com/microsoft/fluentui-react-native/issues/3781
// Subtle Button should match Titlebar buttons on macOS, which:
Expand Down Expand Up @@ -106,5 +118,11 @@ export const defaultButtonColorTokens: TokenSettings<ButtonTokens, Theme> = (t:
borderColor: t.colors.transparentStroke,
iconColor: t.colors.brandForeground1,
},
highlighted: {
backgroundColor: t.colors.subtleBackground,
color: t.colors.brandForeground1,
borderColor: t.colors.transparentStroke,
iconColor: t.colors.brandForeground1,
},
},
} as ButtonTokens);
23 changes: 23 additions & 0 deletions packages/components/Button/src/ButtonColorTokens.win32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ export const defaultButtonColorTokens: TokenSettings<ButtonTokens, Theme> = (t:
borderColor: t.colors.transparentStroke,
iconColor: t.colors.neutralForeground1Hover,
},
highlighted: {
backgroundColor: t.colors.neutralBackground1Hover,
color: t.colors.neutralForeground1Hover,
borderColor: t.colors.neutralForeground1,
iconColor: t.colors.neutralForeground1Hover,
},
primary: {
backgroundColor: t.colors.brandBackground,
color: t.colors.neutralForegroundOnBrand,
Expand Down Expand Up @@ -70,6 +76,12 @@ export const defaultButtonColorTokens: TokenSettings<ButtonTokens, Theme> = (t:
borderInnerColor: t.colors.strokeFocus1,
iconColor: t.colors.neutralForegroundOnBrandHover,
},
highlighted: {
backgroundColor: t.colors.brandBackgroundHover,
color: t.colors.neutralForegroundOnBrandHover,
borderColor: t.colors.neutralForeground1, // Trying to render a 2 tone border sometime causes a crash for unknown reasons, use a single color for now
iconColor: t.colors.neutralForegroundOnBrandHover,
},
},
subtle: {
backgroundColor: t.colors.subtleBackground,
Expand Down Expand Up @@ -100,6 +112,12 @@ export const defaultButtonColorTokens: TokenSettings<ButtonTokens, Theme> = (t:
borderColor: t.colors.transparentStroke,
iconColor: t.colors.neutralForeground1Hover,
},
highlighted: {
backgroundColor: t.colors.subtleBackgroundHover,
color: t.colors.neutralForeground1Hover,
borderColor: t.colors.neutralForeground1,
iconColor: t.colors.neutralForeground1Hover,
},
},
} as ButtonTokens;
};
Expand Down Expand Up @@ -130,4 +148,9 @@ const highContrastColors = {
color: PlatformColor('HighlightText'),
iconColor: PlatformColor('HighlightText'),
},
highlighted: {
backgroundColor: PlatformColor('Highlight'),
color: PlatformColor('HighlightText'),
iconColor: PlatformColor('HighlightText'),
},
};
21 changes: 21 additions & 0 deletions packages/components/Button/src/ButtonTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = () =>
borderWidth: 0,
padding: globalTokens.size60,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size60 - globalTokens.stroke.width20,
},
hasContent: {
minWidth: 96,
paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width10,
Expand All @@ -29,6 +33,9 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = () =>
focused: {
paddingHorizontal: globalTokens.size120,
},
highlighted: {
paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20,
},
},
},
small: {
Expand All @@ -39,6 +46,10 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = () =>
borderWidth: 0,
padding: globalTokens.size40,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size40 - globalTokens.stroke.width20,
},
hasContent: {
minWidth: 64,
minHeight: 24,
Expand All @@ -52,6 +63,9 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = () =>
focused: {
paddingHorizontal: globalTokens.size80,
},
highlighted: {
paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20,
},
},
},
large: {
Expand All @@ -62,6 +76,10 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = () =>
borderWidth: 0,
padding: globalTokens.size80,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size80 - globalTokens.stroke.width20,
},
hasContent: {
minWidth: 96,
paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width10,
Expand All @@ -74,6 +92,9 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = () =>
focused: {
paddingHorizontal: globalTokens.size160,
},
highlighted: {
paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20,
},
},
},
rounded: {
Expand Down
69 changes: 69 additions & 0 deletions packages/components/Button/src/ButtonTokens.win32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,28 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = (theme: T
borderWidth: 0,
padding: globalTokens.size80,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size80 - globalTokens.stroke.width20,
},
primary: !isHighContrast(theme) && {
focused: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size80 - globalTokens.stroke.width20,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size80 - globalTokens.stroke.width20,
},
square: {
focused: {
borderWidth: globalTokens.stroke.width10,
padding: globalTokens.size80 - globalTokens.stroke.width10,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size80 - globalTokens.stroke.width20,
},
},
},
hasContent: {
Expand All @@ -45,16 +57,28 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = (theme: T
padding: globalTokens.size60,
paddingHorizontal: globalTokens.size120,
},
highlighted: {
padding: globalTokens.size60 - globalTokens.stroke.width20,
paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20,
},
primary: !isHighContrast(theme) && {
focused: {
padding: globalTokens.size60 - globalTokens.stroke.width20,
paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20,
},
highlighted: {
padding: globalTokens.size60 - globalTokens.stroke.width20,
paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20,
},
square: {
focused: {
padding: globalTokens.size60 - globalTokens.stroke.width10,
paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width10,
},
highlighted: {
padding: globalTokens.size60 - globalTokens.stroke.width20,
paddingHorizontal: globalTokens.size120 - globalTokens.stroke.width20,
},
},
},
},
Expand All @@ -66,16 +90,28 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = (theme: T
borderWidth: 0,
padding: globalTokens.size40,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size40 - globalTokens.stroke.width20,
},
primary: !isHighContrast(theme) && {
focused: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size40 - globalTokens.stroke.width20,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size40 - globalTokens.stroke.width20,
},
square: {
focused: {
borderWidth: globalTokens.stroke.width10,
padding: globalTokens.size40 - globalTokens.stroke.width10,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size40 - globalTokens.stroke.width20,
},
},
},
hasContent: {
Expand All @@ -91,14 +127,23 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = (theme: T
focused: {
paddingHorizontal: globalTokens.size80,
},
highlighted: {
paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20,
},
primary: !isHighContrast(theme) && {
focused: {
paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20,
},
highlighted: {
paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20,
},
square: {
focused: {
paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width10,
},
highlighted: {
paddingHorizontal: globalTokens.size80 - globalTokens.stroke.width20,
},
},
},
},
Expand All @@ -110,16 +155,28 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = (theme: T
borderWidth: 0,
padding: globalTokens.size100,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size100 - globalTokens.stroke.width20,
},
primary: !isHighContrast(theme) && {
focused: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size100 - globalTokens.stroke.width20,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size100 - globalTokens.stroke.width20,
},
square: {
focused: {
borderWidth: globalTokens.stroke.width10,
padding: globalTokens.size100 - globalTokens.stroke.width10,
},
highlighted: {
borderWidth: globalTokens.stroke.width20,
padding: globalTokens.size100 - globalTokens.stroke.width20,
},
},
},
hasContent: {
Expand All @@ -137,16 +194,28 @@ export const defaultButtonTokens: TokenSettings<ButtonTokens, Theme> = (theme: T
padding: globalTokens.size80,
paddingHorizontal: globalTokens.size160,
},
highlighted: {
padding: globalTokens.size80 - globalTokens.stroke.width20,
paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20,
},
primary: !isHighContrast(theme) && {
focused: {
padding: globalTokens.size80 - globalTokens.stroke.width20,
paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20,
},
highlighted: {
padding: globalTokens.size80 - globalTokens.stroke.width20,
paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20,
},
square: {
focused: {
padding: globalTokens.size80 - globalTokens.stroke.width10,
paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width10,
},
highlighted: {
padding: globalTokens.size80 - globalTokens.stroke.width20,
paddingHorizontal: globalTokens.size160 - globalTokens.stroke.width20,
},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { MenuItemProps, MenuItemTokens, MenuItemSlotProps } from './MenuIte
import { menuItemName } from './MenuItem.types';
import { defaultMenuItemTokens } from './MenuItemTokens';

export const menuItemStates: (keyof MenuItemTokens)[] = ['hovered', 'focused', 'pressed', 'disabled'];
export const menuItemStates: (keyof MenuItemTokens)[] = ['hovered', 'focused', 'highlighted', 'pressed', 'disabled'];

export const stylingSettings: UseStylingOptions<MenuItemProps, MenuItemSlotProps, MenuItemTokens> = {
tokens: [defaultMenuItemTokens, menuItemName],
Expand Down
Loading
Loading