fluentui-react-v9-custom-components
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBuilding Custom Components with FluentUI v9 Base Hooks
基于FluentUI v9基础Hooks构建自定义组件
Base state hooks + render functions let you reuse Fluent's ARIA behavior, keyboard handling, and slot structure with completely custom styling. Imports come from individual component packages (e.g. ), not from .
@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.
基础状态hooks + 渲染函数允许你复用Fluent的ARIA行为、键盘处理能力和插槽结构,同时实现完全自定义的样式。导入路径来自单独的组件包(例如 ),而非 。
@fluentui/react-button@fluentui/react-components你需要自行负责: 自定义样式、可见的焦点指示器、足够的色彩对比度,以及所有视觉层面的可访问性。基础Hooks仅提供ARIA结构,不处理视觉可访问性。
The Pattern
开发模式
tsx
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);
},
);tsx
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);
},
);Available Hooks
可用Hooks
See for the full inventory with import paths, prop/state types, and notes.
references/available-hooks.mdQuick reference:
| Component | Hook | Render function | Package |
|---|---|---|---|
| Button | | | |
| ToggleButton | | | |
| TabList | | | |
| Tab | | | |
| Divider | | | |
| Accordion | | | |
| AccordionPanel | | | |
| Toolbar | | | |
| ToolbarButton | | | |
| Popover | | | |
| Persona | | | |
| Card | | | |
完整的清单(含导入路径、属性/状态类型和说明)请查看 。
references/available-hooks.md快速参考:
| Component | Hook | Render function | Package |
|---|---|---|---|
| Button | | | |
| ToggleButton | | | |
| TabList | | | |
| Tab | | | |
| Divider | | | |
| Accordion | | | |
| AccordionPanel | | | |
| Toolbar | | | |
| ToolbarButton | | | |
| Popover | | | |
| Persona | | | |
| Card | | | |
Compound Components (TabList, Accordion)
复合组件(TabList、Accordion)
Compound components require context values passed to the render function. Use and inject any design props needed by child components before calling it:
use{Component}ContextValues_unstabletsx
import {
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);
},
);复合组件需要向渲染函数传递上下文值。调用渲染函数前,使用并注入子组件所需的任何设计属性:
use{Component}ContextValues_unstabletsx
import {
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);
},
);TypeScript tips
TypeScript提示
- Use for the component's props type
{Component}BaseProps - Cast state to when passing to render:
{Component}StaterenderButton_unstable(state as ButtonState) - Child components (e.g. ) read design values from context via
CustomTabuseTabListContext_unstable
- 使用作为组件的属性类型
{Component}BaseProps - 传递给渲染函数时将state强转为类型:
{Component}StaterenderButton_unstable(state as ButtonState) - 子组件(例如)可通过
CustomTab从上下文中读取设计值useTabListContext_unstable