DocsGuidelines

Component Guidelines

This section outlines the standards for building consistent, accessible, and maintainable components within the UI Kit — covering structure, styling, testing, and documentation best practices.

Structure

Each component should follow the structure below:

HvComp/
├── index.ts            # Entry file with public exports
├── HvComp.tsx          # Component implementation and types
├── HvComp.stories.tsx  # Storybook stories and documentation
├── HvComp.styles.ts    # Styling utilities and class definitions
└── HvComp.test.tsx     # Unit tests
  • Components and their types must be exported in index.ts.
  • Use PascalCase for component names and prefix with Hv (e.g., HvButton, HvAccordion).
  • Boolean props should be opt-in — default value should be false.
  • Name event handler props using the on<Action> convention (e.g., onClick, onChange).

Styles

  • Use the createClasses utility to define component classes.
  • Class names generated by createClasses are exposed to users, so choose names thoughtfully:
    • Always apply classes.root to the root element.
    • When targeting non-root elements, make the purpose clear (e.g., classes.label, classes.iconContainer).
    • For conditional styles, use descriptive names that reflect the condition.
  • Avoid hard-coded styles. Always use the design theme (e.g., colors, spacing) for consistency.

Documentation

  • Create a corresponding HvComp.stories.tsx file using CSF (Component Story Format).
  • The primary example should be named Main, with controls configured appropriately.
  • Include only meaningful and distinct stories — avoid repetitive examples.

Other Conventions

Following these guidelines helps maintain a coherent developer experience across the library and ensures consistency, accessibility, and long-term maintainability.

Example

Here’s an example anatomy of a HvComp component:

// HvComp.styles.ts
import { createClasses } from "@hitachivantara/uikit-react-utils";
 
export const { staticClasses, useClasses } = createClasses("HvComp", {
  /** Applied to the root element */
  root: {
    // leverage the 👇 `theme.` object
    padding: theme.spacing("xs"),
    backgroundColor: theme.colors.backgroundColor,
  },
  /** Applied to the root element when selected */
  selected: {},
  /** Applied to the root element when disabled */
  disabled: {
    // 👇 leverage global `disabled` instead of adding a `buttonDisabled`
    "& $button": {
      cursor: "not-allowed",
    },
  },
  /** Applied to the button element */
  button: {},
});
// HvComp.tsx
import {
  useDefaultProps,
  type ExtractNames,
} from "@hitachivantara/uikit-react-utils";
 
import { HvBaseProps } from "../types/generic";
import { staticClasses, useClasses } from "./MyComp.styles";
 
// export `staticClasses` 👇 as `compClasses` (camelCase, no `Hv`)
export { staticClasses as compClasses };
 
// export the classes 👇 inferred from `useClasses`
export type HvCompClasses = ExtractNames<typeof useClasses>;
 
// extend the types to where `...others` 👇 is being passed
export interface HvCompProps extends HvBaseProps<HTMLDivElement> {
  // 👇 name boolean props so its obvious they're boolean
  selected?: boolean;
  // 👇 add JSDoc to the props, especially if they're complex
  /** Disables the component visually and its controls */
  disabled?: boolean;
  children?: ReactNode;
  // Use permissive 👇 `ReactNode` for props that are rendered as-is
  buttonContent?: ReactNode;
  // re-use types 👇 when possible
  onClick?: HvButtonProps["onClick"];
  // 👇 call handlers `on<Action>` or `on<Element><Action>` for sub-elements
  onButtonClick?: HvButtonProps["onClick"];
  // 👇 ❌ don't add redundant types (included in the parent interface)
  id?: string;
}
 
// 👇 Add a JSDoc block to the component explaining its purpose
/** HvComp does some amazing stuff */
export const HvComp = forwardRef<
  // no-indent
  React.ComponentRef<"div">,
  HvCompProps
>(function HvComp(props, ref) {
  const {
    children,
    // fix collisions 👇 by renaming to `<x>Prop`
    classes: classesProp,
    className,
    // 👇 make booleans be opt-in (default is false/undefined)
    selected,
    // 👇 don't  set defaults (ie `false`) unnecessarily
    disabled,
    buttonContent,
    onClick,
    onButtonClick,
    // 👇 call remaining props `others`
    ...others
    // use the 👇 useDefaultProps utility
  } = useDefaultProps("HvComp", props);
  // use the useClasses utility 👇
  const { classes, css, cx } = useClasses(classesProp);
 
  // internal state variables.
  const canSelect = !disabled && !selected;
  // reserve `useMemo` for expensive 👇 computations
  const hasElement = useMemo(() => expensiveSearch(children), [children]);
 
  // AVOID useEffect...
  useEffect(() => {
    // 👇 ❌ ...especially when dealing with event handlers
    onClick();
  }, [onClick]);
 
  // rename handlers 👇 (`handle<Action>`) and 👇 type them accordingly
  const handleButtonClick: HvButtonProps["onClick"] = (evt) => {
    evt.preventDefault();
    // use the ?. 👇 operator for optional event handlers & forward the event
    onButtonClick?.(evt);
  };
 
  // keep `render<Stuff>` as a `ReactNode` if possible
  // if you 👇 need `(props) => ReactNode`, it should likely be a separate component
  const renderContent = (
    <div>
      <span>{/* some content */}</span>
    </div>
  );
 
  return (
    <div
      // 👇 don't forget forwarding the ref
      ref={ref}
      // 👇 merge class names with `cx` provided by `useClasses`
      className={cx(
        // 👇 pass internal styles first, using `css`
        css(/* internal styles */),
        // 👇 ensure `classes.root` is on the root element
        classes.root,
        {
          // conditional classes 👇 are based on their *condition*
          [classes.selected]: selected,
          [classes.disabled]: disabled,
        },
        // 👇 pass user-defined `className` last
        className,
      )}
      onClick={disabled ? undefined : onClick}
      // 👇 forward others according to the types
      {...others}
    >
      <HvButton className={classes.button} onClick={handleButtonClick}>
        {buttonContent}
      </HvButton>
      {selected && renderContent}
      {children}
    </div>
  );
});