This document contains guidelines and patterns for developing components in the AIKit project.
- Code Style and Language Requirements
- Storybook Files Creation
- Testing Guidelines
- README Documentation
CRITICAL: All code documentation, comments, and JSDoc must be written in English.
This includes:
- JSDoc comments: All function, class, interface, and type documentation
- Inline comments: All explanatory comments within the code
- TODO comments: All task comments and notes
- Git commit messages: All commit descriptions and messages
- Code review comments: All discussions and feedback
❌ Incorrect (Russian):
/**
* Хук для управления состоянием компонента
* @param props - пропсы компонента
* @returns объект с состоянием
*/
function useMyHook(props: Props) {
// Обрабатываем клик
const handleClick = () => {
// Вызываем callback
props.onClick();
};
return {handleClick};
}✅ Correct (English):
/**
* Hook for managing component state
* @param props - component props
* @returns object with state
*/
function useMyHook(props: Props) {
// Handle click event
const handleClick = () => {
// Call callback
props.onClick();
};
return {handleClick};
}- International Collaboration: Enables developers worldwide to contribute
- Consistency: Matches industry standards and open-source best practices
- Maintainability: Future developers will understand the codebase
- Tooling: Better IDE support and AI-assisted development
- Documentation: Aligns with all user-facing documentation
Note: UI text strings and user-facing messages should use i18n/localization and can be in multiple languages, but the code itself must use English.
All components should have Storybook stories for documentation and testing purposes. Stories are located in the __stories__ directory within each component folder.
ComponentName/
├── ComponentName.tsx
├── ComponentName.scss
├── README.md
├── index.ts
└── __stories__/
├── Docs.mdx
└── ComponentName.stories.tsx
Location: ComponentName/__stories__/Docs.mdx
Standard Template:
import {Meta, Canvas, Controls, Markdown} from '@storybook/addon-docs';
import * as Stories from './ComponentName.stories';
import Readme from '../README.md?raw';
<Meta of={Stories} />
<Markdown>{Readme}</Markdown>
## Playground
<Canvas of={Stories.Playground} />
<Controls of={Stories.Playground} />Key Points:
- Always import
Meta,Canvas,Controls, andMarkdownfrom@storybook/addon-docs - Import all stories as
* as Storiesfrom the corresponding.stories.tsxfile - Import the component's README using
?rawsuffix - Use
<Meta of={Stories} />to link to the stories metadata - Use
<Markdown>{Readme}</Markdown>to render the README content - Include a Playground section with
<Canvas>and<Controls>components - The Playground story should always be the first/default story for interactive testing
Location: ComponentName/__stories__/ComponentName.stories.tsx
Standard Structure:
import {useState} from 'react';
import type {Meta, StoryObj} from '@storybook/react-webpack5';
import {ComponentName} from '..';
import {ContentWrapper} from '../../../demo/ContentWrapper';
import MDXDocs from './Docs.mdx';
export default {
title: 'category/ComponentName',
component: ComponentName,
parameters: {
docs: {
page: MDXDocs,
},
},
} as Meta;
type Story = StoryObj<typeof ComponentName>;
const defaultDecorators = [
(Story) => (
<ContentWrapper>
<Story />
</ContentWrapper>
),
] satisfies Story['decorators'];
export const Playground: Story = {
args: {
// Default props for interactive testing
},
decorators: defaultDecorators,
};
// Additional stories...Story Categories:
atoms/ComponentName- Atomic components (buttons, inputs, indicators)molecules/ComponentName- Molecule components (composed atoms)organisms/ComponentName- Organism components (complex UI sections)pages/ComponentName- Page-level componentstemplates/ComponentName- Template components
Story Types to Include:
-
Playground (Required) - Interactive story with controls
export const Playground: Story = { args: { // All configurable props }, decorators: defaultDecorators, };
-
Default State - Component in its default state
export const Default: Story = { args: { // Minimal required props }, decorators: defaultDecorators, };
-
With State - Stories demonstrating stateful behavior
export const WithValue: Story = { render: (args) => { const [value, setValue] = useState('initial value'); return <ComponentName {...args} value={value} onChange={setValue} />; }, decorators: defaultDecorators, };
-
Variants - Different visual or functional variants
export const LargeSize: Story = { args: { size: 'l', }, decorators: defaultDecorators, };
-
Edge Cases - Disabled, loading, error states
export const Disabled: Story = { args: { disabled: true, }, decorators: defaultDecorators, };
-
Complex Examples - Real-world usage scenarios
export const WithCustomContent: Story = { args: { children: <CustomComponent />, }, decorators: defaultDecorators, };
Decorator Guidelines:
- Use
ContentWrapperfromdemo/ContentWrapperto constrain component width when needed - Set explicit width only if the component has specific display requirements:
- Content wrapping/line breaks behavior
- Grid layout that needs specific dimensions
- Component designed for modals or popups
- Use
satisfies Story['decorators']for type safety - Create
defaultDecoratorsconstant for reusability
Best Practices:
- Always include a
Playgroundstory as the first exported story - Use descriptive story names that explain the use case
- Group related stories logically
- Include comments explaining complex story logic
- Use
renderfunction for stories requiring state or complex logic - Use
argsfor simple prop variations - Test all major props and their combinations
- Include accessibility considerations in stories
Tests should be created based on Storybook stories to ensure consistency between documentation and functionality. We use Playwright Component Testing for visual regression testing and interaction testing.
Location: ComponentName/__tests__/ directory with the following files:
helpersPlaywright.tsx- exports composed storiesComponentName.visual.spec.tsx- Playwright test file__snapshots__/- directory for visual regression snapshots
Helper File Template (helpersPlaywright.tsx):
import {composeStories} from '@storybook/react';
import * as DefaultComponentNameStories from '../__stories__/ComponentName.stories';
export const ComponentNameStories = composeStories(DefaultComponentNameStories);Test File Template (ComponentName.visual.spec.tsx):
import React from 'react';
import {expect, test} from '~playwright/core';
import {ComponentNameStories} from './helpersPlaywright';
test.describe('ComponentName', {tag: '@ComponentName'}, () => {
test('should render playground state', async ({mount, expectScreenshot}) => {
await mount(<ComponentNameStories.Playground />);
await expectScreenshot();
});
test('should render default state', async ({mount, expectScreenshot}) => {
await mount(<ComponentNameStories.Default />);
await expectScreenshot();
});
// More tests for each story
});Important: Always import expect from ~playwright/core, not as a fixture parameter.
All Playwright tests MUST be run using Docker commands to ensure consistency across different environments and to match the CI/CD pipeline behavior.
Available Commands:
-
Run all tests in Docker:
npm run playwright:docker
-
Run specific tests by tag:
npm run playwright:docker -- --grep "@ComponentName" -
Update visual snapshots in Docker:
npm run playwright:docker:update
-
Update snapshots for specific tests:
npm run playwright:docker:update -- --grep "@ComponentName"
Why Docker?
- Ensures pixel-perfect consistency in visual regression tests
- Matches the exact environment used in CI/CD
- Eliminates differences caused by fonts, rendering engines, and OS-specific behaviors
- Required for updating snapshots that will be committed to the repository
Note: Local Playwright commands (npm run playwright, npm run playwright:update) should NOT be used as they may produce different snapshots than CI/CD.
-
One Test Per Story
- Create a separate
test()for each exported story - Name it descriptively:
test('should render [story name] state', async () => {}) - This ensures clear mapping between stories and tests
- Create a separate
-
Visual Regression Testing
Every story should have a visual regression test:
test('should render playground state', async ({mount, expectScreenshot}) => { await mount(<ComponentNameStories.Playground />); await expectScreenshot(); });
-
Interaction Testing
Test user interactions like clicks, hovers, and input:
test('should handle button click', async ({mount, page}) => { await mount(<ComponentNameStories.WithButton />); const button = page.getByRole('button', {name: 'Click me'}); await button.click(); // Verify the result using explicit assertions await expect(page.getByText('Clicked!')).toBeVisible(); });
-
Hover States and Tooltips
test('should show tooltip on hover', async ({mount, page, expectScreenshot}) => { await mount(<ComponentNameStories.WithTooltip />); const element = page.locator('.element'); await element.hover(); // Wait for tooltip to appear using explicit assertion await expect(page.getByText('Tooltip text')).toBeVisible(); await expectScreenshot(); });
-
Async Behavior Testing
For components with async operations:
test('should handle async operation', async ({mount, page}) => { await mount(<ComponentNameStories.WithAsync />); await page.getByRole('button').click(); // Wait for async operation to complete using explicit assertion await expect(page.getByText('Loaded!')).toBeVisible(); });
-
Test Coverage Requirements
- Every exported story should have at least one test
- Include visual regression tests for all visual states
- Include interaction tests for interactive components
- Test edge cases (disabled, loading, error states)
- Test different viewport sizes if responsive behavior is important
-
Test Naming Conventions
- Use descriptive test names:
test('should [expected behavior]', async () => {}) - Examples:
test('should render enabled state', async () => {})test('should display all suggestions', async () => {})test('should handle suggestion click', async () => {})
- Use descriptive test names:
-
Using Playwright Fixtures
mount- mounts React componentspage- provides access to Playwright's page objectexpectScreenshot- performs visual regression testing- Always use
async/awaitfor Playwright operations
-
Waiting for Elements
Use Playwright's explicit assertions instead of
waitFor():// Prefer explicit assertions over waitFor() await expect(page.getByText('Hello')).toBeVisible(); // Check element visibility await expect(page.locator('.element')).toBeVisible(); // Check element state await expect(page.getByRole('button')).toBeDisabled(); await expect(page.getByRole('button')).toBeEnabled(); // Check element count await expect(page.locator('.item')).toHaveCount(5); // Wait for timeout (use sparingly, only for animations or delays) await page.waitForTimeout(500);
Why prefer
expectoverwaitFor():- More readable and explicit about what's being verified
- Better integration with Playwright's reporting system
- More informative error messages when assertions fail
- Follows Playwright best practices
- Playwright actions (click, fill, etc.) already auto-wait for elements
-
Test Tags
Use test tags for filtering:
test.describe('ComponentName', {tag: '@ComponentName'}, () => { // Tests });
Based on a component with multiple stories:
helpersPlaywright.tsx:
import {composeStories} from '@storybook/react';
import * as DefaultSubmitButtonStories from '../__stories__/SubmitButton.stories';
export const SubmitButtonStories = composeStories(DefaultSubmitButtonStories);SubmitButton.visual.spec.tsx:
import React from 'react';
import {expect, test} from '~playwright/core';
import {SubmitButtonStories} from './helpersPlaywright';
test.describe('SubmitButton', {tag: '@SubmitButton'}, () => {
// Visual regression tests for each story
test('should render enabled state', async ({mount, expectScreenshot}) => {
await mount(<SubmitButtonStories.Enabled />);
await expectScreenshot();
});
test('should render disabled state', async ({mount, expectScreenshot}) => {
await mount(<SubmitButtonStories.Disabled />);
await expectScreenshot();
});
test('should render loading state', async ({mount, expectScreenshot}) => {
await mount(<SubmitButtonStories.Loading />);
await expectScreenshot();
});
test('should render cancelable state', async ({mount, expectScreenshot}) => {
await mount(<SubmitButtonStories.Cancelable />);
await expectScreenshot();
});
test('should render all sizes', async ({mount, expectScreenshot}) => {
await mount(<SubmitButtonStories.Size />);
await expectScreenshot();
});
// Interaction test
test('should handle button click', async ({mount, page}) => {
let clicked = false;
await mount(
<SubmitButtonStories.Enabled
onClick={() => {
clicked = true;
}}
/>,
);
const button = page.getByRole('button');
await button.click();
// Additional assertions if needed
await page.waitForTimeout(100);
});
// Test disabled state prevents clicks
test('should not respond to clicks when disabled', async ({mount, page}) => {
await mount(<SubmitButtonStories.Disabled />);
const button = page.getByRole('button');
await expect(button).toBeDisabled();
});
});Every component must have a comprehensive README.md file documenting its purpose, usage, and API.
All documentation text MUST be written in English.
# ComponentName
Brief one-line description of the component.
## Features
- **Feature 1**: Description
- **Feature 2**: Description
- **Feature 3**: Description
- (List 3-7 key features with bold titles)
## Usage
\`\`\`tsx
import {ComponentName} from '@gravity-ui/aikit';
// Basic usage example
<ComponentName
prop1="value"
prop2={handler}
/>
// Advanced usage example
<ComponentName
prop1="value"
prop2={handler}
prop3={true}
> <ChildComponent />
> </ComponentName>
// Edge case example
<ComponentName
prop1="value"
disabled={true}
/>
\`\`\`
## Props
| Prop | Type | Required | Default | Description |
| ----------- | ------------------- | -------- | ------- | -------------------- |
| `prop1` | `string` | ✓ | - | Description of prop1 |
| `prop2` | `() => void` | ✓ | - | Description of prop2 |
| `prop3` | `boolean` | - | `false` | Description of prop3 |
| `prop4` | `'a' \| 'b' \| 'c'` | - | `'a'` | Description of prop4 |
| `children` | `ReactNode` | - | - | Child components |
| `className` | `string` | - | - | Additional CSS class |
| `qa` | `string` | - | - | QA/test identifier |
## [Additional Sections as Needed]
### States (for stateful components)
Describe different states the component can be in.
### Variants (for components with variants)
Describe visual or functional variants.
### Keyboard Support (for interactive components)
List keyboard shortcuts and interactions.
### Accessibility
Describe accessibility features and ARIA attributes.
## Styling
The component uses CSS variables for theming:
| Variable | Description |
| ----------------- | --------------------------- |
| `--variable-name` | Description of CSS variable |
\`\`\`css
/_ Example of how to override _/
.custom-component {
--variable-name: custom-value;
}
\`\`\`
## Implementation Details (Optional)
Additional technical details for advanced users or contributors.- Use H1 (
#) for the component name - Provide a concise one-line description
- Description should explain WHAT the component is and its PRIMARY purpose
Example:
# SubmitButton
A submit button component with state management through props and send/cancel icon switching.- Use H2 (
##) for "Features" - List 3-7 key features using unordered list
- Bold the feature name/title
- Follow with brief description
- Focus on user-facing functionality
Example:
## Features
- **State Management**: Component state is controlled via a single `state` prop
- **Icon Switching**: Automatically switches between ArrowUp and Stop icons
- **Multiple Sizes**: Supports three sizes: s, m, l
- **Keyboard Support**: Full keyboard navigation support- Use H2 (
##) for "Usage" - Provide 2-4 code examples showing:
- Basic/minimal usage
- Common usage with typical props
- Advanced usage with optional features
- Edge cases if applicable
- Use TypeScript syntax with proper typing
- Include comments explaining complex parts
- Use proper import statements
Example:
## Usage
\`\`\`tsx
import {SubmitButton} from '@gravity-ui/aikit';
// Basic usage
<SubmitButton
onClick={async () => handleSubmit()}
state="enabled"
/>
// With size
<SubmitButton
onClick={async () => handleSubmit()}
state="enabled"
size="l"
/>
\`\`\`- Use H2 (
##) for "Props" - Always include a props table with columns:
- Prop: Prop name in backticks (e.g.,
`propName`) - Type: TypeScript type in backticks
- Required: ✓ for required, - for optional
- Default: Default value in backticks, or
-if none - Description: Brief description of the prop
- Prop: Prop name in backticks (e.g.,
Formatting Rules:
- Use
`propName`for prop names - Use
`Type`for types - Use
`'value'`for string defaults - Use
`123`for number defaults - Use
`false`or`true`for boolean defaults - Use
-for no default - Use
✓for required props - Use
\|to escape pipe in union types:`'a' \| 'b'`
Example:
## Props
| Prop | Type | Required | Default | Description |
| ----------- | ----------------------------- | -------- | ------- | -------------------- |
| `onClick` | `() => void \| Promise<void>` | ✓ | - | Click handler |
| `state` | `'enabled' \| 'disabled'` | ✓ | - | Button state |
| `size` | `'s' \| 'm' \| 'l'` | - | `'m'` | Button size |
| `className` | `string` | - | - | Additional CSS class |
| `qa` | `string` | - | - | QA/test identifier |## States
The component supports the following states:
- **enabled**: Description of enabled state
- **disabled**: Description of disabled state
- **loading**: Description of loading state## Variants
### Size Variants
- **s**: Small size (24px height)
- **m**: Medium size (32px height) - default
- **l**: Large size (40px height)## Keyboard Support
- `Enter` - Submit the form
- `Shift+Enter` - Create new line
- `Escape` - Clear input## Accessibility
The component implements the following accessibility features:
- Uses semantic `<button>` element
- Includes appropriate ARIA labels
- Supports keyboard navigation
- Announces state changes to screen readers- Use H2 (
##) for "Styling" - Document ALL CSS variables used by the component
- Provide a table with variable names and descriptions
- Include code examples showing how to override styles
CSS Variable Description Format:
- Variable Name: Use exact CSS variable name in backticks (
`--variable-name`) - Description: Clear description of what the variable controls
- Use present tense: "Color of text" not "Colors text"
- Be specific: Instead of "Button color", use "Background color of button in enabled state"
- Include defaults if helpful: "Border radius (default: 4px)"
CSS Variable Categories:
- Colors: Text colors, background colors, border colors
- Spacing: Padding, margins, gaps
- Typography: Font sizes, weights, line heights, font families
- Borders: Border radius, border widths
- Sizes: Width, height, icon sizes
- Transitions: Animation durations, timing functions
Example:
## Styling
The component uses CSS variables for theming:
| Variable | Description |
| --------------------------- | ------------------------------------------- |
| `--g-color-text-primary` | Primary text color |
| `--g-color-text-secondary` | Secondary text color (for hints and labels) |
| `--g-color-base-background` | Background color of the component |
| `--g-color-line-generic` | Border color |
| `--g-spacing-1` | Gap between action icons (default: 8px) |
| `--g-text-body-font-size` | Font size for body text |
| `--g-text-body-line-height` | Line height for body text |
| `--g-border-radius-m` | Border radius for medium-sized elements |
\`\`\`css
/_ Example: Custom theme _/
.custom-button {
--g-color-text-primary: #007bff;
--g-color-base-background: #f8f9fa;
--g-border-radius-m: 8px;
}
\`\`\`
\`\`\`tsx
/_ Example: Inline styles _/
<ComponentName
className="custom-button"
style={{
'--g-color-text-primary': '#007bff',
}}
/>
\`\`\`CSS Variable Naming Patterns (based on Gravity UI):
--g-color-*: Color variables--g-text-*: Typography variables--g-spacing-*: Spacing variables--g-border-*: Border variables
When to Document CSS Variables:
- Document ALL CSS variables used directly in the component's styles
- Include variables from parent components if they affect this component
- Include both required and optional variables
- Group related variables together in the table
- Use H2 (
##) for "Implementation Details" - Include for complex components or when additional context is helpful
- Describe internal hooks, state management, or algorithms
- Reference related components or utilities
Example:
## Implementation Details
The component uses the `usePromptBox` hook for state management. The hook:
- Manages textarea value and validation
- Handles keyboard shortcuts (Enter, Shift+Enter)
- Coordinates between header, body, and footer components
- Provides submit and cancel callbacks- Language: All text MUST be in English
- Clarity: Use clear, concise language
- Examples: Provide realistic, working code examples
- Completeness: Document all public props and features
- Types: Always include TypeScript types in prop tables
- Defaults: Always specify default values for optional props
- Tables: Use consistent table formatting with proper alignment
- Code Blocks: Always specify language for code blocks (tsx, css, bash, etc.)
- Links: Link to related documentation when helpful
- Updates: Keep README in sync with component changes
Before finalizing a README, ensure:
- Component name and one-line description are present
- Features section lists 3-7 key features
- Usage section includes 2-4 examples (basic, common, advanced)
- Props table is complete with all columns filled
- All props have proper TypeScript types
- Required vs optional props are clearly marked
- Default values are specified for optional props
- Styling section documents all CSS variables
- CSS variables have clear descriptions
- Code examples use proper TypeScript syntax
- All text is in English
- Tables are properly formatted
- Code blocks specify language
This memory bank provides comprehensive guidelines for:
- Storybook Development: Creating consistent, well-documented stories using standardized templates
- Testing Strategy: Mapping tests directly to stories for consistency and coverage
- Documentation: Writing clear, complete README files with proper structure and CSS variable documentation
Key Principles:
- Consistency: Follow templates and patterns across all components
- Completeness: Document all features, props, and styling options
- English Only: All documentation, jsDoc, comments must be in English
- Test-Story Alignment: Tests should map directly to stories
- User-Focused: Write documentation for component consumers, not just developers
By following these guidelines, we ensure high-quality, maintainable, and well-documented components throughout the AIKit project.