Skip to main content

UI Components

Tables, cells, forms, styling, and testing patterns.

Table System

The table system is built on TanStack React Table and provides a declarative API.

TableConfig

Tables are configured through TableConfig:

export interface TableConfig<T extends TableData = TableData> {
columns: ColumnConfig<T>[];
emptyState?: EmptyState;
disablePagination?: boolean;
disableSorting?: boolean;
disableSearch?: boolean;
disableFilters?: boolean;
pageSizes?: PageSizeOption[];
enableStickySides?: boolean;
actions?: React.ComponentType<{ record: T }>;
}

ColumnConfig

ColumnConfig defines how data is displayed and interacted with:

export type ColumnConfig<TData = TableData> = Cell<TData> & {
filterMode?: FilterMode; // 'NONE' | 'CLIENT' | 'SERVER'
enableSorting?: boolean; // default: true
enableGrouping?: boolean; // default: false
aggregationFn?: AggregationFnOption<TData>;
sortingFn?: SortingFnOption<TData>;
tooltip?: ColumnTooltip<TData>;
};

Table State

TableState supports both controlled and uncontrolled state:

export type TableState = {
globalFilter: string;
columnFilters: ColumnFilter[];
pagination: PaginationState;
sorting: SortingState;
columnOrder: ColumnOrderState;
columnVisibility: ColumnVisibilityState;
rowSelection: RowSelectionState;
rowSelectionEnabled: boolean;
grouping: GroupingState;
};

Table View States

Tables display different views based on their state:

StateDescription
loadingData is being fetched
emptyNo data available
readyData is displayed
errorAn error occurred
filtered-emptyFilters returned no results
no-columnsNo visible columns

Cell Renderer System

Cells define how individual data values are rendered in tables and detail views.

SharedCell Interface

SharedCell is the base interface for all cell types:

export interface SharedCell<T = unknown> {
id: string; // Unique identifier, also used as accessor
accessor?: Accessor; // Custom data access path or function
label?: string; // Header label
type?: string; // Filter type hint (e.g., 'date')
icon?: string; // Icon before value
tooltip?: CellTooltip; // Hover tooltip
endEnhancer?: { content: ReactNode; type: 'tooltip' };
Cell?: CellRenderer<T>; // Custom renderer
style?: StyleObject | CellStyleFunction;
}

Cell Types

The Cell type is a union of all cell configurations:

export type Cell<T = unknown> = SharedCell<T> & (
| DescriptionCellConfig // Name with description
| LinkCellConfig // Clickable link
| MultiCellConfig // Multiple values
| StateCellConfig // Status indicator
| TypeCellConfig // Type badge
);

CellRenderer

Custom cell renderers receive standardized props:

export interface CellRendererProps<T = unknown> {
column: CellConfig; // Column configuration
record: object; // Full row data
value: T | undefined; // Resolved cell value
CellComponent?: CellRenderer<T>; // For recursive rendering
}

Cell Configuration Examples

// Simple text cell
{ id: 'metadata.name', label: 'Name' }

// Cell with accessor function
{
id: 'revision',
label: 'Revision',
accessor: (row) => `v${row.spec?.revisionId}`,
}

// State cell with color mapping
{
id: 'status.phase',
label: 'Status',
type: 'state',
states: {
Running: 'positive',
Failed: 'negative',
Pending: 'warning',
},
}

// Link cell
{
id: 'metadata.name',
label: 'Pipeline',
type: 'link',
href: '/projects/${projectId}/pipelines/${row.metadata.name}',
}

Form System

Forms are built on React Final Form.

Form Component

FormProps is the base form component interface:

export interface FormProps<FieldValues extends FormData = FormData> {
onSubmit: (values: FieldValues) => void | object | Promise<object>;
initialValues?: DeepPartial<FieldValues>;
id?: string; // For external submit buttons
children: React.ReactNode;
render?: (formElement: React.ReactNode) => React.ReactNode;
}

Form State

export interface FormState<FieldValues extends FormData = FormData> {
submitting: boolean;
submitError?: string;
values?: FieldValues;
}

export interface FieldState {
error?: string;
touched: boolean;
}

export interface FieldInput<T = unknown> {
value: T;
name: string;
onChange: (value: T) => void;
onBlur: () => void;
}

Field Types

Available field components:

FieldDescription
StringFieldText input
BooleanFieldCheckbox/toggle
SelectFieldDropdown selection
RadioFieldRadio button group

Form Hooks

  • useField(name) - Get field state and input props
  • useFormState() - Get form submission state

Theme and Styling

Styletron

Michelangelo UI uses Styletron with BaseUI for styling:

import { useStyletron } from 'baseui';

function MyComponent() {
const [css, theme] = useStyletron();

return (
<div className={css({
display: 'flex',
gap: theme.sizing.scale400,
padding: theme.sizing.scale600,
})}>
{/* content */}
</div>
);
}

Styled Components

For complex or reusable styles, use styled():

import { styled } from 'baseui';

export const TaskSeparator = styled('div', ({ $theme }) => ({
height: '1px',
backgroundColor: $theme.colors.borderOpaque,
margin: `${$theme.sizing.scale600} 0`,
}));

When to Extract Styles

Extract to styled components when:

  • 4+ CSS properties
  • Used in multiple places
  • Complex computed values or pseudo-selectors
  • Inline styles make JSX hard to read

Naming Styled Components

Use semantic names that describe purpose:

// Good
TaskSeparator, ExecutionMatrix, PipelineHeader

// Avoid
Container, Card, Wrapper

Testing Patterns

Test Framework

Tests use Vitest with React Testing Library:

import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';

describe('MyComponent', () => {
it('renders content', () => {
render(<MyComponent />);
expect(screen.getByText('Hello')).toBeInTheDocument();
});
});

Test Wrappers

Use buildWrapper to compose provider wrappers:

import { buildWrapper } from '@test/utils/wrappers/build-wrapper';
import { getRouterWrapper } from '@test/utils/wrappers/get-router-wrapper';
import { getServiceProviderWrapper } from '@test/utils/wrappers/get-service-provider-wrapper';

const wrapper = buildWrapper([
getRouterWrapper(),
getServiceProviderWrapper({ mockResponses }),
]);

render(<MyComponent />, wrapper);

Available wrapper functions:

WrapperPurpose
getBaseProviderWrapperBaseUI theme and Styletron
getRouterWrapperReact Router context
getServiceProviderWrapperMock RPC requests
getErrorProviderWrapperError normalization
getInterpolationProviderWrapperInterpolation context
getIconProviderWrapperIcon components
getFormProviderWrapperForm context
getCellProviderWrapperCell rendering context
getUserProviderWrapperUser context

Testing Guidelines

  • Test user-facing behavior, not implementation details
  • Use screen.getByRole() and screen.getByText() over container queries
  • Mock external APIs and RPC calls
  • Use real internal hooks and components
  • Place tests in nearest __tests__/ directory