Skip to Content
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.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).
  • Optionally move styles (HvComp.styles.ts), types (types.ts), or utils (utils.ts) into separate files when they grow too complex or are reused across components.

Styles

  • Use the createClasses utility to define component classes.
  • Leverage the design theme variables (e.g., colors, spacing), avoiding hard-coded values when possible.
  • 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 (e.g. classes.containerOpen).

Documentation

  • Create a corresponding comp.mdx file under the apps/docs/src directory.
  • Leverage the Playground component for the most common & relevant properties
  • Include only meaningful and distinct stories, avoiding redundant 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"; import { theme } from "@hitachivantara/uikit-styles"; export const { staticClasses, useClasses } = createClasses("HvComp", { /** Applied to the root element */ root: { padding: theme.spacing("xs"), // 👈 leverage the `theme.` object 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 without `Hv` prefix) 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> { selected?: boolean; // 👈 name boolean props so its obvious they're boolean /** Disables the component visually and its controls */ disabled?: boolean; // 👆 add JSDoc to the props, especially if they're complex children?: React.ReactNode; buttonContent?: React.ReactNode; // 👈 use `ReactNode` for props that are rendered as-is onClick?: HvButtonProps["onClick"]; // 👈 re-use types when possible onButtonClick?: HvButtonProps["onClick"]; // 👈 call handlers `on[<Element>]<Action>` id?: string; // 👈 ❌ don't add redundant types (included in the parent interface) } // 👇 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, classes: classesProp, // 👈 fix collisions by renaming to `<x>Prop` className, selected, // 👈 make booleans be opt-in (default is false/undefined) disabled, // 👈 don't set defaults (ie `false`) unnecessarily buttonContent, onClick, onButtonClick, ...others // 👈 call remaining props `others` } = useDefaultProps("HvComp", props); // 👈 use the useDefaultProps utility const { classes, css, cx } = useClasses(classesProp); // reserve `useMemo` for expensive 👇 computations const hasInput = useMemo(() => expensiveSearch(children), [children]); // rename handlers 👇 `handle<Action>` and 👇 type them accordingly, or ideally inline them const handleButtonClick: HvButtonProps["onClick"] = (evt) => { evt.preventDefault(); 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 /> </div> ); return ( <div ref={ref} // 👈 always forwarding the ref // 👇 merge class names with `cx` provided by `useClasses` className={cx( css(/* internal styles */), // 👈 pass internal styles first, using `css` classes.root, // 👈 ensure `classes.root` is on the root element { // conditional classes 👇 are based on their *condition* [classes.selected]: selected, [classes.disabled]: disabled, }, className, // 👈 pass user-defined `className` last )} onClick={disabled ? undefined : onClick} {...others} // 👈 forward others according to the types > <HvButton className={classes.button} onClick={handleButtonClick}> {buttonContent} </HvButton> {selected && renderContent} {children} </div> ); });
Last updated on