-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat(react-headless-components-preview): scaffold the package and add first components #35931
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
dmytrokirpa
merged 6 commits into
microsoft:master
from
dmytrokirpa:feat/export-base-hooks-from-suite
Apr 13, 2026
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
7397728
feat(react-headless-components-preview): scaffold the package and add…
dmytrokirpa 17976e4
add missing return type
dmytrokirpa 01cde59
add react-utilities export and fix lint issues for stories
dmytrokirpa 274492a
cleanup tailwindcss configuration
dmytrokirpa b9bff75
address review comments
dmytrokirpa 18483c6
Merge branch 'master' into feat/export-base-hooks-from-suite
dmytrokirpa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
packages/react-components/react-headless-components-preview/library/.swcrc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| { | ||
| "$schema": "https://json.schemastore.org/swcrc", | ||
| "exclude": [ | ||
| "/testing", | ||
| "/**/*.cy.ts", | ||
| "/**/*.cy.tsx", | ||
| "/**/*.spec.ts", | ||
| "/**/*.spec.tsx", | ||
| "/**/*.test.ts", | ||
| "/**/*.test.tsx" | ||
| ], | ||
| "jsc": { | ||
| "parser": { | ||
| "syntax": "typescript", | ||
| "tsx": true, | ||
| "decorators": false, | ||
| "dynamicImport": false | ||
| }, | ||
| "externalHelpers": true, | ||
| "transform": { | ||
| "react": { | ||
| "runtime": "classic", | ||
| "useSpread": true | ||
| } | ||
| }, | ||
| "target": "es2022" | ||
| }, | ||
| "minify": false, | ||
| "sourceMaps": true | ||
| } |
15 changes: 15 additions & 0 deletions
15
packages/react-components/react-headless-components-preview/library/LICENSE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| @fluentui/react-headless-components-preview | ||
|
|
||
| Copyright (c) Microsoft Corporation | ||
|
|
||
| All rights reserved. | ||
|
|
||
| MIT License | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
|
||
| Note: Usage of the fonts and icons referenced in Fluent UI React is subject to the terms listed at https://aka.ms/fluentui-assets-license |
7 changes: 7 additions & 0 deletions
7
packages/react-components/react-headless-components-preview/library/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # @fluentui/react-headless-components-preview | ||
|
|
||
| **React Headless Components for [Fluent UI React](https://react.fluentui.dev/)** | ||
|
|
||
| > [!WARNING] > **This package is in preview and not production-ready.** APIs may change without notice before final release. **Do not use in production.** | ||
| > | ||
| > This package exposes unstyled, headless Fluent UI v9 primitives for teams building custom design systems. For most teams, [`@fluentui/react-components`](https://www.npmjs.com/package/@fluentui/react-components) remains the recommended default. |
7 changes: 7 additions & 0 deletions
7
...components/react-headless-components-preview/library/bundle-size/AllComponents.fixture.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import * as rhc from '@fluentui/react-headless-components-preview'; | ||
|
|
||
| console.log(rhc); | ||
|
|
||
| export default { | ||
| name: 'react-headless-components-preview: entire library', | ||
| }; |
5 changes: 5 additions & 0 deletions
5
...ages/react-components/react-headless-components-preview/library/config/api-extractor.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", | ||
| "extends": "@fluentui/scripts-api-extractor/api-extractor.common.v-next.json", | ||
| "mainEntryPointFilePath": "<projectRoot>/../../../../../../dist/out-tsc/types/packages/react-components/<unscopedPackageName>/library/src/index.d.ts" | ||
| } |
3 changes: 3 additions & 0 deletions
3
packages/react-components/react-headless-components-preview/library/config/tests.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| /** Jest test setup file. */ | ||
|
|
||
| require('@testing-library/jest-dom'); |
201 changes: 201 additions & 0 deletions
201
packages/react-components/react-headless-components-preview/library/docs/Spec.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| # @fluentui/react-headless-components Spec | ||
|
|
||
| ## Background | ||
|
|
||
| `@fluentui/react-headless-components` is an **advanced, opt-in** package that exposes the headless layer of Fluent UI v9 components: pure component logic, accessibility patterns, and semantic slot structure — without any styling opinions. | ||
|
|
||
| It is intended for teams building custom design systems that significantly diverge from Fluent 2. For most teams, the default styled components in `@fluentui/react-components` remain the recommended path. | ||
|
|
||
| **What this package provides:** | ||
|
|
||
| - Unstyled primitive components (headless components) | ||
| - Component behavior, structure, and ARIA patterns | ||
| - Keyboard handling | ||
| - Semantic slot structure | ||
| - Building blocks for advanced composition: `use{Component}` and `render{Component}` | ||
| - Optional context-value hooks for compound components (for example, `useAccordionContextValues`) | ||
|
|
||
| **What this package does NOT provide:** | ||
|
|
||
| - Design props (`appearance`, `size`, `shape`, etc.) | ||
| - Style logic (Griffel, design tokens) | ||
| - Motion logic (animations, transitions) | ||
| - Default slot implementations (icons, components) | ||
|
|
||
| > **Important:** Base hooks provide ARIA attributes and semantic structure, but not visual accessibility (e.g., focus indicators, sufficient contrast). Consumers are responsible for implementing these in their custom styles. | ||
|
|
||
| ## Prior Art | ||
|
|
||
| - [RFC: Component Base State Hooks](../../../../../../docs/react-v9/contributing/rfcs/react-components/convergence/base-state-hooks.md) | ||
| - Fluent UI v9 `_unstable` hook convention used throughout `@fluentui/react-*` packages | ||
|
|
||
| ## Sample Code | ||
|
|
||
| Building a fully custom button using base hooks: | ||
|
|
||
| ```tsx | ||
| import * as React from 'react'; | ||
| import { useButton, renderButton } from '@fluentui/react-headless-components'; | ||
| import type { ButtonProps, ButtonState } from '@fluentui/react-headless-components'; | ||
|
|
||
| type CustomButtonProps = ButtonProps & { | ||
| variant?: 'primary' | 'secondary' | 'tertiary'; | ||
| tone?: 'neutral' | 'success' | 'warning' | 'danger'; | ||
| }; | ||
|
|
||
| export const CustomButton = React.forwardRef<HTMLButtonElement, CustomButtonProps>( | ||
| ({ variant = 'primary', tone = 'neutral', ...props }, ref) => { | ||
| const state = useButton(props, ref); | ||
|
|
||
| state.root.className = ['custom-btn', `custom-btn--${variant}`, `custom-btn--${tone}`, state.root.className] | ||
| .filter(Boolean) | ||
| .join(' '); | ||
|
|
||
| if (state.icon) { | ||
| state.icon.className = ['custom-btn__icon', state.icon.className].filter(Boolean).join(' '); | ||
| } | ||
|
|
||
| return renderButton(state as ButtonState); | ||
| }, | ||
| ); | ||
| ``` | ||
|
|
||
| ## API | ||
|
|
||
| ### Naming Conventions | ||
|
|
||
| | Artifact | Pattern | Example | | ||
| | ---------- | ------------------------ | -------------- | | ||
| | Primitive | `${ComponentName}` | `Button` | | ||
| | Hook | `use${ComponentName}` | `useButton` | | ||
| | Props type | `${ComponentName}Props` | `ButtonProps` | | ||
| | State type | `${ComponentName}State` | `ButtonState` | | ||
| | Render fn | `render${ComponentName}` | `renderButton` | | ||
|
|
||
| Public exports in this package use stable names and wrap internal `_unstable` base hooks from individual component packages. | ||
|
|
||
| ### Type Hierarchy | ||
|
|
||
| ```tsx | ||
| // Package types are the headless/base component contracts | ||
| type ButtonProps = ComponentProps<ButtonSlots> & { | ||
| disabled?: boolean; | ||
| disabledFocusable?: boolean; | ||
| iconPosition?: 'before' | 'after'; | ||
| }; | ||
|
|
||
| type ButtonState = ComponentState<ButtonSlots> & { | ||
| disabled: boolean; | ||
| disabledFocusable: boolean; | ||
| iconPosition: 'before' | 'after'; | ||
| iconOnly: boolean; | ||
| }; | ||
| ``` | ||
|
|
||
| ### Exported Components | ||
|
|
||
| Each exported component is available as an unstyled primitive component, with its low-level building blocks for advanced composition. | ||
|
|
||
| #### Accordion family | ||
|
|
||
| - `Accordion`, `AccordionItem`, `AccordionHeader`, `AccordionPanel` (unstyled primitives) | ||
| - `useAccordion`, `useAccordionItem`, `useAccordionHeader`, `useAccordionPanel` | ||
| - `renderAccordion`, `renderAccordionItem`, `renderAccordionHeader`, `renderAccordionPanel` | ||
| - Context hooks for advanced composition: `useAccordionContext`, `useAccordionContextValues` | ||
|
|
||
| #### Button | ||
|
|
||
| - `Button` (unstyled primitive) | ||
| - `useButton` | ||
| - `renderButton` | ||
|
|
||
| #### Divider | ||
|
|
||
| - `Divider` (unstyled primitive) | ||
| - `useDivider` | ||
| - `renderDivider` | ||
|
|
||
| ## Structure | ||
|
|
||
| ### Composition Layers | ||
|
|
||
| ```text | ||
| use{Component}Base_unstable (internal base state hook — logic + accessibility) | ||
| ↓ | ||
| use{Component} (public stable hook in this package) | ||
| ↓ | ||
| render{Component} (public render function in this package) | ||
| ↓ | ||
| {Component} (unstyled primitive component in this package) | ||
| ``` | ||
|
|
||
| This package exposes headless primitives and their building blocks. Styled components in `@fluentui/react-components` continue to compose on top of the same base logic. | ||
|
|
||
| ### Public API | ||
|
|
||
| Every exported component exposes: | ||
|
|
||
| - an unstyled primitive component (`Button`) | ||
| - a stable hook (`useButton`) | ||
| - a render function (`renderButton`) | ||
| - optional context-value hooks for compound patterns (`useAccordionContextValues`) | ||
|
|
||
| ### Internal | ||
|
|
||
| Each component's base logic lives in its individual package (for example, `@fluentui/react-button`). This package re-exports stable wrappers and primitives on top of that base logic. | ||
|
|
||
| ## Migration | ||
|
|
||
| This package is a new addition; there is no migration from v8 or v0. For teams currently using full Fluent UI components that want to adopt base hooks, the path is: | ||
|
|
||
| 1. Replace `useButton_unstable(props, ref)` with `useButton(props, ref)` from this package | ||
| 2. Remove design props (`appearance`, `size`, `shape`) from your props type and use `ButtonProps` from this package | ||
| 3. Apply your own class names or styles to the returned state slots before passing to the render function | ||
|
|
||
| ## Behaviors | ||
|
|
||
| Headless hooks encapsulate interactive behavior inherited by the styled component layer: | ||
|
|
||
| ### Component States | ||
|
|
||
| - **Disabled**: ARIA `disabled` and `aria-disabled` attributes set; keyboard events suppressed where appropriate | ||
| - **DisabledFocusable**: Element remains focusable while being semantically disabled (`aria-disabled="true"`) | ||
| - **Expanded / collapsed**: Accordion primitives manage disclosure state and relationships via ARIA | ||
|
|
||
| ### Interaction | ||
|
|
||
| #### Keyboard | ||
|
|
||
| Keyboard behavior is component-specific and follows WAI-ARIA authoring practices. Each hook applies the same keyboard handling as the corresponding styled component. | ||
|
|
||
| #### Cursor | ||
|
|
||
| No cursor styles are applied by base hooks. Consumers are responsible for setting appropriate cursor styles. | ||
|
|
||
| #### Touch | ||
|
|
||
| Touch events are handled via the same event handlers applied to root slots. | ||
|
|
||
| #### Screen readers | ||
|
|
||
| - ARIA roles, states, and properties are applied by the hooks | ||
|
|
||
| ## Accessibility | ||
|
|
||
| Headless hooks and primitives provide the semantic foundation for accessibility, but consumers must ensure their custom styles maintain: | ||
|
|
||
| - **Visible focus indicators** — base hooks do not apply focus ring styles | ||
| - **Sufficient color contrast** — base hooks do not apply colors or tokens | ||
| - **Appropriate visual feedback** for all interactive states (hover, active, disabled) | ||
|
|
||
| ### ARIA Patterns Applied | ||
|
|
||
| Each component follows its corresponding WAI-ARIA authoring practice: | ||
|
|
||
| | Component | ARIA pattern | | ||
| | --------- | --------------------- | | ||
| | Accordion | Accordion pattern | | ||
| | Button | Button / Link pattern | | ||
| | Divider | Separator pattern | | ||
|
|
||
| > Keyboard navigation, focus management, and state announcements are delegated to the individual component packages and are identical to their styled counterparts. |
5 changes: 5 additions & 0 deletions
5
packages/react-components/react-headless-components-preview/library/eslint.config.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| // @ts-check | ||
|
|
||
| const fluentPlugin = require('@fluentui/eslint-plugin'); | ||
|
|
||
| module.exports = [...fluentPlugin.configs['flat/react']]; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.