| name | crm-ui-kit-component |
| description | Create a new React component in the crm-react-ui-kit library following the project's conventions (forwardRef, CSS variables theme system, useThemeClassName hook, .props.ts/.themes.ts/.module.css/index.ts files). Use when adding a new component under packages/ui-kit/src/components, scaffolding component files, or asked to create a new UI kit component. |
Create a UI Kit Component
This skill scaffolds a new component inside packages/ui-kit/src/components/<ComponentName>/ following the conventions used by every other component in crm-react-ui-kit.
Monorepo Location
The repo is a Yarn-workspaces + Turbo monorepo. The library lives in the packages/ui-kit workspace (package name @kommo-crm/crm-react-ui-kit); the Storybook app lives in packages/storybook. Stories, unit tests, and e2e tests all stay co-located inside each component folder under packages/ui-kit/src/components/<Name>/.
- Every
src/... path in this skill is relative to packages/ui-kit/. Run scripts from that directory, or from the repo root via yarn workspace @kommo-crm/crm-react-ui-kit <script> (Turbo also fans build/lint/test out across packages).
- Source imports use the
@ui-kit/* alias (maps to packages/ui-kit/src/*) — e.g. @ui-kit/hooks/useThemeClassName. Do NOT use the old bare src/... import prefix.
Required File Structure
Every component MUST have this exact structure:
packages/ui-kit/src/components/<ComponentName>/
├── index.ts # public exports
├── <ComponentName>.tsx # forwardRef component
├── <ComponentName>.props.ts # Props interface + JSDoc
├── <ComponentName>.themes.ts # CSS-variable theme types & presets
└── <ComponentName>.module.css
Optional folders are added later via the dedicated skills:
__tests__/ — unit + e2e tests
__stories__/ — Storybook stories + MDX
__image_snapshots__/ — Playwright snapshots (auto-generated)
hooks/ — internal hooks if the component is complex (e.g. Button)
Conventions (MUST follow)
- The component is exported from
<Name>.tsx using forwardRef. Always set displayName.
- CSS variable names use the prefix
--crm-ui-kit-<component-name>-*.
- Theming is applied via the
useThemeClassName<ThemeType>(theme) hook from @ui-kit/hooks/useThemeClassName.
- Class names are composed with
classnames (import cx from 'classnames';) and CSS Modules (import s from './<Name>.module.css';).
- Boolean props are prefixed with
is* (e.g. isDisabled, isLoading, isInvalid, isCentered, isEllipsis).
- The
theme prop is required and typed strictly as the component's theme type.
- Default values for optional props are destructured inline (
className = '', isDisabled = false).
- Rest props (
...rest) are spread onto the root native element so consumers can pass HTML attributes / data-attrs.
- Public
index.ts re-exports: the component, its theme types and theme presets, and props type.
- Component name in PascalCase, file name matches component name.
- The root CSS Module class name MUST match the lowercased component name (
s.spinner for Spinner, s.button for Button, s.text for Text). Do NOT use a generic .root.
Templates
<Name>.tsx
import React, { forwardRef } from 'react';
import cx from 'classnames';
import { useThemeClassName } from '@ui-kit/hooks/useThemeClassName';
import { type <Name>Props } from './<Name>.props';
import { type <Name>Theme } from './<Name>.themes';
import s from './<Name>.module.css';
type El = HTML<Tag>Element;
export const <Name> = forwardRef<El, <Name>Props>((props, ref) => {
const {
className = '',
theme,
children,
...rest
} = props;
const themeClassName = useThemeClassName<<Name>Theme>(theme);
return (
<div
ref={ref}
className={cx(s.<name>, themeClassName, className)}
{...rest}
>
{children}
</div>
);
});
<Name>.displayName = '<Name>';
If the component renders a <button> / <input> / etc., type the element accordingly and also type the props based on React.DetailedHTMLProps<React.<Tag>HTMLAttributes<...>, ...>.
<Name>.props.ts
import React from 'react';
import { type <Name>Theme } from './<Name>.themes';
type Native<Tag>Attributes = React.DetailedHTMLProps<
React.<Tag>HTMLAttributes<HTML<Tag>Element>,
HTML<Tag>Element
>;
export interface <Name>Props extends Native<Tag>Attributes {
theme: <Name>Theme;
isExample?: boolean;
}
JSDoc each prop. Storybook reads it via react-docgen-typescript so descriptions show up in the docs.
<Name>.themes.ts
Use a string-literal union as the theme key to enforce the design-token contract:
type <Name>ThemeKey =
| '--crm-ui-kit-<name>-color'
| '--crm-ui-kit-<name>-background-color'
| '--crm-ui-kit-<name>-border-radius';
export type <Name>Theme = {
[K in <Name>ThemeKey]: string;
};
const <Name>BaseThemeValues = {
'--crm-ui-kit-<name>-border-radius':
'var(--crm-ui-kit-border-radius-default)',
};
export const <Name>PrimaryTheme: <Name>Theme = {
...<Name>BaseThemeValues,
'--crm-ui-kit-<name>-color': 'var(--crm-ui-kit-palette-text-primary)',
'--crm-ui-kit-<name>-background-color':
'var(--crm-ui-kit-palette-background-primary)',
};
Rules:
- All values reference palette CSS variables defined in
packages/ui-kit/src/stylesheets/theme.css whenever possible — never hardcode hex colors in default themes.
- Base values shared across presets MUST go through a
<Name>BaseThemeValues constant (or <Name>BaseSizesTheme: Omit<<Name>Theme, '<keys-overriden-per-preset>'> if they cover most of the theme). Do NOT type the base as Partial<<Name>Theme> — it forces an as <Name>Theme cast on every preset.
- Optional theme keys go into a separate
<Name>ThemeOptionalKey union and become optional on the theme type (see Button.themes.ts for a focus-visible example).
- Export at least one preset (
<Name>PrimaryTheme). Add more presets following the naming <Name><Variant>Theme.
Commonly used palette variables (always read packages/ui-kit/src/stylesheets/theme.css before creating a new component)
Text:
--crm-ui-kit-palette-text-primary (dark text, default)
--crm-ui-kit-palette-text-secondary-light (muted gray)
--crm-ui-kit-palette-text-secondary-dark
Backgrounds:
--crm-ui-kit-palette-background-primary (white / dark card)
--crm-ui-kit-palette-background-default (light gray page bg)
--crm-ui-kit-palette-background-secondary-800 (subtle gray chip/tag)
Borders:
--crm-ui-kit-palette-border-default
--crm-ui-kit-palette-border-primary
--crm-ui-kit-palette-border-error
Accent:
--crm-ui-kit-palette-active-element-900 (blue, "primary" CTA)
--crm-ui-kit-color-error (red, danger/error)
--crm-ui-kit-color-white (pure white)
Shape:
--crm-ui-kit-border-radius-default (4px)
--crm-ui-kit-disabled-opacity (0.6)
<Name>.module.css
.<name> {
color: var(--crm-ui-kit-<name>-color);
background-color: var(--crm-ui-kit-<name>-background-color);
border-radius: var(--crm-ui-kit-<name>-border-radius);
}
Use only the CSS variables declared in <Name>.themes.ts for any themable property. Static, non-themable styles can be hardcoded.
index.ts
export { <Name> } from './<Name>';
export {
type <Name>Theme,
<Name>PrimaryTheme,
} from './<Name>.themes';
export { type <Name>Props } from './<Name>.props';
Wire the component into the package exports
After scaffolding, add the entry to packages/ui-kit/package.json so consumers can import it via @kommo-crm/crm-react-ui-kit/<Name> (build output paths are still ./dist/components/<Name>/..., unchanged by the monorepo move):
-
Add to exports:
"./<Name>": "./dist/components/<Name>/index.js",
"./assets/<Name>.css": "./dist/assets/<Name>.css",
-
Add to typesVersions["*"]:
"<Name>": ["./dist/components/<Name>/index.d.ts"]
-
Bump the version field (minor bump — adding a component is a new feature):
"version": "X.Y+1.0"
Keep alphabetical/visual grouping consistent with neighbouring entries.
Workflow
- [ ] Confirm the component name (PascalCase) and the root HTML tag
- [ ] Identify theme tokens (colors, sizes, spacing) and decide which are required vs optional
- [ ] Create the 5 files with templates above
- [ ] Replace ALL <Name>, <name>, <Tag>, <tag> placeholders
- [ ] Add entries to package.json (exports + typesVersions)
- [ ] Hand off to crm-ui-kit-unit-test, crm-ui-kit-e2e-test, crm-ui-kit-stories
When used as part of crm-ui-kit-full-component, do NOT run yarn lint after this phase — the orchestrator runs lint once in Phase 6 against the full file set (source + tests + stories + e2e) so prettier violations are resolved in a single pass.
Reference Components (FALLBACK ONLY)
The templates above are the source of truth — do NOT read these proactively. Open ONE of these files only if the user's request hits a specific edge case the templates don't cover:
| Edge case | File to read (one only) |
|---|
| Optional theme keys (e.g. focus-visible) | packages/ui-kit/src/components/Button/Button.themes.ts |
VisuallyHiddenInput form-control wrapping | packages/ui-kit/src/components/Switcher/Switcher.tsx |
Internal sub-component (e.g. <Name>.Label) | packages/ui-kit/src/components/Switcher/Switcher.tsx |
Ellipsis / line-clamp via maxRows | packages/ui-kit/src/components/Text/Text.tsx |
Never browse a component's directory in full. Read exactly the one file that the table maps to your edge case.
Anti-Patterns
- Do NOT hardcode colors / sizes inside
.module.css — use the CSS variables declared in the theme file.
- Do NOT export themes from
<Name>.tsx. Themes live in <Name>.themes.ts.
- Do NOT use default exports.
- Do NOT add comments explaining what the JSX does — props' JSDoc + reference patterns are enough.
- Do NOT forget
displayName — required for Storybook and React DevTools.
- Do NOT name the root CSS class
.root — match the lowercased component name.