// "Build user interfaces using Redpanda UI Registry components with React, TypeScript, and Vitest testing. Use when user requests UI components, pages, forms, or mentions 'build UI', 'create component', 'design system', 'frontend', or 'registry'."
| name | frontend-developer |
| description | Build user interfaces using Redpanda UI Registry components with React, TypeScript, and Vitest testing. Use when user requests UI components, pages, forms, or mentions 'build UI', 'create component', 'design system', 'frontend', or 'registry'. |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep, mcp__ide__getDiagnostics |
Build UIs using the Redpanda UI Registry design system following this repo's patterns.
ALWAYS:
mcp__context7__get-library-docs with library /websites/redpanda-ui-registry_netlify_appsrc/components/redpanda-ui/yes | bunx @fumadocs/cli add --dir https://redpanda-ui-registry.netlify.app/r <component>bun i --yarn after installing packagesNEVER:
@redpanda-data/ui or Chakra UIcli.json)className directly to registry componentsstyle prop on registry componentsconsole.log or comments in code# REQUIRED FIRST STEP
Invoke: mcp__context7__get-library-docs
Library: /websites/redpanda-ui-registry_netlify_app
# Single component
yes | bunx @fumadocs/cli add --dir https://redpanda-ui-registry.netlify.app/r button
# Multiple components (space-separated)
yes | bunx @fumadocs/cli add --dir https://redpanda-ui-registry.netlify.app/r card dialog form
# Then generate yarn.lock
bun i && bun i --yarn
Structure:
// Functional component with explicit types
interface Props {
title: string;
onSubmit: (data: FormData) => void;
}
export function MyComponent({ title, onSubmit }: Props) {
// Component logic
}
Styling:
// โ
CORRECT: Use component variants
<Button variant="primary" size="lg">Click</Button>
// โ
CORRECT: Wrap for spacing
<div className="mt-4">
<Button variant="primary">Click</Button>
</div>
// โ WRONG: Direct margin on component
<Button className="mt-4" variant="primary">Click</Button>
TypeScript:
any - infer correct typesPerformance:
useMemo for expensive computations - but only when there's a noticeable performance impactmemo for components receiving propsTest types:
.test.ts): Pure logic, utilities, helpers - Node environment.test.tsx): React components, UI interactions - JSDOM environmentUnit test example:
// utils.test.ts
import { formatNumber } from "./utils";
describe("formatNumber", () => {
test("should format numbers with commas", () => {
expect(formatNumber(1000)).toBe("1,000");
});
});
Integration test example:
// component.test.tsx
import { render, screen, fireEvent, waitFor } from 'test-utils';
import { createRouterTransport } from '@connectrpc/connect';
import { createPipeline } from 'protogen/redpanda/api/console/v1alpha1/pipeline-PipelineService_connectquery';
import { MyComponent } from './component';
describe('MyComponent', () => {
test('should trigger gRPC mutation when form is submitted', async () => {
// Mock the gRPC service method
const mockCreatePipeline = vi.fn(() =>
Promise.resolve({ id: '123', name: 'test-pipeline' })
);
// Create a mocked transport
const transport = createRouterTransport(({ rpc }) => {
rpc(createPipeline, mockCreatePipeline);
});
// Render with the mocked transport
render(<MyComponent />, { transport });
// Fill out the form
fireEvent.change(screen.getByLabelText('Pipeline Name'), {
target: { value: 'test-pipeline' }
});
// Submit the form
fireEvent.click(screen.getByRole('button', { name: 'Create' }));
// Verify the mutation was called with correct data
await waitFor(() => {
expect(mockCreatePipeline).toHaveBeenCalledWith({
name: 'test-pipeline'
});
});
});
});
Mocking:
vi.mock("module-name", async (importOriginal) => {
const actual = await importOriginal<typeof import("module-name")>();
return {
...actual,
functionToMock: vi.fn(),
};
});
const mockFunction = vi.mocked(functionToMock);
# Run in order
bun run type:check # TypeScript errors
bun run test # All tests
bun run lint # Code quality
bun run build # Production build
bun run start2 --port=3004 # Dev server - check browser
Success criteria:
bun run test # All tests (unit + integration)
bun run test:unit # Unit tests only (.test.ts)
bun run test:integration # Integration tests only (.test.tsx)
bun run test:watch # Watch mode
bun run test:coverage # Coverage report
Check src/components/redpanda-ui/ for installed components before installing new ones.
Use component variants instead of custom styling:
<Button variant="primary" size="lg" />
<Card variant="outline" />
<Dialog size="md" />
For complex forms, use React Hook Form + Zod:
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
const schema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { name: "", email: "" },
});
For simple forms, use AutoForm:
import { AutoForm } from '@autoform/react';
import { ZodProvider } from '@autoform/zod';
<AutoForm
schema={schema}
onSubmit={handleSubmit}
provider={ZodProvider}
/>
Create sub-components in the same file when logic repeats:
export function FeatureComponent() {
return (
<div>
<FeatureHeader />
<FeatureContent />
</div>
);
}
// Colocated sub-components
function FeatureHeader() { /* ... */ }
function FeatureContent() { /* ... */ }
File naming:
.test.ts for utilities and pure functions (unit tests).test.tsx for React components (integration tests)Mock location:
src/test-utils/Render utility:
test-utils/test-utils.tsx for component testsAsync testing:
waitFor for async operationsCheck before installing:
# Check if component exists
ls src/components/redpanda-ui/components/
Multi-component install:
yes | bunx @fumadocs/cli add --dir https://redpanda-ui-registry.netlify.app/r \
button card dialog form input label select
After package install:
bun i && bun i --yarn # ALWAYS run this
Testing shortcuts:
.test.ts.test.tsxsrc/test-utils/test-utils/test-utils.tsxAfter completing work: