npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@atomic-testing/component-driver-fluent-v9

v0.104.0

Published

Component driver for Fluent UI v9 ("Fluent 2") React components (@fluentui/react-components)

Readme

@atomic-testing/component-driver-fluent-v9

Component drivers for Fluent UI v9 ("Fluent 2", @fluentui/react-components). Component drivers expose simple APIs for unit tests or end-to-end tests to interact with Fluent-based components—reading state and driving actions—so test engineers focus on test flows instead of the component internals.

The problem

Fluent v9 styles every component with Griffel, an atomic CSS-in-JS engine — the classes it emits are hashed and change across builds, so they are not stable test anchors. Fluent also ships a strong accessibility program (it is the Microsoft 365/Office design system), so the stable anchors, in priority order, are:

  1. role + ARIA state — Fluent renders correct roles/aria-* per component (e.g. aria-pressed on ToggleButton, aria-disabled on Link).
  2. Fluent's own un-hashed structural classes — every component stamps a plain fui-<ComponentName> class (and fui-<ComponentName>__<part> for sub-parts, e.g. fui-Field__hint) alongside the hashed Griffel utility classes. These are Fluent-owned and stable across releases; the drivers in this package use them where role/ARIA isn't enough (e.g. FieldDriver's hint/validation-message reads).
  3. Never the hashed Griffel utility classes.

Several core controls (Input, Textarea, Checkbox, Switch, Radio, Select) render as real native form elements at their root — data-testid (or any locator) placed on the component lands directly on the native <input>/<textarea>/<select>, not a styled wrapper — so this package reuses @atomic-testing/component-driver-html's drivers wholesale wherever that holds.

The solution

The drivers in this package locate Fluent parts by those stable anchors and expose high-level interactions. Combined with the React adapter (@atomic-testing/react-19 or another React major), the same scene definitions run across DOM (jsdom) and end-to-end (Playwright) tests.

Target package & version pin

This driver targets Fluent UI v9 and is declared as a peer dependency pinned to ^9.0.0: consumers bring their own @fluentui/react-components. Fluent v8 (@fluentui/react) is a materially different DOM/styling contract (mergeStyles, no Griffel) and is out of scope for this package.

Installation

npm install @atomic-testing/core @atomic-testing/react-19 \
  @atomic-testing/component-driver-html @atomic-testing/component-driver-fluent-v9 \
  @fluentui/react-components --save-dev

Refer to the documentation for usage patterns and examples.

Portal & overlay recipe (Wave 2)

Dialog, Popover, Drawer (OverlayDrawer), Menu, Toast/Toaster, and TeachingPopover all portal by default — verified against rendered DOM (@fluentui/[email protected]): each mounts into a cloned FluentProvider on document.body (Fluent's own mountNode default, confirmed against @fluentui/react-portal's types), a sibling of the render root rather than a descendant of the trigger. InlineDrawer is the one exception — it renders in-tree, no portal at all. Two distinct re-root techniques cover this wave, chosen per component by whether the scene's own locator can land directly on the portalled surface:

  • Static re-root + class compounding (Dialog, Popover, OverlayDrawer, TeachingPopover, Toaster) — the driver overrides the overriddenParentLocator()/overrideLocatorRelativePosition() static hooks (see packages/core/src/drivers/ComponentDriver.ts) to re-root at the un-hashed Fluent structural class of that surface (e.g. .fui-DialogSurface, .fui-PopoverSurface), and the scene's own locator (forwarded onto the surface component, e.g. <DialogSurface data-testid="...">) compounds onto that SAME element. Two simultaneously open instances disambiguate correctly because each surface only matches its own forwarded test id — verified with two open dialogs/popovers side by side. Anchored on the class rather than role where the role is shared: role="dialog" alone is worn by Dialog, OverlayDrawer, and TeachingPopover; role="group" is far too generic for Popover alone.
  • Trigger-anchored + byLinkedElement (Menu) — the driver is constructed from the TRIGGER locator and resolves the portalled MenuList by following the trigger's id to the list's aria-labelledby, re-read fresh on every call (byLinkedElement, the same technique component-driver-radix-v1 uses for its own aria-controls/aria-describedby links). Necessary because role="menu"/role="presentation" are identical across every simultaneously open menu — a static class/role re-root cannot tell "this menu" from a sibling one, but the trigger↔list id link can. Verified with two open menus side by side.
  • Trigger-anchored + byLinkedElement on aria-controls (Combobox, Dropdown, TagPicker, Wave 3) — the same byLinkedElement idiom as Menu, but following the trigger/input's aria-controls (not aria-labelledby) to the portalled listbox, and the attribute is present ONLY while open (absent entirely, not merely empty, once closed) — every listbox-reading method guards the resolution in try/catch, and isOpen() reads the trigger's own aria-expanded rather than listbox existence, since Fluent keeps the listbox mounted briefly after a legitimate close.

Tooltip is trigger-anchored too, but for a different reason: its content carries no reliable per-instance link in the default relationship="label" mode (only relationship="description" sets aria-describedby), so isOpen() falls back to a best-effort shared-portal check in that mode — see the Known gaps section below and TooltipDriver's TSDoc.

Escape dismisses the topmost stacked overlay, not a specific targeted one — verified against real Chromium: with two overlays open, Escape always closes the most-recently-opened one, regardless of which overlay's locator the key event is dispatched on (Fluent's dismiss handling is a global, stack-ordered listener). Drive closeByEscape() on the LAST-opened instance in a stacked scenario.

Drivers

Drivers land in waves (see the umbrella issue #1098); all 6 waves (core form primitives; overlays & portals; selection & specialized inputs; navigation & disclosure; data display & feedback; complex/composite) ship in full below, plus a Wave 6 follow-up (issue #1138) adding FlatTree/FlatTreeItem — this is the complete driver catalog.

| Driver | Fluent component | Notes | | ---------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ButtonDriver | Button | Native <button>; delegates wholesale to HTMLButtonDriver. | | CompoundButtonDriver | CompoundButton | Same native <button> root as Button; getSecondaryContent() reads the fui-CompoundButton__secondaryContent part (see JSDoc for the known primary/secondary text-splitting limitation). | | ToggleButtonDriver | ToggleButton | Native <button>; pressed state read/written via aria-pressed (no native "pressed" concept exists for <button>). | | InputDriver | Input | The root IS a native <input> — full HTMLTextInputDriver surface, incl. isError via aria-invalid. | | TextareaDriver | Textarea | The root IS a native <textarea> — full HTMLTextAreaDriver surface. | | CheckboxDriver | Checkbox | Extends HTMLCheckboxDriver (the root IS a real native <input type="checkbox">); label prop renders a sibling <label for>, resolved via the forid link. isIndeterminate() reads the live .indeterminate property via the :indeterminate CSS pseudo-class. | | SwitchDriver | Switch | Same shape as Checkbox, but no value concept (pure on/off) — does not implement IFormFieldDriver. | | RadioDriver | Radio | The root IS a real native <input type="radio">; setSelected(false) is rejected (native radio semantics). | | RadioGroupDriver | RadioGroup | Delegates to HTMLRadioButtonGroupDriverpoint its ScenePart locator at the radio inputs (e.g. the group container appended with an input[type="radio"] descendant selector), not at the [role="radiogroup"] wrapper. | | SelectDriver | Select | The root IS a native <select> — full HTMLSelectDriver surface. | | LabelDriver | Label | Plain native <label>; getFor() reads the linked control's id. | | FieldDriver | Field | Container wrapper; getLabel/getHint/getValidationMessage read descendant parts anchored on Fluent's fui-Field__* structural classes. | | LinkDriver | Link | Native <a>; overrides isDisabled to read aria-disabled (an anchor has no native disabled property). | | DividerDriver | Divider | [role="separator"]; getOrientation() reads aria-orientation. | | ImageDriver | Image | Native <img>; getSrc/getAlt read attributes directly. | | TextDriver | Text | Plain content wrapper; all behavior is inherited (getText). |

Wave 2 — overlays & portals (see the portal & overlay recipe above):

| Driver | Fluent component | Notes | | ------------------------ | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | DialogDriver | Dialog (+ DialogSurface/Body/Title/Content/Actions) | Portalled; re-roots on .fui-DialogSurface. isModal() reads aria-modal; non-modal dialogs auto-render a close X. No portable closeByBackdropClick (the backdrop is an un-linkable document.body sibling, not a descendant) — only closeByEscape. | | PopoverDriver | Popover (+ PopoverTrigger/Surface) | Portalled; re-roots on .fui-PopoverSurface (role is the too-generic "group"). Shares that class with TeachingPopoverSurface — use TeachingPopoverDriver for that component specifically. | | OverlayDrawerDriver | OverlayDrawer | Portalled; re-roots on .fui-OverlayDrawer (role="dialog", shared with Dialog/TeachingPopover). defaultOpen is deprecated/non-functional — drive it via the controlled open prop. | | InlineDrawerDriver | InlineDrawer | Renders in-tree — no portal, no re-root, unlike every other driver in this wave. | | DrawerDriverBase | shared base | getHeaderTitle/getBodyText + open/close lifecycle common to both drawer variants. | | MenuDriver | Menu (+ MenuTrigger/Popover/List) | Constructed from the TRIGGER locator; resolves the portalled MenuList via the trigger id ↔ list aria-labelledby link (byLinkedElement) — correctly disambiguates two simultaneously open menus. | | MenuItemDriver | MenuItem | role="menuitem"; getLabel/isDisabled. | | MenuItemCheckboxDriver | MenuItemCheckbox | role="menuitemcheckbox"; adds isChecked() via aria-checked. Selecting one persists the open menu. | | MenuItemRadioDriver | MenuItemRadio | role="menuitemradio"; adds isChecked() via aria-checked. Selecting one closes the menu (unlike checkbox items) — re-open to observe the persisted choice. | | MenuButtonDriver | MenuButton | Native <button> (delegates to HTMLButtonDriver); getMenu() returns the MenuDriver it opens. | | SplitButtonDriver | SplitButton | Wrapper <div> around a primary action button and a menu-invoking button; clickPrimary()/getMenu() expose each half separately (the base click() on the wrapper is not meaningful). | | TooltipDriver | Tooltip | Trigger-anchored. getContent() reads aria-label (default relationship="label") or follows aria-describedby (relationship="description") — available regardless of open state, since Fluent mounts the content unconditionally. isOpen()'s fallback for "label"-relationship tooltips is best-effort when multiple tooltips are mounted (see Known gaps). Opens via .focus(), not hover (hover does not reveal Fluent's tooltip under jsdom). | | ToasterDriver | Toaster | Portalled; re-roots on .fui-Toaster. Deliberately has no caller-supplied interior — toasts arrive dynamically via dispatchToast, read positionally/by-title (getToastByIndex/getToastByTitle) rather than as a declared scene. | | ToastDriver | Toast (+ Title/Body) | getTitle/getBodyText; no built-in dismiss button (use a declared content part for consumer-supplied actions, like component-driver-mui-v9's SnackbarDriver). | | TeachingPopoverDriver | TeachingPopover (+ Header/Title/Body/Footer) | Portalled; re-roots on the more-specific .fui-TeachingPopoverSurface (not the shared .fui-PopoverSurface). Has a built-in dismiss button, unlike plain Dialog/Popover. |

Wave 3 — selection & specialized inputs:

| Driver | Fluent component | Notes | | ------------------------ | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ComboboxDriver | Combobox (+ Option/OptionGroup) | Root IS a native <input role="combobox"> — extends HTMLTextInputDriver wholesale. Listbox is trigger-anchored via aria-controls byLinkedElement (same technique as MenuDriver). No selectByValue — Fluent never reflects Option's value to the DOM, only the label. Single-select only. | | ComboboxOptionDriver | Option | role="option"; getLabel/isDisabled/isSelected — a standalone class, not sharing MenuItemDriver's inheritance. | | DropdownDriver | Dropdown (@fluentui/react-select) | NOT the native-<select>-backed Select despite the shared naming — a fully custom combobox widget. Trigger is a real <button role="combobox">; isOpen() reads its aria-expanded (the listbox stays mounted post-close). Listbox resolved via aria-controls byLinkedElement. Single-select only. | | DropdownOptionDriver | Option (single-select Dropdown) | role="option"; getLabel/isSelected/isDisabled. | | SliderDriver | Slider | Root IS a native <input type="range"> — extends HTMLRangeInputDriver wholesale. Adds getMin/getMax/getStep/isRequired/getLabel. Single-thumb only — Fluent v9 ships no multi-thumb variant. | | SpinButtonDriver | SpinButton | Root IS the native <input role="spinbutton">; stepper <button>s reached via the general-sibling combinator (~), same escape hatch as component-driver-astryx's NumberInputDriver. setValue types then blurs to commit; increment/decrement click the steppers; moveToMin/moveToMax/incrementByPage/decrementByPage drive Home/End/PageUp/PageDown. | | SwatchPickerDriver | SwatchPicker | Does not portal. Items matched on the un-hashed .fui-ColorSwatch class (role flips radio/gridcell with layout). No getValue/selectByValue — color-based equivalents instead (getSwatchColors/selectByColor/getSelectedColor). | | SwatchPickerItemDriver | ColorSwatch | Real native <button>; isSelected() reads aria-checked falling back to aria-selected. setSelected(false) rejected (no self-deselect). getColor() reads the --fui-SwatchPicker--color inline CSS var — no getValue(). | | RatingDriver | Rating | Root IS role="radiogroup" with visually-hidden native radio items; getValue/setValue drive the :checked radio via Interactor.activate. No native disabled/readOnly — isDisabled() reads a :disabled descendant radio (consumer <fieldset disabled> cascade). | | RatingDisplayDriver | RatingDisplay | Read-only sibling of Rating; root is role="img", never radiogroup. Does not implement IInputDriver. getValue/getCount read fui-RatingDisplay__valueText/__countText. | | TagDriver | Tag | Static, non-dismissible tag; root IS <span class="fui-Tag">. getLabel() reads the fui-Tag__primaryText part. Does not implement IDisableableDriverdisabled has zero DOM reflection. | | InteractionTagDriver | InteractionTag (+ Primary/Secondary) | Dismissible tag; wrapper <div> around two real native <button>s, delegated wholesale to HTMLButtonDriver. isDisabled() reads the Primary button's disabled. clickPrimary() selects; dismiss() removes. | | TagGroupDriver | TagGroup | List container; never portals. isDisabled() reads the group's own always-present aria-disabled. getTagCount/getTagLabels/getTagByIndex enumerate mixed Tag/InteractionTag children positionally. | | TagPickerDriver | TagPicker (+ Control/Group/Input/List/Option) | Constructed from TagPickerControl's locator (<TagPicker> itself renders no DOM). Portalled TagPickerList resolved via aria-controls byLinkedElement; isOpen reads the input's aria-expanded. getSelectedLabels/removeSelected read the in-tree TagPickerGroup; getOptionCount/selectByLabel auto-open the portalled list. | | TagPickerOptionDriver | TagPickerOption / a selected Tag | Shared getLabel/isDisabled surface for both an open-list option and an already-selected tag (both render role="option"). No portable isSelected for either. | | SearchBoxDriver | SearchBox | Same native <input type="search"> root as Input — full HTMLTextInputDriver surface, plus clear()/hasClearButton(). The dismiss button renders unconditionally by default and is a sibling with no per-instance link, resolved via the ancestor-:has() re-root (same idiom as component-driver-mui-v9's CheckboxDriver). | | ColorPickerDriver | ColorPicker (+ ColorArea/ColorSlider) | Composite; renders its own <div class="fui-ColorPicker"> root, unlike a bare context provider. area/hueSlider parts locate children by un-hashed structural classes. Strictly a controlled component (color/onColorChange only). | | ColorAreaDriver | ColorArea | The 2-D saturation/value picker; root wraps two real native <input type="range">s (inputX/inputY), driven via setRangeValue/getInputValue. No disabled/required (not valid attributes on a plain <div>). | | ColorSliderDriver | ColorSlider | The 1-D hue slider; locator lands straight on a real native <input type="range"> — same shape as the standalone SliderDriver, so this driver extends HTMLRangeInputDriver wholesale. |

Wave 4 — navigation & disclosure: none of this wave's components portal by default except NavDrawer (which reuses the Wave 2 OverlayDrawer recipe). A unifying design rule shapes the whole wave: a driver models each independently INTERACTIVE/addressable unit; purely structural wrapper elements (a decorative divider, a header/panel pairing that's always 1:1 with its item, a non-interactive group <div>) fold into their interactive sibling's driver rather than getting their own class — the same way AccordionHeader/AccordionPanel fold into AccordionItemDriver, and BreadcrumbDivider isn't modeled at all (see its row below).

| Driver | Fluent component | Notes | | -------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | TabListDriver | TabList | A ListComponentDriver over role="tab" children (templated off component-driver-mui-v9's TabsDriver), since TabList has no built-in TabPanel — panel wiring is left to the consumer. Tab's value prop genuinely reflects onto the native value attribute (unlike Option.value elsewhere in this package), so getSelectedValue/selectByValue are reliable, un-hashed reads. | | TabDriver | Tab | Real <button role="tab">; aria-selected is absent entirely (not "false") when disabled. Overrides getText(): when reserveSelectedTabSpace (the default) and the tab is unselected, Fluent renders an invisible SECOND copy of the label in a fui-Tab__content--reserved-space span purely to reserve layout width — the inherited whole-button text read would return the label doubled (verified: "ProfileProfile"), so this driver reads .fui-Tab__content specifically instead. | | BreadcrumbDriver | Breadcrumb | Items live inside a child <ol role="list">, interleaved with decorative <li> dividers — both share the <li> tag, so item enumeration uses childListHelper's :nth-child + class-selector filter (same mixed-sibling shape TagGroupDriver handles) rather than ListComponentDriver. BreadcrumbDivider has no driver — purely decorative (aria-hidden), see the wave-level rule above. | | BreadcrumbItemDriver | BreadcrumbItem | The <li> wrapper itself carries no interactive state; folds in BreadcrumbButton's behavior via getButton() and overrides click() to target the button, not the wrapper. | | BreadcrumbButtonDriver | BreadcrumbButton | Renders <a> (with href) or <button>isDisabled() combines a native-disabled check with an aria-disabled read (mirrors LinkDriver's reasoning) since either element shape is possible. isCurrent() reads aria-current="page", present only when the current prop is set. | | AccordionDriver | Accordion | A ListComponentDriver over fui-AccordionItem children (homogeneous <div> siblings, safe for :nth-of-type). multiple/collapsible mode has zero DOM reflection (grepped the compiled package) — this driver exposes no mode getter; drive/observe expansion per item instead. | | AccordionItemDriver | AccordionItem (+ AccordionHeader/AccordionPanel) | Folds header/panel state in directly (templated off component-driver-radix-v1's single-item AccordionDriver). AccordionPanel fully UNMOUNTS while collapsed (Fluent's hard-coded unmountOnExit) — getPanelText() returns null whenever absent. collapse() no-ops on the parent's OWN collapsible prop (Fluent refuses to reach zero open items otherwise) — see Known Gaps. click() no-ops on a disabled header, same portability contract as RadioDriver.setSelected. | | NavDriver | Nav | Never portals. Item enumeration (NavItem/NavCategory/NavCategoryItem/NavSubItem, arbitrarily nested) walks via childListHelper's groupSelector: '*' recursion, flattening the tree. Shares its item-query surface with NavDrawerDriver via the internal NavDriverBase. | | NavDrawerDriver | NavDrawer | Portal-backed by default (wraps DrawerOverlayDrawer, same as Wave 2) — re-roots on the un-hashed fui-NavDrawer class. Corrects the umbrella issue's hypothesis: "Nav's flyouts are overlay-backed" is true only of NavDrawer's own outer surface, NOT of NavCategory expansion inside it (see NavCategoryItemDriver). Targets the default (portal) variant only — type="inline" isn't covered. | | NavItemDriver | NavItem / NavSubItem | Renders <a href> or <button> — the component's OWN root IS the interactive element (no wrapper to look past, unlike BreadcrumbItemDriver). aria-current is always a literal "page"/"false" string, never merely absent. | | NavCategoryItemDriver | NavCategoryItem | Extends NavItemDriver, adding expand/collapse. Always a real <button>, never <a>. No portal — grepped the entire compiled @fluentui/react-nav package for Popover/Menu/role="menu": zero matches; the sub-item group is a same-tree animated accordion, reached via the general-sibling CSS combinator (same escape hatch SpinButtonDriver uses for its steppers). | | ToolbarDriver | Toolbar | aria-orientation is present only when verticalgetOrientation() defaults the horizontal case rather than passing through null. Button enumeration (getButtonByLabel) descends one level into .fui-ToolbarGroup wrappers via childListHelper's groupSelector (ToolbarRadioGroup shares this same class, no separate fui-ToolbarRadioGroup class is exported), since ToolbarButton/ToolbarToggleButton/ToolbarRadioButton all share the identical fui-Button class. | | ToolbarButtonDriver | ToolbarButton | Plain native <button class="fui-Button"> — no fui-ToolbarButton class of its own; delegates wholesale, like ButtonDriver. | | ToolbarDividerDriver | ToolbarDivider | Delegates to DividerDriver wholesale (identical DOM, no separate class). Orientation is INVERTED relative to the toolbar — verified against source (vertical: !toolbarContext.vertical): a horizontal toolbar's divider itself reports 'vertical'. | | ToolbarRadioGroupDriver | ToolbarRadioGroup | ToolbarRadioGroup IS ToolbarGroup with role="radiogroup" forced on, sharing its class — no native :checked/[value=] to delegate to HTMLRadioButtonGroupDriver, so this is a ListComponentDriver over role="radio" buttons instead (mirrors TabListDriver's shape). | | ToolbarRadioButtonDriver | ToolbarRadioButton | Real <button role="radio" aria-checked> — Fluent explicitly strips the aria-pressed its shared toggle-button primitive would otherwise carry. setSelected(false) is rejected, same contract as RadioDriver. | | OverflowDriver | Overflow | Overflow/OverflowItem render NO wrapper of their own — they clone ref/class/data-* attributes onto the consumer's OWN child, so the scene locator must target that element directly, assuming the idiomatic flat-row usage (every item a direct child of the wrapped container). getOverflowMenu() returns a plain Wave 2 MenuDriver resolved via [data-overflow-menu] — no new portal logic needed, since useOverflowMenu() wires a trigger but builds no Menu of its own. | | OverflowItemDriver | OverflowItem | isOverflowing() reads the data-overflowing attribute directly (portable/jsdom-safe) rather than relying on computed CSS visibility, since overflowed items stay mounted (display: none via a stylesheet rule) rather than being removed. |

Wave 5 — data display & feedback: presentational/read-state drivers — the fastest wave per-component (few new interactions, no portals except InfoButton's own inline-by-default popover). A unifying design rule shapes several of this wave's composite components: CardDriver folds CardHeader/CardFooter/CardPreview reads directly onto itself (mirroring DialogDriver/TeachingPopoverDriver's "one composite driver" shape from Wave 2), rather than minting a driver class per sub-component — none of the three are independently addressable/interactive the way a repeated list item (AvatarGroupItem, ListItem) is.

| Driver | Fluent component | Notes | | ----------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | AvatarDriver | Avatar | role="img" aria-label="{name}"; getName() reads that aria-label (present regardless of whether a badge is also set). getInitials() reads the fui-Avatar__initials part, undefined when an image renders instead. getPresenceBadge() returns a nested PresenceBadgeDriver for the fui-Avatar__badge part. | | AvatarGroupDriver | AvatarGroup | A ListComponentDriver over AvatarGroupItem wrappers (NOT the nested .fui-Avatar elements directly — each is the sole avatar under its own wrapper, so :nth-of-type addressing on the avatar itself would miscount; see AvatarGroupItemDriver). Overflow (AvatarGroupPopover, the +N trigger) is out of scope — only inline items are enumerated. | | AvatarGroupItemDriver | AvatarGroupItem | Wrapper with no state of its own; getAvatar() returns the nested AvatarDriver. | | BadgeDriver | Badge | Plain <div class="fui-Badge">; all state is its own text content (inherited getText()) — appearance/color/shape/size have no un-hashed DOM reflection. | | CounterBadgeDriver | CounterBadge | Extends BadgeDriver wholesale (identical DOM shape plus a marker class). getDisplayedCount() reads the already-overflowCount-clamped text (e.g. count={150} with the default overflowCount={99} renders "99+"). | | PresenceBadgeDriver | PresenceBadge | role="img" aria-label="{status}" — for a plain status the label IS the status string; getStatusLabel() reads it raw (the outOfOffice composite label text is unverified — see Known gaps). | | CardDriver | Card (+ CardHeader/CardFooter/CardPreview) | Folds header/footer/preview reads directly (see the wave-level rule above). A selectable card (selected/onSelectionChange supplied) renders a REAL native <input type="checkbox" class="fui-Card__checkbox">, driven directly via Interactor.isChecked/click. isDisabled() reads the root's aria-disabled (present regardless of selectable). | | PersonaDriver | Persona | getPrimaryText/getSecondaryText/getTertiaryText read their own fui-Persona__* structural classes (the whole-root getText() would double-count the avatar's own initials). getAvatar()/getPresenceBadge() reach the avatar or (in presenceOnly mode) the standalone presence slot. | | ListDriver | List | A ListComponentDriver over .fui-ListItem children — homogeneous siblings regardless of root tag (<ul>/<ol>/<div>) or role (role="list" plain, role="listbox" when selectionMode is set). getSelectedValues() enumerates selected items' values. | | ListItemDriver | ListItem | Its value prop reflects directly onto a plain value attribute (a rare un-hashed prop reflection in this package). isSelected() reads aria-selected, present only when the parent's selectionMode is set. | | SkeletonDriver | Skeleton (+ SkeletonItem) | role="progressbar" aria-busy="true"; individual items carry zero distinguishable state, so this driver exposes only getItemCount() rather than a per-item class (same "no interactive unit" call Wave 4 makes for BreadcrumbDivider). | | SpinnerDriver | Spinner | role="progressbar"; getLabel() reads the fui-Spinner__label part, undefined when rendered without one. | | ProgressBarDriver | ProgressBar | role="progressbar" with aria-valuemin/aria-valuemax/aria-valuenow; aria-valuenow is entirely ABSENT (not "0") in the indeterminate (no value prop) state — isIndeterminate() reads that absence directly. | | InfoLabelDriver | InfoLabel | The scene locator lands on the inner <label> (InfoLabel's PRIMARY slot), not the wrapping <span> — extends LabelDriver wholesale. getInfoButton() reaches the sibling InfoButton via an ancestor :has() re-root (same idiom as SearchBoxDriver's wrapper). | | InfoButtonDriver | InfoButton | Unlike every other Popover-backed overlay in this package, InfoButton's inline prop defaults to true — its popover surface renders as a plain DOM SIBLING (adjacent-sibling-combinator addressed), no portal re-root needed. Targets only that default case; inline={false} (portal) is out of scope. | | MessageBarDriver | MessageBar (+ MessageBarBody/Title/Actions) | getTitle() reads fui-MessageBarTitle exactly; getBodyText() reads the WHOLE fui-MessageBarBody, which includes the title's text too since Title nests INSIDE Body (known gap, same class as CompoundButtonDriver's). No built-in dismiss — declare the consumer-supplied action as its own scene part, same as ToastDriver. | | AlertDriver | Alert | Deprecated by Fluent itself (AlertProps's own TSDoc: "use the Toast or MessageBar component") and importable ONLY from @fluentui/react-components/unstable — NOT the stable package surface. getText() includes an action button's text when present (no isolated message read; known gap). |

Wave 6 — complex/composite (the highest-engineering-cost wave, saved for last by design): Table/DataGrid — the single largest driver family in this catalog, split the same way component-driver-mui-v9's Table family is (a top-level driver over rows, rows over cells, plus a distinct header-row driver) — Tree, and Carousel.

| Driver | Fluent component | Notes | | -------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TableDriver | Table | A ListComponentDriver over data (body) rows (.fui-TableBody .fui-TableRow), templated off component-driver-mui-v9's TableDriver. Header row is a separate TableHeaderRowDriver rather than folded in — a real DOM difference from MUI (Fluent's TableCell/TableHeaderCell are two structurally distinct components, <td>/<th>, each with its own structural class), not a stylistic choice. sortByColumn/getSortDirection are generic/best-effort (read aria-sort, click the header cell) since plain Table ships no built-in sort orchestration. | | TableRowDriver | TableRow (body) | A ListComponentDriver over .fui-TableCell children. | | TableHeaderRowDriver | TableRow (header) | A ListComponentDriver over .fui-TableHeaderCell children; adds best-effort getSortDirection/sortByColumn. | | TableRowDriverBase | shared base | ListComponentDriver-based cell iteration (getCellCount/getCell/getCellTexts) shared by both row kinds above. | | TableCellDriver | TableCell | Plain native <td>, no role; relies on inherited getText(). getActionButtons()/isActionsVisible() read a nested TableCellActions (shared logic with DataGridCellDriver) — see Wave 6 scope decisions. | | TableHeaderCellDriver | TableHeaderCell | Plain native <th>; isSortable()/getSortDirection() read aria-sort (absent entirely, not "none", when not sortable). | | DataGridDriver | DataGrid | A ListComponentDriver over data rows (.fui-DataGridBody [role="row"]) with built-in sort, row selection (single/multiselect), and column resize — the richer columns/items-driven sibling of Table. Columns are addressed by zero-based index, not field — columnId has zero DOM reflection (verified: getNativeElementProps's per-tag attribute allowlist strips it from every rendered slot). | | DataGridRowDriver | DataGridRow (data) | A childListHelper-based row over .fui-DataGridCell children (not ListComponentDriverDataGridRow conditionally renders a leading DataGridSelectionCell only when selectionMode is set, which breaks :nth-of-type addressing). Implements IToggleDriver; setSelected clicks the row's real native checkbox/radio. | | DataGridHeaderRowDriver | DataGridRow (header) | Same childListHelper shape over [role="columnheader"] children; adds sort, "select all" (multiselect only), and per-column resize delegation.