Skip to content

docs: add headless component authoring skill - #36502

Open
Dmytro Kirpa (dmytrokirpa) wants to merge 3 commits into
microsoft:masterfrom
dmytrokirpa:dmytrokirpa/headless-component-skill
Open

docs: add headless component authoring skill#36502
Dmytro Kirpa (dmytrokirpa) wants to merge 3 commits into
microsoft:masterfrom
dmytrokirpa:dmytrokirpa/headless-component-skill

Conversation

@dmytrokirpa

Copy link
Copy Markdown
Contributor

Summary

  • add a Copilot skill for authoring and extending headless v9 component primitives
  • document base-hook, data-attribute, packaging, bundle-size, test, Storybook, CSS Module, and accessibility requirements
  • define a two-PR gh stack workflow separating component-package base APIs from the dependent headless package
  • register the skill for Copilot and Claude entry points

Validation

  • verified all documented headless library, stories, and docsite Nx targets exist
  • verified installed gh stack and Beachball flags used by the workflow
  • checked skill metadata, mirror pointer, portable paths, and diff formatting

No Beachball change file is required because this change does not modify a published package.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 806b7b8e-562f-4b2a-b4f5-b9d06fc6131f
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

📊 Bundle size report

✅ No changes found

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Pull request demo site: URL

6. Record every inventory row as implemented, intentionally excluded with a
source-backed reason, or unresolved. Trace implemented rows through:
`types/exports -> hook/context -> rendered DOM/slots -> test or story`.
7. Plan the change as two stacked PRs before editing:

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.

if we need regression test for styled hook, then it should be 3 separate PRs, do you think we should have that covered by skill too?

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.

I think this should be a mandatory preflight

- When adding a new v9 base hook, follow the repository-enforced signature:
`useComponentNameBase_unstable(props, ref?)`, with an explicitly typed
`props` parameter and `ref` typed as `React.Ref<...>`.
- Keep base hooks free of Tabster and other forbidden focus/keyboard runtimes.

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.

should we mention that icons should not be part of base hooks & bundleIcon on module level?

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 all of these as part of harness now, wondering if agent would recover when failing lint/verify-bundle task

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.

wondering if this would do the job most determinstically:


- Generate new control via `nx .....`
- Make sure `lint` targets and `verify-bundle-isolation` are passing

State attributes are the supported CSS targeting contract.

- Presence state: emit `''` when true and omit when false.
- Boolean-valued state: emit `'true'` or `'false'` when both values are

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.

should not it also following presence / absence approach?

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.

confused what is our pattern ? we agreed on absence api for boolean but I saw in Victor Genaev (@mainframev) we are doing true :D ?

6. Add a default story plus stories for important behavioral states.
7. Exercise the primary interaction instead of rendering only static variants.
Interactive stories should drive the real component behavior and assert the
resulting role/ARIA/data state in a `play` function. Cover the inverse action

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.

AFAIR we do not use play functions, that was probably migrated from the 1JS skill, right?

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.

from CompoundButton PR:

image

--package @fluentui/react-<name> --type minor \
--message "feat(react-<name>): expose headless base APIs"

# top branch, after editing only the headless library and stories

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 should correct a bit these examples with beachball instructions as we following zero based semver for headless until we are stable

`yarn beachball change` form; never create or edit them manually.
- After generation, inspect the new JSON file and verify it contains exactly the
owning package and no inherited package from another stack layer.
- New public base exports and new headless primitives are normally `minor`

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.

here we should also correct it, it will be correct later when headless will become stable


### Renderer and Context

- Re-export or alias the upstream unstable renderer when its slot structure is

@mainframev Victor Genaev (mainframev) Aug 6, 2026

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.

maybe some examples needed, I see that for CompoundButton it created it in a following format, instead of one line re-export:

import { renderCompoundButton_unstable } from '@fluentui/react-button';

/**
 * Renders a CompoundButton component.
 */
export const renderCompoundButton = renderCompoundButton_unstable;

# top branch, after editing only the headless library and stories
yarn beachball change --no-commit --branch master \
--package @fluentui/react-headless-components-preview --type minor \
--message "feat(react-headless-components-preview): add <ComponentName>"

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.

Suggested change
--message "feat(react-headless-components-preview): add <ComponentName>"
--message "feat: add <ComponentName>"

I think we can omit the package scope in message as change file has packageName

### Component-Package PR

- Inspect `packages/react-components/react-<name>/library/bundle-size/`.
- Every new public runtime export must be referenced by a bundle fixture. Named

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.

with the skill running for a component it created fixtures for base hooks, probably we should explicitly mention that bundle fixture should be added to components only

Copilot AI 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.

Pull request overview

Adds guidance for authoring and extending headless Fluent UI v9 components.

