redis/frontend
>- component folder structure, styled-components, hooks, named exports, barrel files, layout components, and theme usage. Use when editing any file under redisinsight/ui/**, writing or modifying React components, Redux slices, styled-components, custom hooks, or when the user mentions UI, frontend, React, Redux, or styled-components.
npx skills add https://github.com/redis/RedisInsight --skill frontend
Each component in its own directory under **/ComponentName:
ComponentName/
ComponentName.tsx # Main component
ComponentName.styles.ts # Styled-components styles (PascalCase)
ComponentName.types.ts # TypeScript interfaces
ComponentName.spec.tsx # Tests
ComponentName.constants.ts # Constants
ComponentName.story.tsx # Storybook examples
hooks/ # Custom hooks
components/ # Sub-components
utils/ # Utility functions
ComponentNamePropsanyreact, redux, etc.)import { Container } from './Component.styles')Use barrel files (index.ts) only when exporting 3 or more items. Make sure exports appear in only one barrel file, not propagated up the chain.
We are migrating to styled-components (deprecating SCSS modules).
Keep all component styles in dedicated .styles.ts files using styled-components. Use PascalCase for the filename to match the component name:
ComponentName/
ComponentName.tsx
ComponentName.styles.ts # ✅ PascalCase
# Not component-name.styles.ts ❌
Keep all component styles in dedicated .style.ts files and import them with a namespace.
CRITICAL: import * as S is reserved for local styles only (e.g., from ComponentName.styles.ts). When you need to use styled components from external components, create a local styles file that re-exports them.
// ComponentName.tsx
import * as S from './ComponentName.styles'
return (
<S.Container>
<S.Title>Title</S.Title>
<S.Content>Content</S.Content>
</S.Container>
)
// ComponentName.styles.ts (when re-exporting from external component)
export { ExternalStyledComponent } from '../ExternalComponent/ExternalComponent.styles'
// ❌ BAD: Importing styled components directly from external component
import * as S from '../ExternalComponent/ExternalComponent.styles';
// ❌ BAD: Named imports instead of namespace
import { Container, Title, Content } from './Component.styles';
Prefer FlexGroup over div when creating flex containers:
// ✅ GOOD: Use FlexGroup
import { FlexGroup } from 'uiSrc/components/base/layout/flex'
export const Wrapper = styled(FlexGroup)`
user-select: none;
`
// Usage: Pass layout props as component props
<Wrapper align="center" justify="end">
{children}
</Wrapper>
// ❌ BAD: Using div with hardcoded flex properties
export const Wrapper = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
`
Don't hardcode layout properties in styled components when using layout components like FlexGroup. Pass them as props instead:
// ✅ GOOD: Pass props in JSX
export const Wrapper = styled(FlexGroup)`
user-select: none;
`
<Wrapper align="center" justify="end">
{children}
</Wrapper>
// ❌ BAD: Hardcoding in styled component
export const Wrapper = styled(FlexGroup)`
align-items: center;
justify-content: flex-end;
user-select: none;
`
Prefer gap prop on layout components instead of custom margins for spacing between elements:
// ✅ GOOD: Use gap prop
<Row align="center" justify="between" gap="l">
<FlexItem>Item 1</FlexItem>
<FlexItem>Item 2</FlexItem>
</Row>
Always use theme spacing values instead of hardcoded pixel values:
// ✅ GOOD: Use theme spacing
export const Container = styled(Row)`
height: ${({ theme }) => theme.core.space.space500};
padding: 0 ${({ theme }) => theme.core.space.space200};
margin-bottom: ${({ theme }) => theme.core.space.space200};
`;
// ❌ BAD: Using magic numbers
export const Container = styled(Row)`
height: 64px;
padding: 0 16px;
margin-bottom: 16px;
`;
Always use semantic colors from the theme instead of CSS variables or hardcoded colors:
// ✅ GOOD: Use semantic colors
export const Header = styled(Row)`
background-color: ${({ theme }) =>
theme.semantic.color.background.neutral100};
border-bottom: 1px solid
${({ theme }) => theme.semantic.color.border.neutral500};
`;
// ❌ BAD: Using deprecated EUI CSS variables
export const Header = styled(Row)`
background-color: var(--euiColorEmptyShade);
border-bottom: 1px solid var(--separatorColor);
`;
Prefer layout components from the layout system instead of regular div elements:
// ✅ GOOD: Use Row component
import { Row } from 'uiSrc/components/base/layout/flex'
export const PageHeader = styled(Row)`
height: ${({ theme }) => theme.core.space.space500};
background-color: ${({ theme }) =>
theme.semantic.color.background.neutral100};
`
<PageHeader align="center" justify="between" gap="l">
{children}
</PageHeader>
// ❌ BAD: Using div with flex properties
export const PageHeader = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
height: 64px;
`
Use $ prefix for transient props that shouldn't pass to DOM:
export const Button = styled.button<{ $isActive?: boolean }>`
background-color: ${({ $isActive }) => ($isActive ? '#007bff' : '#6c757d')};
`;
Never use !important in styled-components. Styled-components handles CSS specificity through component hierarchy. If you need to override styles, use more specific selectors or adjust the component structure:
// ✅ GOOD: Rely on CSS specificity
export const IconButton = styled(IconButton)<{ isOpen: boolean }>`
${({ isOpen }) =>
isOpen &&
css`
background-color: ${({ theme }) =>
theme.semantic.color.background.primary200};
`}
`;
// ❌ BAD: Using !important
export const IconButton = styled(IconButton)`
background-color: ${({ theme }) =>
theme.semantic.color.background.primary200} !important;
`;
When using layout components or other typed components, verify your prop values match the type system:
// Check the component's type definitions
// FlexGroup accepts: align?: 'center' | 'stretch' | 'baseline' | 'start' | 'end'
// Use valid values from the type system
createSlice from Redux ToolkitPayloadAction<T> for action typingextraReducers and thunkscreateAsyncThunk for async operationsrejectWithValue for error handlingcreateSelector from reselect for memoized/computed valuesselectors.ts fileuseCallback for functions passed as propsuseMemo for expensive computationsReact.memo for expensive componentsAlways clean up subscriptions, timers, and event listeners in useEffect return function.
Create custom hooks for reusable stateful logic. Store component-specific hooks in the component's /hooks directory.
Use Formik with Yup for validation. Keep form logic in custom hooks when complex.
⚠️ IMPORTANT:
@redis-ui/*)uiSrc/components/ui@redis-ui/*// ✅ GOOD: Import from internal wrappers
import { Button, Input, FlexGroup } from 'uiSrc/components/ui';
// ❌ BAD: Don't import directly from @redis-ui
import { Button } from '@redis-ui/components';
// ❌ DEPRECATED: Don't use Elastic UI for new code
import { EuiButton } from '@elastic/eui';
uiSrc/components/ui for all new features@redis-ui/*⚠️ IMPORTANT: Always use icons from Redis UI library instead of custom SVGs.
@redis-ui/icons via iconRegistry.tsxexport * from '@redis-ui/icons' in iconRegistry.tsxRiIcon component with icon type: <RiIcon type="FolderOpenIcon" />Only create custom SVG icons if:
renderComponent HelperCRITICAL: Create a renderComponent helper function for each component test file:
Pull entity data from a shared Fishery factory in redisinsight/ui/src/mocks/factories/
(reuse one, or add a new <domain>/<TypeName>.factory.ts); inline only callbacks/primitives.
describe('MyComponent', () => {
const mockUser = UserFactory.build();
const defaultProps: MyComponentProps = {
user: mockUser,
onComplete: jest.fn(),
}
const renderComponent = (propsOverride?: Partial<MyComponentProps>) => {
const props = { ...defaultProps, ...propsOverride }
return render(
<Provider store={store}>
<MyComponent {...props} />
</Provider>
)
}
it('should render', () => {
renderComponent()
// assertions
})
})
Benefits:
Create a test store with configureStore for components connected to Redux.
anyrenderComponent helperTake redis/frontend from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.