Skip to content

Latest commit

 

History

History
137 lines (108 loc) · 4.6 KB

File metadata and controls

137 lines (108 loc) · 4.6 KB

V9 Component Patterns

File Structure

Every v9 component package follows this exact layout:

packages/react-components/react-<name>/library/src/
├── components/<Name>/
│   ├── <Name>.tsx                        # ForwardRefComponent
│   ├── <Name>.types.ts                   # Props, State, Slots types
│   ├── <Name>.test.tsx                   # Unit tests (adjacent)
│   ├── use<Name>.ts or .tsx              # State management hook
│   ├── use<Name>Styles.styles.ts         # Griffel styling
│   ├── render<Name>.tsx                  # JSX rendering
│   └── index.ts                          # Component barrel export
├── contexts/                             # Optional: context definitions
├── utils/                                # Optional: shared utilities
├── testing/
│   └── isConformant.ts                   # Conformance tests
├── stories/src/<Name>/
│   ├── <Name>Accessibility.md            # Optional: concise accessibility guidance
│   └── <Name>AccessibilitySpec.mdx       # Optional: full component accessibility spec
├── <Name>.ts                             # Root barrel per component
└── index.ts                              # Package export

Hook-Based Architecture

Components use three core hooks:

  1. use<Name>(props, ref) — Processes props and slots into normalized state. Use .ts if pure logic, .tsx if the hook body contains JSX.

  2. use<Name>Styles(state) — Creates Griffel CSS-in-JS styling using design tokens. Always ends in .styles.ts.

  3. render<Name>(state) — Pure JSX rendering from state. Always .tsx.

Where to Fix Bugs

Bug type Fix location
State / behavior use<Name>.ts
Styling use<Name>Styles.styles.ts
Rendering / JSX render<Name>.tsx
Types / props <Name>.types.ts

Slot System

All v9 components use slots for extensibility:

// Types
type ButtonSlots = {
  root: Slot<'button'>;
  icon?: Slot<'span'>;
};

// Hook — create slots
const state: ButtonState = {
  root: slot.always(props.root, { elementType: 'button' }),
  icon: slot.optional(props.icon, { elementType: 'span' }),
};

// Render — use assertSlots for type safety
export const renderButton_unstable = (state: ButtonState) => {
  assertSlots<ButtonSlots>(state);
  return (
    <state.root>
      {state.icon && <state.icon />}
      {state.root.children}
    </state.root>
  );
};

Griffel Styling

Use makeStyles with design tokens — never hardcode values:

import { makeStyles } from '@griffel/react';
import { tokens } from '@fluentui/react-theme';

export const useButtonStyles = makeStyles({
  root: {
    color: tokens.colorNeutralForeground1,
    backgroundColor: tokens.colorNeutralBackground1,
    padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalM}`,
    ':hover': {
      backgroundColor: tokens.colorNeutralBackground1Hover,
    },
  },
});

Always use mergeClasses() and preserve user className as the last argument:

state.root.className = mergeClasses(
  classes.root,
  props.size === 'small' && classes.small,
  state.root.className, // Always last
);

TypeScript Patterns

// Component.types.ts
export type ComponentProps = ComponentPropsWithRef<'div'> & {
  appearance?: 'primary' | 'secondary';
  size?: 'small' | 'medium' | 'large';
};

export type ComponentState = Required<Pick<ComponentProps, 'appearance' | 'size'>> & {
  components: ComponentSlots;
  root: SlotProps<'div'>;
};

// Main component — always ForwardRefComponent, never React.FC
export const Component: ForwardRefComponent<ComponentProps> = React.forwardRef((props, ref) => {
  const state = useComponent_unstable(props, ref);
  useComponentStyles_unstable(state);
  return renderComponent_unstable(state);
});

Accessibility patterns

Refer to <Name>Accessibility.md or <Name>BestPractices.md for high-importance accessibility requirements.

Check <Name>AccessibilitySpec.mdx for the full description of accessibility behaviors, including keyboard interaction, assistive tech behaviors, high contrast and reduced motion styles, content restrictions, and guidance on extending the control without breaking accessibility.

Most packages only have one <Name>AccessibilitySpec.mdx covering all components in the package -- for example, MenuAccessibilitySpec.mdx covers Menu, MenuList, MenuItem, MenuItemCheckbox, MenuItemLink, MenuItemRadio, MenuItemSwitch, etc.