This documentation illustrates how we approach component development in Superset and provides examples to help you in writing new components or updating existing ones by following our community-approved standards.
This guide is intended primarily for reusable components. Whenever possible, all new components should be designed with reusability in mind.
superset-frontend/src/components
{ComponentName}/
index.tsx
{ComponentName}.test.tsx
{ComponentName}.stories.tsx
Components root directory: Components that are meant to be re-used across different parts of the application should go in the superset-frontend/src/components directory. Components that are meant to be specific for a single part of the application should be located in the nearest directory where the component is used, for example, superset-frontend/src/Explore/components
Exporting the component: All components within the superset-frontend/src/components directory should be exported from superset-frontend/src/components/index.ts to facilitate their imports by other components
Component directory name: Use PascalCase for the component directory name
Storybook: Components should come with a storybook file whenever applicable, with the following naming convention {ComponentName}.stories.tsx. More details about Storybook below
Unit and end-to-end tests: All components should come with unit tests using Jest and React Testing Library. The file name should follow this naming convention {ComponentName}.test.tsx. Read the Testing Guidelines and Best Practices for more details about tests
All new components should be written in TypeScript. This helps catch errors early and provides better development experience with IDE support.
interface ComponentProps { title: string; isVisible?: boolean; onClose?: () => void; } export const MyComponent: React.FC<ComponentProps> = ({ title, isVisible = true, onClose }) => { // Component implementation };
Use functional components with hooks instead of class components:
// ✅ Good - Functional component with hooks export const MyComponent: React.FC<Props> = ({ data }) => { const [loading, setLoading] = useState(false); useEffect(() => { // Effect logic }, []); return <div>{/* Component JSX */}</div>; }; // ❌ Avoid - Class component class MyComponent extends React.Component { // Class implementation }
Extend Ant Design components rather than building from scratch:
import { Button } from 'antd'; import styled from '@emotion/styled'; const StyledButton = styled(Button)` // Custom styling using emotion `;
Design components with reusability in mind:
interface ButtonProps { variant?: 'primary' | 'secondary' | 'tertiary'; size?: 'small' | 'medium' | 'large'; loading?: boolean; disabled?: boolean; children: React.ReactNode; onClick?: () => void; } export const CustomButton: React.FC<ButtonProps> = ({ variant = 'primary', size = 'medium', ...props }) => { // Implementation };
Every component should include comprehensive tests:
// MyComponent.test.tsx import { render, screen, fireEvent } from '@testing-library/react'; import { MyComponent } from './MyComponent'; test('renders component with title', () => { render(<MyComponent title="Test Title" />); expect(screen.getByText('Test Title')).toBeInTheDocument(); }); test('calls onClose when close button is clicked', () => { const mockOnClose = jest.fn(); render(<MyComponent title="Test" onClose={mockOnClose} />); fireEvent.click(screen.getByRole('button', { name: /close/i })); expect(mockOnClose).toHaveBeenCalledTimes(1); });
Create stories for visual testing and documentation:
// MyComponent.stories.tsx import type { Meta, StoryObj } from '@storybook/react'; import { MyComponent } from './MyComponent'; const meta: Meta<typeof MyComponent> = { title: 'Components/MyComponent', component: MyComponent, parameters: { layout: 'centered', }, tags: ['autodocs'], }; export default meta; type Story = StoryObj<typeof meta>; export const Default: Story = { args: { title: 'Default Component', isVisible: true, }, }; export const Hidden: Story = { args: { title: 'Hidden Component', isVisible: false, }, };
import React, { memo } from 'react'; export const ExpensiveComponent = memo<Props>(({ data }) => { // Expensive rendering logic return <div>{/* Component content */}</div>; });
Use useCallback and useMemo appropriately:
export const OptimizedComponent: React.FC<Props> = ({ items, onSelect }) => { const expensiveValue = useMemo(() => { return items.reduce((acc, item) => acc + item.value, 0); }, [items]); const handleSelect = useCallback((id: string) => { onSelect(id); }, [onSelect]); return <div>{/* Component content */}</div>; };
Ensure components are accessible:
export const AccessibleButton: React.FC<Props> = ({ children, onClick }) => { return ( <button type="button" aria-label="Descriptive label" onClick={onClick} > {children} </button> ); };
For components that might fail, consider error boundaries:
export const SafeComponent: React.FC<Props> = ({ children }) => { return ( <ErrorBoundary fallback={<div>Something went wrong</div>}> {children} </ErrorBoundary> ); };