UUI Components Skill
Package Structure
@epam/uui-components— Legacy package with logic-only components (no visual styles). Contains older components.@epam/uui— Package for ready-to-use, themeable components. All new components must be created here in theuui/folder.
When creating new components, add them to uui/components/, not to uui-components/.
File Path Conventions
Components are organized by category in uui/components/:
| Category | Path | Examples |
|---|---|---|
| buttons | uui/components/buttons/ |
Button, IconButton, LinkButton |
| inputs | uui/components/inputs/ |
Checkbox, TextInput, NumericInput, Switch |
| typography | uui/components/typography/ |
Text, TextPlaceholder, RichTextView |
| overlays | uui/components/overlays/ |
Modal, Tooltip, DropdownMenu |
| layout | uui/components/layout/ |
FlexRow, FlexCell, Blocker |
| pickers | uui/components/pickers/ |
PickerInput, PickerModal |
| datePickers | uui/components/datePickers/ |
DatePicker, RangeDatePicker |
| dnd | uui/components/dnd/ |
Drag and drop |
| widgets | uui/components/widgets/ |
Tag, Badge, Spinner |
| tables | uui/components/tables/ |
DataTable |
| filters | uui/components/filters/ |
FiltersPanel |
| navigation | uui/components/navigation/ |
MainMenu, TabButton |
| forms | uui/components/forms/ |
Form components |
| fileUpload | uui/components/fileUpload/ |
File upload |
| errors | uui/components/errors/ |
Error handling |
Each component typically has:
ComponentName.tsx— Component implementationComponentName.module.scss— Styles (SCSS modules)__tests__/ComponentName.test.tsx— Unit tests
Component Creation Workflows
Workflow 1: Wrapping Existing uui-components Component
When wrapping a logic-only component from @epam/uui-components:
Example: IconButton
import * as uuiComponents from '@epam/uui-components';
import { withMods, Overwrite } from '@epam/uui-core';
import { settings } from '../../settings';
import css from './IconButton.module.scss';
// 1. Define mods type for themeable properties
interface IconButtonMods {
color?: 'info' | 'success' | 'error' | 'primary' | 'accent' | 'critical' | 'warning' | 'secondary' | 'neutral' | 'white';
size?: '18' | '24' | '30' | '36';
}
export interface IconButtonModsOverride {}
// 2. Create props interface extending core props
export type IconButtonCoreProps = Omit<uuiComponents.IconButtonProps, 'size'>;
export interface IconButtonProps extends IconButtonCoreProps, Overwrite<IconButtonMods, IconButtonModsOverride> {}
// 3. Create applyMods function that returns CSS classes
function applyIconButtonMods(props: IconButtonProps) {
return [
'uui-icon_button',
`uui-color-${props.color || 'neutral'}`,
css.root, // SCSS module class
];
}
// 4. Wrap with withMods HOC
export const IconButton = withMods<uuiComponents.IconButtonProps, IconButtonProps>(
uuiComponents.IconButton, // Core component
applyIconButtonMods, // Mods function
(props) => {
// Optional: transform props or add defaults
return {
dropdownIcon: props.dropdownIcon || settings.iconButton.icons.dropdownIcon,
};
},
);
Key points:
withMods<CoreProps, Props>(coreComponent, applyMods, propsTransformer?)applyModsreturns array of CSS class strings- Third parameter is optional function to transform/inject props
- Use
Overwrite<Mods, ModsOverride>pattern for extensibility
Workflow 2: Brand-New Component (No uui-components Counterpart)
When creating a component from scratch:
Example: TextPlaceholder
import * as React from 'react';
import cx from 'classnames';
import { IHasCX, IHasRawProps } from '@epam/uui-core';
import css from './TextPlaceholder.module.scss';
import { PropsWithChildren } from 'react';
// 1. Define props interface
export interface ITextPlaceholderProps extends IHasRawProps<React.HTMLAttributes<HTMLDivElement>>, IHasCX {
wordsCount?: number;
isNotAnimated?: boolean;
}
export type TextPlaceholderProps = PropsWithChildren<ITextPlaceholderProps>;
// 2. Implement component directly
export const TextPlaceholder: React.FunctionComponent<TextPlaceholderProps> = (props) => {
// Component logic here
return (
<div className={cx(css.root, 'uui-text-placeholder')} {...props.rawProps}>
{/* Component JSX */}
</div>
);
};
Key points:
- Implement directly without
withModsif not themeable - Use
IHasCXandIHasRawPropsfrom@epam/uui-corefor standard props - Use
cx(classnames) for conditional classes - Use SCSS modules for styling
Styling Patterns
SCSS Modules
Components use SCSS modules (.module.scss):
// IconButton.module.scss
.root {
display: inline-flex;
align-items: center;
// Styles here
}
Import in component:
import css from './IconButton.module.scss';
Mods to CSS Classes
The applyMods function maps props to CSS classes:
function applyButtonMods(mods: ButtonProps) {
return [
css.root, // SCSS module class
'uui-button', // Global UUI class
`uui-fill-${mods.fill || 'solid'}`, // Dynamic class from prop
`uui-color-${mods.color || 'primary'}`,
`uui-size-${mods.size || settings.button.sizes.default}`,
];
}
Patterns:
- Use
css.rootfor component-specific styles - Use
uui-*prefixed classes for themeable utilities - Use
settings.componentName.*for default values - Return array of class strings (will be joined by
withMods)
Export Pattern
Component-Level Index
Each category folder has an index.ts that re-exports components:
// uui/components/buttons/index.ts
export * from './Button';
export * from './IconButton';
export * from './LinkButton';
Root Index
Main uui/components/index.ts re-exports all categories:
// uui/components/index.ts
export * from './buttons';
export * from './inputs';
export * from './typography';
// ... etc
When adding a new component:
- Export from component file:
export const MyComponent = ... - Add to category
index.ts:export * from './MyComponent'; - If new category, add to root
index.ts:export * from './newCategory';
Common Patterns
cx Prop (IHasCX)
Most components extend IHasCX, which adds a cx prop for consumer styling. Always merge props.cx into the root element's classNames so consumers can pass custom classes:
import cx from 'classnames';
// With withMods — withMods merges cx automatically
// Manual mods:
<div className={cx(applyTagMods(props), props.cx)}>
<Clickable cx={cx(applyTagMods(props), props.cx)} {...props} />
Using Settings
The settings object (uui/settings.tsx) provides theme-specific defaults: icons, sizes, placeholders. Structure: settings.<component>.<category>.<key>.
Access:
import { settings } from '../../settings';
// Icons
dropdownIcon: props.dropdownIcon || settings.iconButton.icons.dropdownIcon,
// Sizes
size: props.size || settings.button.sizes.default,
Structure: Each component has nested objects (e.g. icons, sizes). Themes override via PartialSettings — see skin packages (loveship, epam-electric, epam-promo) for how settings are merged. To add new component settings, add a section in uui/settings.tsx following existing patterns (e.g. accordionSettings, buttonSettings).
Props Transformation
The third parameter of withMods can transform props:
export const TextInput = withMods<CoreTextInputProps, TextInputProps>(
uuiTextInput,
applyTextInputMods,
() => ({
acceptIcon: settings.textInput.icons.acceptIcon,
cancelIcon: settings.textInput.icons.clearIcon,
dropdownIcon: settings.textInput.icons.dropdownIcon,
}),
);
Conditional Mods
Apply mods conditionally in applyMods:
function applyCheckboxMods(mods: CheckboxMods) {
return [
css.root,
`uui-size-${mods.size || settings.checkbox.sizes.default}`,
'uui-control-mode-' + (mods.mode || 'form'),
'uui-color-primary',
];
}
Additional Patterns
Using forwardRef
Many components expose refs via React.forwardRef. Use when consumers need direct DOM access (e.g. focus management, measurements):
import React from 'react';
import { withMods } from '@epam/uui-core';
import * as uuiComponents from '@epam/uui-components';
// withMods works with forwardRef components automatically.
// If the core component uses forwardRef, the wrapped component inherits it.
export const Switch = withMods<uuiComponents.SwitchProps, SwitchProps>(uuiComponents.Switch, applySwitchMods);
For brand-new components without withMods:
export const Tag = React.forwardRef<HTMLElement, TagProps>((props, ref) => {
return (
<div ref={ ref } className={ cx(applyTagMods(props), props.cx) }>
{/* ... */}
</div>
);
});
Manual Mods (without withMods)
Some components apply mods manually instead of using withMods. This is common when wrapping primitives like Clickable with forwardRef:
export const Tag = React.forwardRef<HTMLElement, TagProps>((props, ref) => {
return (
<Clickable
cx={ cx(applyTagMods(props), props.cx) }
ref={ ref }
{ ...props }
/>
);
});
function applyTagMods(props: TagProps) {
return [
css.root,
`uui-size-${props.size || '36'}`,
`uui-color-${props.color || 'neutral'}`,
];
}
See uui/components/widgets/Tag.tsx, uui/components/layout/FlexRow.tsx for examples.
Data-Driven Components (DataTable, PickerInput)
Components like DataTable, PickerInput, PickerModal, and FiltersPanel use DataSource from @epam/uui-core. See .cursor/skills/data-sources/SKILL.md for DataSource usage.
Deprecation Warnings
Use devLogger to warn about deprecated prop values:
import { devLogger } from '@epam/uui-core';
devLogger.warnAboutDeprecatedPropValue<IconButtonProps, 'color'>({
component: 'IconButton',
propName: 'color',
propValue: props.color,
condition: () => ['info', 'success'].includes(props.color),
message: "'info' and 'success' colors are deprecated. Use 'primary' instead.",
});
References
- Example wrapping component:
uui/components/buttons/IconButton.tsx - Example brand-new component:
uui/components/typography/TextPlaceholder.tsx - Example with complex mods:
uui/components/inputs/TextInput.tsx - Example with props transformation:
uui/components/buttons/Button.tsx - Example with forwardRef + manual mods:
uui/components/widgets/Tag.tsx