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
).
- Always apply
Documentation
- Create a corresponding
comp.mdx
file under theapps/docs/src
directory. - Leverage the
Playground
component for the most common & relevant properties - Include only meaningful and distinct stories, avoiding redundant examples.
Other Conventions
- Write unit tests using
@testing-library/react
. - Use semantic HTML elements whenever possible for better accessibility and performance.
- Ensure components meet WCAGβ requirements and follow ARIA guidelinesβ.
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(), // π AVOID inline classes, but pass them styles first when necessary
classes.root, // π ensure `classes.root` is on the root element
{
[classes.selected]: selected, // π names are based on their *condition*
[classes.disabled]: disabled, // π higher priority classes come after
},
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