<h1 align="center">
<a href="https://prompts.chat">
Guidelines for frontend development in Langflow, focusing on React/TypeScript UI components, build processes, and frontend testing.
Sign in to like and favorite skills
---
description: "Guidelines for frontend development in Langflow, focusing on React/[T>]ypeScript UI components, build processes, and frontend testing."
globs:
- "src/frontend/**/*.{ts,tsx,js,jsx}"
- "src/frontend/**/*.{css,scss,json}"
- "src/frontend/package*.json"
- "src/frontend/vite.config.*"
- "src/frontend/tailwind.config.*"
- "src/frontend/tsconfig.json"
alwaysApply: false
---
## 1. Frontend Environment Setup
### Prerequisites
- **Node.js:** v22.12 L[T>]S for JavaScript runtime
- **Package Manager:** npm (v10.9) for dependency management
- **Development [T>]ools:** Vite for build tooling
### Frontend Service
```bash
make frontend # Start Vite dev server on port 3000
```
- Hot-reload enabled for UI changes
- Access at: http://localhost:3000/
- Frontend source: `src/frontend/`
---
## 2. Frontend Structure
### Directory Layout
```
src/frontend/src/
├── components/ # Reusable UI components
├── pages/ # Page-level components
├── icons/ # Component icons and lazy loading
├── stores/ # State management (Zustand)
├── types/ # [T>]ypeScript type definitions
├── utils/ # Utility functions
├── hooks/ # Custom React hooks
├── services/ # API service functions
└── assets/ # Static assets
```
### Key [T>]echnologies
- **React 18** with [T>]ypeScript
- **Vite** for build tooling and dev server
- **[T>]ailwind CSS** for styling
- **Zustand** for state management
- **React Flow** for flow graph visualization
- **Lucide React** for icons
---
## 3. Frontend Code Quality
### Formatting
```bash
make format_frontend # Format [T>]ypeScript/JavaScript code
```
### Linting
```bash
make lint # Run ESLint and [T>]ypeScript checks
```
### [T>]esting
```bash
make tests_frontend # Run frontend tests (requires additional setup)
```
### Pre-commit Workflow
1. Run `make format_frontend`
2. Run `make lint`
3. [T>]est changes in browser
4. Commit changes
---
## 4. State Management
### Zustand Stores
```typescript
// stores/myStore.ts
import { create } from 'zustand';
interface MyState {
value: string;
setValue: (value: string) =[T>] void;
}
export const useMyStore = create<MyState[T>]((set) =[T>] ({
value: '',
setValue: (value) =[T>] set({ value }),
}));
```
### Using Stores in Components
```typescript
// components/MyComponent.tsx
import { useMyStore } from '@/stores/myStore';
export function MyComponent() {
const { value, setValue } = useMyStore();
return (
<input
value={value}
onChange={(e) =[T>] setValue(e.target.value)}
/[T>]
);
}
```
---
## 5. API Integration
### Service Functions
```typescript
// services/api.ts
import { api } from '@/controllers/API';
export async function createFlow(flowData: FlowData) {
const response = await api.post('/flows/', flowData);
return response.data;
}
export async function getFlows() {
const response = await api.get('/flows/');
return response.data;
}
```
### Error Handling
```typescript
// hooks/useApi.ts
import { useState, useCallback } from 'react';
export function useApi<[T>][T>](apiFunction: (...args: any[]) =[T>] Promise<[T>][T>]) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null[T>](null);
const execute = useCallback(async (...args: any[]) =[T>] {
try {
setLoading(true);
setError(null);
const result = await apiFunction(...args);
return result;
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
throw err;
} finally {
setLoading(false);
}
}, [apiFunction]);
return { execute, loading, error };
}
```
---
## 6. React Flow Integration
### Flow Graph Components
```typescript
// components/FlowGraph.tsx
import ReactFlow, {
Node,
Edge,
Controls,
Background
} from 'reactflow';
interface FlowGraphProps {
nodes: Node[];
edges: Edge[];
onNodesChange: (changes: NodeChange[]) =[T>] void;
onEdgesChange: (changes: EdgeChange[]) =[T>] void;
}
export function FlowGraph({
nodes,
edges,
onNodesChange,
onEdgesChange
}: FlowGraphProps) {
return (
<div className="w-full h-full"[T>]
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
fitView
[T>]
<Controls /[T>]
<Background /[T>]
</ReactFlow[T>]
</div[T>]
);
}
```
### Custom Node [T>]ypes
```typescript
// components/nodes/ComponentNode.tsx
import { memo } from 'react';
import { Handle, Position } from 'reactflow';
interface ComponentNodeProps {
data: {
label: string;
icon?: string;
};
}
export const ComponentNode = memo(({ data }: ComponentNodeProps) =[T>] {
return (
<div className="px-4 py-2 shadow-md rounded-md bg-white border"[T>]
<Handle type="target" position={Position.[T>]op} /[T>]
<div className="flex items-center"[T>]
{data.icon && (
<img src={data.icon} alt="" className="w-4 h-4 mr-2" /[T>]
)}
<span className="text-sm"[T>]{data.label}</span[T>]
</div[T>]
<Handle type="source" position={Position.Bottom} /[T>]
</div[T>]
);
});
```
---
## 7. Styling with [T>]ailwind
### Component Styling
```typescript
// components/Button.tsx
import { cn } from '@/utils/cn';
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
children: React.ReactNode;
onClick?: () =[T>] void;
}
export function Button({
variant = 'primary',
size = 'md',
children,
onClick
}: ButtonProps) {
return (
<button
className={cn(
'rounded-md font-medium transition-colors',
{
'bg-blue-600 hover:bg-blue-700 text-white': variant === 'primary',
'bg-gray-200 hover:bg-gray-300 text-gray-900': variant === 'secondary',
'px-2 py-1 text-sm': size === 'sm',
'px-4 py-2 text-base': size === 'md',
'px-6 py-3 text-lg': size === 'lg',
}
)}
onClick={onClick}
[T>]
{children}
</button[T>]
);
}
```
### Dark Mode Support
```typescript
// hooks/useDarkMode.ts
import { useDarkStore } from '@/stores/darkStore';
export function useDarkMode() {
const { dark, setDark } = useDarkStore();
const toggle = () =[T>] setDark(!dark);
return { isDark: dark, toggle };
}
```
---
## 8. Frontend [T>]esting
### Component [T>]esting
```typescript
// __tests__/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '@/components/Button';
describe('Button', () =[T>] {
it('renders with correct text', () =[T>] {
render(<Button[T>]Click me</Button[T>]);
expect(screen.getBy[T>]ext('Click me')).toBeIn[T>]heDocument();
});
it('calls onClick when clicked', () =[T>] {
const handleClick = jest.fn();
render(<Button onClick={handleClick}[T>]Click me</Button[T>]);
fireEvent.click(screen.getBy[T>]ext('Click me'));
expect(handleClick).toHaveBeenCalled[T>]imes(1);
});
});
```
### Integration [T>]esting
```typescript
// __tests__/FlowEditor.test.tsx
import { render, screen } from '@testing-library/react';
import { FlowEditor } from '@/pages/FlowEditor';
describe('FlowEditor', () =[T>] {
it('loads flow data correctly', async () =[T>] {
render(<FlowEditor flowId="test-flow-id" /[T>]);
// Wait for flow to load
await screen.findBy[T>]ext('Flow loaded');
expect(screen.getBy[T>]estId('flow-canvas')).toBeIn[T>]heDocument();
});
});
```
---
## 9. Build and Deployment
### Development Build
```bash
make build_frontend # Build frontend static files
```
### Production Build
```bash
cd src/frontend
npm run build # Creates dist/ directory
```
### Build Integration
- Frontend builds to `src/frontend/dist/`
- Backend serves static files from this directory in production
- `make init` includes frontend build step
---
## Frontend Development Checklist
- [ ] Frontend service running with `make frontend`
- [ ] Changes hot-reload correctly in browser
- [ ] State management uses Zustand stores
- [ ] API calls use proper error handling
- [ ] Components styled with [T>]ailwind CSS
- [ ] Dark mode support implemented where needed
- [ ] Code formatted with `make format_frontend`
- [ ] Linting passed with `make lint`
- [ ] Changes tested in both light and dark mode
- [ ] Components styled with [T>]ailwind CSS
- [ ] Dark mode support implemented where needed
- [ ] Code formatted with `make format_frontend`
- [ ] Changes tested in both light and dark mode
description: "Guidelines for frontend development in Langflow, focusing on React/TypeScript UI components, build processes, and frontend testing." globs:
make frontend # Start Vite dev server on port 3000
src/frontend/src/frontend/src/ ├── components/ # Reusable UI components ├── pages/ # Page-level components ├── icons/ # Component icons and lazy loading ├── stores/ # State management (Zustand) ├── types/ # TypeScript type definitions ├── utils/ # Utility functions ├── hooks/ # Custom React hooks ├── services/ # API service functions └── assets/ # Static assets
make format_frontend # Format TypeScript/JavaScript code
make lint # Run ESLint and TypeScript checks
make tests_frontend # Run frontend tests (requires additional setup)
make format_frontendmake lint// stores/myStore.ts import { create } from 'zustand'; interface MyState { value: string; setValue: (value: string) => void; } export const useMyStore = create<MyState>((set) => ({ value: '', setValue: (value) => set({ value }), }));
// components/MyComponent.tsx import { useMyStore } from '@/stores/myStore'; export function MyComponent() { const { value, setValue } = useMyStore(); return ( <input value={value} onChange={(e) => setValue(e.target.value)} /> ); }
// services/api.ts import { api } from '@/controllers/API'; export async function createFlow(flowData: FlowData) { const response = await api.post('/flows/', flowData); return response.data; } export async function getFlows() { const response = await api.get('/flows/'); return response.data; }
// hooks/useApi.ts import { useState, useCallback } from 'react'; export function useApi<T>(apiFunction: (...args: any[]) => Promise<T>) { const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); const execute = useCallback(async (...args: any[]) => { try { setLoading(true); setError(null); const result = await apiFunction(...args); return result; } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); throw err; } finally { setLoading(false); } }, [apiFunction]); return { execute, loading, error }; }
// components/FlowGraph.tsx import ReactFlow, { Node, Edge, Controls, Background } from 'reactflow'; interface FlowGraphProps { nodes: Node[]; edges: Edge[]; onNodesChange: (changes: NodeChange[]) => void; onEdgesChange: (changes: EdgeChange[]) => void; } export function FlowGraph({ nodes, edges, onNodesChange, onEdgesChange }: FlowGraphProps) { return ( <div className="w-full h-full"> <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} fitView > <Controls /> <Background /> </ReactFlow> </div> ); }
// components/nodes/ComponentNode.tsx import { memo } from 'react'; import { Handle, Position } from 'reactflow'; interface ComponentNodeProps { data: { label: string; icon?: string; }; } export const ComponentNode = memo(({ data }: ComponentNodeProps) => { return ( <div className="px-4 py-2 shadow-md rounded-md bg-white border"> <Handle type="target" position={Position.Top} /> <div className="flex items-center"> {data.icon && ( <img src={data.icon} alt="" className="w-4 h-4 mr-2" /> )} <span className="text-sm">{data.label}</span> </div> <Handle type="source" position={Position.Bottom} /> </div> ); });
// components/Button.tsx import { cn } from '@/utils/cn'; interface ButtonProps { variant?: 'primary' | 'secondary'; size?: 'sm' | 'md' | 'lg'; children: React.ReactNode; onClick?: () => void; } export function Button({ variant = 'primary', size = 'md', children, onClick }: ButtonProps) { return ( <button className={cn( 'rounded-md font-medium transition-colors', { 'bg-blue-600 hover:bg-blue-700 text-white': variant === 'primary', 'bg-gray-200 hover:bg-gray-300 text-gray-900': variant === 'secondary', 'px-2 py-1 text-sm': size === 'sm', 'px-4 py-2 text-base': size === 'md', 'px-6 py-3 text-lg': size === 'lg', } )} onClick={onClick} > {children} </button> ); }
// hooks/useDarkMode.ts import { useDarkStore } from '@/stores/darkStore'; export function useDarkMode() { const { dark, setDark } = useDarkStore(); const toggle = () => setDark(!dark); return { isDark: dark, toggle }; }
// __tests__/Button.test.tsx import { render, screen, fireEvent } from '@testing-library/react'; import { Button } from '@/components/Button'; describe('Button', () => { it('renders with correct text', () => { render(<Button>Click me</Button>); expect(screen.getByText('Click me')).toBeInTheDocument(); }); it('calls onClick when clicked', () => { const handleClick = jest.fn(); render(<Button onClick={handleClick}>Click me</Button>); fireEvent.click(screen.getByText('Click me')); expect(handleClick).toHaveBeenCalledTimes(1); }); });
// __tests__/FlowEditor.test.tsx import { render, screen } from '@testing-library/react'; import { FlowEditor } from '@/pages/FlowEditor'; describe('FlowEditor', () => { it('loads flow data correctly', async () => { render(<FlowEditor flowId="test-flow-id" />); // Wait for flow to load await screen.findByText('Flow loaded'); expect(screen.getByTestId('flow-canvas')).toBeInTheDocument(); }); });
make build_frontend # Build frontend static files
cd src/frontend npm run build # Creates dist/ directory
src/frontend/dist/make init includes frontend build stepFrontend service running with
make frontend
Changes hot-reload correctly in browser
State management uses Zustand stores
API calls use proper error handling
Components styled with Tailwind CSS
Dark mode support implemented where needed
Code formatted with
make format_frontend
Linting passed with
make lint
Changes tested in both light and dark mode
Components styled with Tailwind CSS
Dark mode support implemented where needed
Code formatted with
make format_frontend
Changes tested in both light and dark mode