| name | uui-components |
| description | Helps create and modify UUI (EPAM Unified UI) components following established patterns. Use when creating new components, modifying existing components, working with withMods, component props, styling, or component architecture in the UUI library. |
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 the uui/ 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 implementation
ComponentName.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';
interface IconButtonMods {
color?: 'info' | 'success' | 'error' | 'primary' | 'accent' | 'critical' | 'warning' | 'secondary' | 'neutral' | 'white';
size?: '18' | '24' | '30' | '36';
}
export interface IconButtonModsOverride {}
export type IconButtonCoreProps = Omit<uuiComponents.IconButtonProps, 'size'>;
export interface IconButtonProps extends IconButtonCoreProps, Overwrite<IconButtonMods, IconButtonModsOverride> {}
function applyIconButtonMods(props: IconButtonProps) {
return [
'uui-icon_button',
`uui-color-${props.color || 'neutral'}`,
css.root,
];
}
export const IconButton = withMods<uuiComponents.IconButtonProps, IconButtonProps>(
uuiComponents.IconButton,
applyIconButtonMods,
(props) => {
return {
dropdownIcon: props.dropdownIcon || settings.iconButton.icons.dropdownIcon,
};
},
);
Key points:
withMods<CoreProps, Props>(coreComponent, applyMods, propsTransformer?)
applyMods returns 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';
export interface ITextPlaceholderProps extends IHasRawProps<React.HTMLAttributes<HTMLDivElement>>, IHasCX {
wordsCount?: number;
isNotAnimated?: boolean;
}
export type TextPlaceholderProps = PropsWithChildren<ITextPlaceholderProps>;
export const TextPlaceholder: React.FunctionComponent<TextPlaceholderProps> = (props) => {
return (
<div className={cx(css.root, 'uui-text-placeholder')} {...props.rawProps}>
{/* Component JSX */}
</div>
);
};
Key points:
- Implement directly without
withMods if not themeable
- Use
IHasCX and IHasRawProps from @epam/uui-core for standard props
- Use
cx (classnames) for conditional classes
- Use SCSS modules for styling
Styling Patterns
SCSS Modules
Components use SCSS modules (.module.scss):
.root {
display: inline-flex;
align-items: center;
}
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,
'uui-button',
`uui-fill-${mods.fill || 'solid'}`,
`uui-color-${mods.color || 'primary'}`,
`uui-size-${mods.size || settings.button.sizes.default}`,
];
}
Patterns:
- Use
css.root for 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:
export * from './Button';
export * from './IconButton';
export * from './LinkButton';
Root Index
Main uui/components/index.ts re-exports all categories:
export * from './buttons';
export * from './inputs';
export * from './typography';
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';
<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';
dropdownIcon: props.dropdownIcon || settings.iconButton.icons.dropdownIcon,
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';
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