Changes:

  • Documents implementation, testing, packaging, and accessibility requirements.
  • Defines stacked-PR and validation workflows.
  • Registers the skill for Copilot and Claude.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
AGENTS.md Registers the new slash command.
.agents/skills/headless-component/SKILL.md Defines the authoring workflow and requirements.
.claude/skills/headless-component/SKILL.md Adds the Claude skill pointer.
Suppressed comments (3)

.agents/skills/headless-component/SKILL.md:467

  • Headless and v9 stories intentionally keep interaction assertions in tests rather than Storybook play functions. Requiring play here would make generated stories violate that convention; keep the story interactive/demonstrable and put the role, ARIA, data-state, and inverse-action assertions in unit or Cypress coverage.
7. Exercise the primary interaction instead of rendering only static variants.
   Interactive stories should drive the real component behavior and assert the
   resulting role/ARIA/data state in a `play` function. Cover the inverse action
   too; if the behavior requires a real browser primitive unavailable to the
   Storybook test runner, cover it in Cypress and keep the story demonstrable.

.agents/skills/headless-component/SKILL.md:571

  • This preview package is currently 0.2.4 and follows zero-semver: new headless components have shipped as patches (for example TagPicker and MenuButton in library/CHANGELOG.md:12-28). minor would incorrectly advance 0.2.x to 0.3.0 for a normal additive primitive.
yarn beachball change --no-commit --branch master \
  --package @fluentui/react-headless-components-preview --type minor \
  --message "feat(react-headless-components-preview): add <ComponentName>"

.agents/skills/headless-component/SKILL.md:591

  • This rule incorrectly assigns minor to new primitives in the 0.x headless preview package. Keep minor for new exports in stable v9 component packages, but use patch for additive headless-preview APIs under its zero-semver policy; otherwise future skill runs will create the wrong release bump.
- New public base exports and new headless primitives are normally `minor`
  changes. Use `patch` only when correcting an existing contract without adding
  public API, and never use `major` without explicit approval.

Comment on lines +78 to +82
7. Plan the change as two stacked PRs before editing:
- **bottom PR — component package:** base hook/types/renderer/context exports
and their package-local tests, API docs, bundle-size coverage, manifest
updates, and change file
- **top PR — headless package:** stable headless primitive, `data-*`
# bottom branch, after editing only the component package
yarn beachball change --no-commit --branch master \
--package @fluentui/react-<name> --type minor \
--message "feat(react-<name>): expose headless base APIs"

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.

valid

6. Record every inventory row as implemented, intentionally excluded with a
source-backed reason, or unresolved. Trace implemented rows through:
`types/exports -> hook/context -> rendered DOM/slots -> test or story`.
7. Plan the change as two stacked PRs before editing:

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.

I think this should be a mandatory preflight

Add `*.cy.tsx` integration coverage when behavior depends on real browser focus,
Tabster, positioning, portals, native popovers, or nested trigger composition.

## Implementation

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.

should we just wire a generator for this ?

- When adding a new v9 base hook, follow the repository-enforced signature:
`useComponentNameBase_unstable(props, ref?)`, with an explicitly typed
`props` parameter and `ref` typed as `React.Ref<...>`.
- Keep base hooks free of Tabster and other forbidden focus/keyboard runtimes.

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 all of these as part of harness now, wondering if agent would recover when failing lint/verify-bundle task

- When adding a new v9 base hook, follow the repository-enforced signature:
`useComponentNameBase_unstable(props, ref?)`, with an explicitly typed
`props` parameter and `ref` typed as `React.Ref<...>`.
- Keep base hooks free of Tabster and other forbidden focus/keyboard runtimes.

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.

wondering if this would do the job most determinstically:


- Generate new control via `nx .....`
- Make sure `lint` targets and `verify-bundle-isolation` are passing

State attributes are the supported CSS targeting contract.

- Presence state: emit `''` when true and omit when false.
- Boolean-valued state: emit `'true'` or `'false'` when both values are

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.

confused what is our pattern ? we agreed on absence api for boolean but I saw in Victor Genaev (@mainframev) we are doing true :D ?

# bottom branch, after editing only the component package
yarn beachball change --no-commit --branch master \
--package @fluentui/react-<name> --type minor \
--message "feat(react-<name>): expose headless base APIs"

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.

valid

Comment thread AGENTS.md
| Skill | Command | Purpose |
| -------------------- | -------------------------- | ------------------------------------------------------------------------- |
| `v9-component` | `/v9-component Name` | Scaffold a new v9 component with all required files |
| `headless-component` | `/headless-component Name` | Author an unstyled v9 primitive with state attributes, tests, and stories |

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.

if we have this we should consider to disable model invocation on the headless skill completely

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants