Skip to content

[Bug]: headless subpaths publish context values hooks but not their readers, so a styling layer cannot read the look context the package's own renderers provide #36652

Description

Component

Utilities

(concretely: AvatarGroup, Button, Link, Avatar — plus TagGroup, InteractionTag and Accordion, which
are the same gap and are still open.)

Package version

@fluentui/react-headless-components-preview 0.2.5

React version

18.3.1

Environment

node v22.12.0
@fluentui/react-headless-components-preview 0.2.5

Current Behavior

Several headless subpaths publish a context's values hook and its types, but not the reader
— so a consumer composing their own parts cannot read what the subpath's own render functions
already provide.

The gap as measured before the fix:

Subpath Published Missing
./avatar-group AvatarGroupContextValue, both useAvatarGroupContextValues hooks the reader useAvatarGroupContext_unstable and the provider
./button the Button context
./link the Link context
./avatar the Avatar context
./tag-group useTagGroupContextValues useTagGroupContext_unstablestill open
InteractionTag values hook (13 hits in-tree) the reader (zero hits) — still open
./accordion useAccordionContext useAccordionItemContext, useAccordionHeaderContextstill open

The package is internally inconsistent about this: Toolbar/useToolbar.ts:27 already does the right
thing —
export { useToolbarContext_unstable as useToolbarContext } from '@fluentui/react-toolbar'; — and
./avatar now does too. The others stop at the values hook.

This is not a nicety, and one instance was a measured rendering defect. For Button/Link/Avatar
the provider side was already correct: the headless renderers are verbatim re-exports of
Griffel's and already wrap subtrees in Griffel's ButtonContextProvider / LinkContextProvider /
AvatarContextProvider (renderMessageBarActions.tsx:23, renderMessageBarBody.tsx:19,
renderTag.tsx:18). The gap was entirely consumer-side — and because those providers are Griffel's
own context instances, a locally-declared context can never receive the values. A re-export is
the only fix available to a consumer.

Measured symptom: a styled Button inside a styled MessageBarActions rendered medium where
the @fluentui/react-components equivalent renders small, because the size the provider was
already publishing was unreachable. Context identity was verified directly
(ButtonContextProvider from the headless package === the one from @fluentui/react-button, and
likewise for Link and Avatar).

Expected Behavior

Every headless subpath that publishes a context's values hook also publishes its reader (and, where
the package owns the provider, the provider), so that a consumer building their own parts can
participate in the same contract the package's own render functions do.

Reproduction

// Today: there is nothing to import.
import { useAvatarGroupContext } from '@fluentui/react-headless-components-preview/avatar-group';
//       ^ not exported

// And a locally-declared context is not a workaround for Button/Link/Avatar:
// the provider in the tree is Griffel's own instance, so a local context never receives values.

Steps to reproduce

  1. Compose a custom Button inside a headless MessageBarActions.
  2. Try to read the size the provider is publishing.
  3. Observe there is no exported reader, and that a locally-declared context reads its default.

Discovery context

Found during pixel-level verification of a styling layer built over
@fluentui/react-headless-components-preview — the layer supplies its own presentation for each
part, so it needs to read the look context the container publishes. The AvatarGroup and
Button/Link/Avatar instances were the ones that blocked shipped components; the TagGroup,
InteractionTag and Accordion instances were found by auditing the rest of the package for the same
shape and are listed above so the gap can be closed once rather than four times.

Proposed fix

One line per context in the component barrel, plus the matching subpath entry — the shape
Toolbar/useToolbar.ts:27 and Avatar/avatarContext.ts already use. For AvatarGroup that is a
small dedicated avatarGroupContext.ts module (mirroring ./avatar's) plus two barrel entries; for
Button/Link/Avatar, three dedicated context modules plus three barrel entries and three subpath
entries. Additive in every case — nothing that exists today changes meaning, and the API reports
regenerate cleanly.

A working implementation for four of the seven sites (AvatarGroup, Button, Link, Avatar) is
included in PR #[WINDMOD-PR-NUMBER] (commits 5a89364f3f and 9612d547d9), with the etc/*.api.md
files regenerated. The remaining three — TagGroup, InteractionTag, Accordion — want the same
one-line-per-context treatment and are not in that PR; a drafted TagGroup change type-checks
(exit 0) with a negative control that fails as expected (exit 2), and can be contributed here.

Happy to split the whole thing into a standalone PR against this issue.

Suggested severity

High - No workaround (for Button/Link/Avatar specifically: the provider is a foreign context
instance, so no consumer-side declaration can substitute).

Are you willing to submit a PR to fix?

yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions