Loading...
Loading...
Guide for building custom React components using Fluent UI v9 base state hooks and render functions. Use when asked to: create a component based on FluentUI headless hooks, build a custom component using Fluent UI base state hooks, create a component with custom styling that reuses Fluent UI accessibility behavior, implement a component using render{Component}_unstable and use{Component}Base_unstable, or consume @fluentui/react-button/@fluentui/react-tabs/etc. without Fluent 2 visual design.
npx skill4agent add dmytrokirpa/fluentui fluentui-react-v9-custom-components@fluentui/react-button@fluentui/react-componentsYour responsibility: custom styling, visible focus indicators, sufficient color contrast, and all visual accessibility. Base hooks provide ARIA structure but not visual a11y.
import * as React from 'react';
import { useButtonBase_unstable, renderButton_unstable } from '@fluentui/react-button';
import type { ButtonBaseProps, ButtonState } from '@fluentui/react-button';
type CustomButtonProps = ButtonBaseProps & {
variant?: 'primary' | 'secondary' | 'tertiary';
tone?: 'neutral' | 'success' | 'warning' | 'danger';
};
export const CustomButton = React.forwardRef<HTMLButtonElement, CustomButtonProps>(
({ variant = 'primary', tone = 'neutral', ...props }, ref) => {
// 1. Get state: ARIA, keyboard handling, slot structure
const state = useButtonBase_unstable(props, ref);
// 2. Mutate state: apply your classes/styles
state.root.className = ['btn', `btn--${variant}`, `btn--${tone}`].filter(Boolean).join(' ');
if (state.icon) {
state.icon.className = 'btn__icon';
}
// 3. Render using Fluent's render function
return renderButton_unstable(state as ButtonState);
},
);references/available-hooks.md| Component | Hook | Render function | Package |
|---|---|---|---|
| Button | | | |
| ToggleButton | | | |
| TabList | | | |
| Tab | | | |
| Divider | | | |
| Accordion | | | |
| AccordionPanel | | | |
| Toolbar | | | |
| ToolbarButton | | | |
| Popover | | | |
| Persona | | | |
| Card | | | |
use{Component}ContextValues_unstableimport {
useTabListBase_unstable,
useTabListContextValues_unstable,
renderTabList_unstable,
useTabListA11yBehavior_unstable,
} from '@fluentui/react-tabs';
import type { TabListBaseProps, TabListState } from '@fluentui/react-tabs';
type CustomTabListProps = TabListBaseProps & { appearance?: 'filled' | 'outline' };
export const CustomTabList = React.forwardRef<HTMLDivElement, CustomTabListProps>(
({ appearance = 'filled', ...props }, ref) => {
const state = useTabListBase_unstable(props, ref);
// Inject design props consumed by child Tab components via context
Object.assign(state, { appearance, size: 'medium', reserveSelectedTabSpace: true });
const contextValues = useTabListContextValues_unstable(state as TabListState);
// Apply custom classes
state.root.className = `tab-list tab-list--${appearance}`;
// Keyboard navigation: choose one approach
// Option A — Tabster (recommended for accessibility)
const focusProps = useTabListA11yBehavior_unstable({ vertical: state.vertical });
state.root = { ...state.root, ...focusProps };
// Option B — focusgroup proposal
// (state.root as any).focusgroup = `tablist ${state.vertical ? 'block' : 'inline'} no-memory wrap`;
return renderTabList_unstable(state as TabListState, contextValues);
},
);{Component}BaseProps{Component}StaterenderButton_unstable(state as ButtonState)CustomTabuseTabListContext_unstable