Skip to content

feat(react-headless-components-preview): expose missing state data attributes - #36480

Open
Victor Genaev (mainframev) wants to merge 2 commits into
microsoft:masterfrom
mainframev:vgenaev/headless-data-attributes
Open

feat(react-headless-components-preview): expose missing state data attributes#36480
Victor Genaev (mainframev) wants to merge 2 commits into
microsoft:masterfrom
mainframev:vgenaev/headless-data-attributes

Conversation

@mainframev

@mainframev Victor Genaev (mainframev) commented Jul 31, 2026

Copy link
Copy Markdown
Contributor
  • Expose missing resolved interaction and structural state as typed data-* attributes.
  • Preserve all existing attribute names and value semantics.

FUI Modern builds on headless components and needs stable selectors for state-driven styling:

.fx-Modal:where([data-state="open"]) {
   ... 
}

.fx-Button:where([data-icon-position="left"]) {
   ...
}

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-headless-components-preview
react-headless-components-preview: entire library
251.701 kB
73.226 kB
253.609 kB
73.624 kB
1.908 kB
398 B
react-headless-components-preview
@fluentui/react-headless-components-preview/tag-picker
62.147 kB
20.587 kB
62.192 kB
20.597 kB
45 B
10 B
react-headless-components-preview
@fluentui/react-headless-components-preview/teaching-popover
35.416 kB
11.745 kB
35.494 kB
11.771 kB
78 B
26 B

🤖 This report was generated against c83bfa7666778640350f54991c8ebe712a8f389b

@github-actions

Copy link
Copy Markdown

Pull request demo site: URL

@Hotell Martin Hochel (Hotell) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

left some comments, those +2kb are not great :D

});

state.popover.open = popoverOpen;
state.root['data-open'] = stringifyDataAttribute(popoverOpen);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isn't this boolean ? AFAIR we are going to toggle bool attributes instead of toggling value ?


setMenuItemDataAttributes(state);
// eslint-disable-next-line react-hooks/immutability
state.root['data-checked'] = stringifyDataAttribute(state.checked);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

cant this be achieved just by calling the base hook and setting state.root ?


export type { AvatarGroupContextValue, AvatarGroupContextValues, AvatarGroupSlots } from '@fluentui/react-avatar';

export type AvatarGroupProps = AvatarGroupBaseProps;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

re-export inline pls

// eslint-disable-next-line react-hooks/immutability
state.root['data-disabled'] = stringifyDataAttribute(state.input.disabled);
// eslint-disable-next-line react-hooks/immutability
state.root['data-invalid'] = stringifyDataAttribute(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

not sure if it makes sense to duplicate the attribute. we already have aria-invalid available

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

replied here: #36480 (comment)

/**
* Data attribute set when the link is rendered inline.
*/
'data-inline'?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isn't it a style prop leaked?

@mainframev Victor Genaev (mainframev) Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good call, I think it was leaked, moreover, in current docs we have examples with no-op inline prop, styles are applied in the examples but independent from inline prop. I will omit it here and until we are stable we should remove that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

root: {
focusgroupstart?: string;
'data-disabled'?: string;
'data-has-submenu'?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we have aria-has-popup and aria-expanded already available, do we need to duplicate those attributes?

@mainframev Victor Genaev (mainframev) Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, good question, I am leaning towards keep these things separated, the opposite would help us to save some not significant amount of bytes on HTML size, but as we previously discussed it's relatively cheap stage of page parsing, so more HTML attributes on existing nodes won't hurt us. Keeping these things separated would have better separation of concerns (area for a11y, data-* for styling) and more obvious & expected DX

@mainframev Victor Genaev (mainframev) Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

some other DS following this path of separated aria and data attributes / duplications:

<input data-slot="input" class="..." id="input-invalid" placeholder="Error" aria-invalid="true" data-invalid="true">

root: {
'data-disabled'?: string;
'data-has-submenu'?: string;
'data-submenu-open'?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same

export type TooltipState = Omit<TooltipBaseState, 'mountNode' | 'hidden'>;
export type TooltipState = Omit<TooltipBaseState, 'mountNode' | 'hidden'> & {
content: {
'data-visible'?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we need to unify the data-visible/data-open naming convention

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

agree, in my opinion, in most cases we should use data-open as it represents logical open / close state. data-visible can be used when visibility can differ from open / close (enter / exit transitions) and there are not so many cases as for data-open in the components. Here it was added because of parity with visible prop, but I do not think it should be the same as prop naming and it fits into the popover behaviors in other components.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated

state.root['data-disabled'] = stringifyDataAttribute(state.textarea.disabled);
// eslint-disable-next-line react-hooks/immutability
state.root['data-invalid'] = stringifyDataAttribute(
state.textarea['aria-invalid'] === true || state.textarea['aria-invalid'] === 'true',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same as above

@mainframev
Victor Genaev (mainframev) force-pushed the vgenaev/headless-data-attributes branch 2 times, most recently from 20545eb to c1622bb Compare August 10, 2026 11:50
@mainframev
Victor Genaev (mainframev) force-pushed the vgenaev/headless-data-attributes branch from c1622bb to eebc128 Compare August 10, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants