Frontend Development Prompt Templates
AI prompt templates for frontend development. Build responsive UIs with React, Vue, and more.
Overview
Frontend development creates the user-facing layer of applications, what users see and interact with. These prompts help you build components, handle state, implement responsive designs, and solve common UI challenges. The key is specifying your framework, design requirements, and interaction patterns for code that fits your project.
Best Practices
Specify your framework and version (React 18, Vue 3, etc.), syntax and patterns differ significantly
Include your styling approach (Tailwind, CSS modules, styled-components) for consistent output
Describe the component's purpose and where it fits in the UI hierarchy
Mention accessibility requirements, they affect component structure and attributes
Include any design system or component library you're using (shadcn/ui, MUI, etc.)
Prompt Templates
1. React Component Generator
Create a React component with these specifications: **Component name:** [NAME] **Purpose:** [WHAT IT DOES] **Props:** - [PROP 1]: [TYPE] - [DESCRIPTION] - [PROP 2]: [TYPE] - [DESCRIPTION] **Features:** - [FEATURE 1 - e.g. loading state] - [FEATURE 2 - e.g. error handling] - [FEATURE 3 - e.g. pagination] **Styling:** - Method: [TAILWIND / CSS MODULES / STYLED-COMPONENTS] - Design system: [IF ANY] **State management:** - Local state: [WHAT STATE IT MANAGES] - External: [REDUX / CONTEXT / ZUSTAND / NONE] **Accessibility:** - [REQUIREMENTS - e.g. keyboard navigation, screen reader support] **Provide:** 1. Component code with TypeScript 2. Props interface 3. Usage example 4. Any custom hooks if needed
Component: UserCard Purpose: Display user profile info with actions Props: user (User object), onEdit (callback), onDelete (callback), isLoading (boolean) Features: Loading skeleton, confirmation modal for delete, responsive layout Styling: Tailwind CSS State: Modal open/close Accessibility: Keyboard accessible buttons, proper ARIA labels
```typescript
// UserCard.tsx
import { useState } from 'react';
import { User } from '@/types';
import { ConfirmModal } from '@/components/ConfirmModal';
import { Skeleton } from '@/components/Skeleton';
interface UserCardProps {
user: User;
onEdit: (userId: string) => void;
onDelete: (userId: string) => Promise<void>;
isLoading?: boolean;
}
export function UserCard({ user, onEdit, onDelete, isLoading = false }: UserCardProps) {
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const handleDelete = async () => {
setIsDeleting(true);
try {
await onDelete(user.id);
setIsDeleteModalOpen(false);
} finally {
setIsDeleting(false);
}
};
if (isLoading) {
return <UserCardSkeleton />;
}
return (
<>
<article
className="bg-white rounded-lg shadow-md p-4 sm:p-6 flex flex-col sm:flex-row gap-4"
aria-label={`Profile card for ${user.name}`}
>
{/* Avatar */}
<div className="flex-shrink-0">
<img
src={user.avatarUrl || '/default-avatar.png'}
alt=""
className="w-16 h-16 sm:w-20 sm:h-20 rounded-full object-cover"
aria-hidden="true"
/>
</div>
{/* User Info */}
<div className="flex-grow min-w-0">
<h3 className="text-lg font-semibold text-gray-900 truncate">
{user.name}
</h3>
<p className="text-sm text-gray-500 truncate">{user.email}</p>
{user.role && (
<span className="inline-block mt-2 px-2 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded">
{user.role}
</span>
)}
</div>
{/* Actions */}
<div className="flex sm:flex-col gap-2 sm:ml-4">
<button
onClick={() => onEdit(user.id)}
className="flex-1 sm:flex-none px-4 py-2 text-sm font-medium text-blue-600 bg-blue-50 rounded-md hover:bg-blue-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
aria-label={`Edit ${user.name}'s profile`}
>
Edit
</button>
<button
onClick={() => setIsDeleteModalOpen(true)}
className="flex-1 sm:flex-none px-4 py-2 text-sm font-medium text-red-600 bg-red-50 rounded-md hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition-colors"
aria-label={`Delete ${user.name}'s account`}
>
Delete
</button>
</div>
</article>
{/* Delete Confirmation Modal */}
<ConfirmModal
isOpen={isDeleteModalOpen}
onClose={() => setIsDeleteModalOpen(false)}
onConfirm={handleDelete}
isLoading={isDeleting}
title="Delete User"
message={`Are you sure you want to delete ${user.name}? This action cannot be undone.`}
confirmText="Delete"
confirmVariant="danger"
/>
</>
);
}
// Loading skeleton
function UserCardSkeleton() {
return (
<div className="bg-white rounded-lg shadow-md p-4 sm:p-6 flex flex-col sm:flex-row gap-4 animate-pulse">
<Skeleton className="w-16 h-16 sm:w-20 sm:h-20 rounded-full" />
<div className="flex-grow space-y-2">
<Skeleton className="h-5 w-32" />
<Skeleton className="h-4 w-48" />
<Skeleton className="h-6 w-16 mt-2" />
</div>
<div className="flex sm:flex-col gap-2">
<Skeleton className="h-9 w-20" />
<Skeleton className="h-9 w-20" />
</div>
</div>
);
}
```
### Usage Example
```typescript
import { UserCard } from '@/components/UserCard';
import { useUser } from '@/hooks/useUser';
function UserProfile({ userId }: { userId: string }) {
const { user, isLoading, deleteUser } = useUser(userId);
const router = useRouter();
return (
<UserCard
user={user}
isLoading={isLoading}
onEdit={(id) => router.push(`/users/${id}/edit`)}
onDelete={deleteUser}
/>
);
}
```When building new React components, refactoring existing components, or learning component patterns
- •Separate loading states into skeleton components for better UX and code organization
- •Use aria-label for buttons with actions, screen readers need context beyond just 'Delete'
2. Form Implementation
Create a form component with validation: **Form purpose:** [WHAT DATA IT COLLECTS] **Framework:** [REACT / VUE / ANGULAR] **Fields:** - [FIELD 1]: [TYPE] - [VALIDATION RULES] - [FIELD 2]: [TYPE] - [VALIDATION RULES] - [FIELD 3]: [TYPE] - [VALIDATION RULES] **Form library:** [REACT-HOOK-FORM / FORMIK / VEE-VALIDATE / NATIVE] **Validation:** [ZOD / YUP / CUSTOM] **Behavior:** - Submit action: [WHAT HAPPENS ON SUBMIT] - Error handling: [HOW TO SHOW ERRORS] - Loading state: [WHILE SUBMITTING] **Styling:** [TAILWIND / CSS / COMPONENT LIBRARY] **Provide:** 1. Form component 2. Validation schema 3. Error display 4. Submit handling
Purpose: User registration form Framework: React Fields: name (required, 2-50 chars), email (required, valid email), password (required, 8+ chars, 1 number), confirmPassword (must match) Library: React Hook Form + Zod Behavior: Submit to API, show field-level errors, disable button while submitting Styling: Tailwind CSS
```typescript
// RegistrationForm.tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useState } from 'react';
// Validation schema
const registrationSchema = z
.object({
name: z
.string()
.min(2, 'Name must be at least 2 characters')
.max(50, 'Name must be less than 50 characters'),
email: z
.string()
.email('Please enter a valid email address'),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/\d/, 'Password must contain at least one number'),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
});
type RegistrationFormData = z.infer<typeof registrationSchema>;
interface RegistrationFormProps {
onSubmit: (data: Omit<RegistrationFormData, 'confirmPassword'>) => Promise<void>;
}
export function RegistrationForm({ onSubmit }: RegistrationFormProps) {
const [submitError, setSubmitError] = useState<string | null>(null);
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<RegistrationFormData>({
resolver: zodResolver(registrationSchema),
mode: 'onBlur', // Validate on blur for better UX
});
const handleFormSubmit = async (data: RegistrationFormData) => {
setSubmitError(null);
try {
const { confirmPassword...submitData } = data;
await onSubmit(submitData);
} catch (error) {
setSubmitError(
error instanceof Error ? error.message : 'Registration failed. Please try again.'
);
}
};
return (
<form
onSubmit={handleSubmit(handleFormSubmit)}
className="space-y-6 max-w-md mx-auto"
noValidate
>
{/* Global error */}
{submitError && (
<div
className="p-4 bg-red-50 border border-red-200 rounded-md text-red-700 text-sm"
role="alert"
>
{submitError}
</div>
)}
{/* Name field */}
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
Full Name
</label>
<input
id="name"
type="text"
autoComplete="name"
{...register('name')}
className={`mt-1 block w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.name ? 'border-red-300' : 'border-gray-300'
}`}
aria-invalid={errors.name ? 'true' : 'false'}
aria-describedby={errors.name ? 'name-error' : undefined}
/>
{errors.name && (
<p id="name-error" className="mt-1 text-sm text-red-600" role="alert">
{errors.name.message}
</p>
)}
</div>
{/* Email field */}
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email Address
</label>
<input
id="email"
type="email"
autoComplete="email"
{...register('email')}
className={`mt-1 block w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.email ? 'border-red-300' : 'border-gray-300'
}`}
aria-invalid={errors.email ? 'true' : 'false'}
aria-describedby={errors.email ? 'email-error' : undefined}
/>
{errors.email && (
<p id="email-error" className="mt-1 text-sm text-red-600" role="alert">
{errors.email.message}
</p>
)}
</div>
{/* Password field */}
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Password
</label>
<input
id="password"
type="password"
autoComplete="new-password"
{...register('password')}
className={`mt-1 block w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.password ? 'border-red-300' : 'border-gray-300'
}`}
aria-invalid={errors.password ? 'true' : 'false'}
aria-describedby={errors.password ? 'password-error' : 'password-hint'}
/>
<p id="password-hint" className="mt-1 text-xs text-gray-500">
At least 8 characters with 1 number
</p>
{errors.password && (
<p id="password-error" className="mt-1 text-sm text-red-600" role="alert">
{errors.password.message}
</p>
)}
</div>
{/* Confirm Password field */}
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700">
Confirm Password
</label>
<input
id="confirmPassword"
type="password"
autoComplete="new-password"
{...register('confirmPassword')}
className={`mt-1 block w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 ${
errors.confirmPassword ? 'border-red-300' : 'border-gray-300'
}`}
aria-invalid={errors.confirmPassword ? 'true' : 'false'}
aria-describedby={errors.confirmPassword ? 'confirm-error' : undefined}
/>
{errors.confirmPassword && (
<p id="confirm-error" className="mt-1 text-sm text-red-600" role="alert">
{errors.confirmPassword.message}
</p>
)}
</div>
{/* Submit button */}
<button
type="submit"
disabled={isSubmitting}
className="w-full py-2 px-4 bg-blue-600 text-white font-medium rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{isSubmitting ? (
<span className="flex items-center justify-center gap-2">
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
Creating account...
</span>
) : (
'Create Account'
)}
</button>
</form>
);
}
```
### Usage
```typescript
import { RegistrationForm } from '@/components/RegistrationForm';
import { useRouter } from 'next/navigation';
export default function RegisterPage() {
const router = useRouter();
const handleRegister = async (data: { name: string; email: string; password: string }) => {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Registration failed');
}
router.push('/login?registered=true');
};
return (
<div className="min-h-screen flex items-center justify-center py-12 px-4">
<div className="w-full max-w-md">
<h1 className="text-2xl font-bold text-center mb-8">Create Your Account</h1>
<RegistrationForm onSubmit={handleRegister} />
</div>
</div>
);
}
```When building forms with validation, user input collection, or data entry interfaces
- •Use mode: 'onBlur' for better UX, validating on every keystroke is annoying
- •Always include aria-invalid and aria-describedby for accessibility
3. Data Fetching Component
Create a component that fetches and displays data: **Data type:** [WHAT DATA] **Framework:** [REACT / VUE / etc.] **Fetching method:** [REACT QUERY / SWR / USE EFFECT / SERVER COMPONENTS] **Features:** - Loading state: [HOW TO SHOW LOADING] - Error state: [HOW TO SHOW ERRORS] - Empty state: [WHEN NO DATA] - Pagination/Infinite scroll: [IF NEEDED] - Caching: [REQUIREMENTS] **API endpoint:** [ENDPOINT STRUCTURE] **Provide:** 1. Component code 2. Custom hook for data fetching 3. Loading/error/empty states 4. TypeScript types
Data: List of blog posts Framework: React with Next.js 14 App Router Fetching: React Query (TanStack Query) Features: Loading skeleton, error with retry, empty state, infinite scroll API: GET /api/posts?cursor=xxx&limit=10 Caching: 5 minute stale time
```typescript
// hooks/usePosts.ts
import { useInfiniteQuery } from '@tanstack/react-query';
interface Post {
id: string;
title: string;
excerpt: string;
author: { name: string; avatarUrl: string };
publishedAt: string;
readingTime: number;
}
interface PostsResponse {
posts: Post[];
nextCursor: string | null;
hasMore: boolean;
}
async function fetchPosts(cursor?: string): Promise<PostsResponse> {
const params = new URLSearchParams({ limit: '10' });
if (cursor) params.set('cursor', cursor);
const response = await fetch(`/api/posts?${params}`);
if (!response.ok) {
throw new Error('Failed to fetch posts');
}
return response.json();
}
export function usePosts() {
return useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam }) => fetchPosts(pageParam),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
```
```typescript
// components/PostList.tsx
'use client';
import { useEffect } from 'react';
import { useInView } from 'react-intersection-observer';
import { usePosts } from '@/hooks/usePosts';
import { PostCard, PostCardSkeleton } from './PostCard';
export function PostList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
isError,
error,
refetch,
} = usePosts();
// Infinite scroll trigger
const { ref: loadMoreRef, inView } = useInView({
threshold: 0,
rootMargin: '100px',
});
useEffect(() => {
if (inView && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
// Loading state
if (isLoading) {
return (
<div className="space-y-6">
{Array.from({ length: 5 }).map((_, i) => (
<PostCardSkeleton key={i} />
))}
</div>
);
}
// Error state
if (isError) {
return (
<div className="text-center py-12">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-red-100 mb-4">
<svg className="w-8 h-8 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">Failed to load posts</h3>
<p className="text-gray-500 mb-4">{error?.message || 'Something went wrong'}</p>
<button
onClick={() => refetch()}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
Try again
</button>
</div>
);
}
// Flatten pages into single array
const posts = data?.pages.flatMap((page) => page.posts) ?? [];
// Empty state
if (posts.length === 0) {
return (
<div className="text-center py-12">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-gray-100 mb-4">
<svg className="w-8 h-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z" />
</svg>
</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">No posts yet</h3>
<p className="text-gray-500">Check back later for new content.</p>
</div>
);
}
return (
<div className="space-y-6">
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
{/* Load more trigger */}
{hasNextPage && (
<div ref={loadMoreRef} className="py-4">
{isFetchingNextPage && (
<div className="space-y-6">
<PostCardSkeleton />
<PostCardSkeleton />
</div>
)}
</div>
)}
{/* End of list */}
{!hasNextPage && posts.length > 0 && (
<p className="text-center text-gray-500 py-4">
You've reached the end
</p>
)}
</div>
);
}
```
```typescript
// components/PostCard.tsx
import Link from 'next/link';
import { formatDistanceToNow } from 'date-fns';
interface Post {
id: string;
title: string;
excerpt: string;
author: { name: string; avatarUrl: string };
publishedAt: string;
readingTime: number;
}
export function PostCard({ post }: { post: Post }) {
return (
<article className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow">
<Link href={`/posts/${post.id}`} className="block">
<h2 className="text-xl font-semibold text-gray-900 mb-2 hover:text-blue-600">
{post.title}
</h2>
<p className="text-gray-600 mb-4 line-clamp-2">{post.excerpt}</p>
</Link>
<div className="flex items-center justify-between text-sm text-gray-500">
<div className="flex items-center gap-2">
<img
src={post.author.avatarUrl}
alt={post.author.name}
className="w-6 h-6 rounded-full"
/>
<span>{post.author.name}</span>
</div>
<div className="flex items-center gap-4">
<span>{formatDistanceToNow(new Date(post.publishedAt))} ago</span>
<span>{post.readingTime} min read</span>
</div>
</div>
</article>
);
}
export function PostCardSkeleton() {
return (
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 animate-pulse">
<div className="h-6 bg-gray-200 rounded w-3/4 mb-2" />
<div className="h-4 bg-gray-200 rounded w-full mb-1" />
<div className="h-4 bg-gray-200 rounded w-2/3 mb-4" />
<div className="flex justify-between">
<div className="flex items-center gap-2">
<div className="w-6 h-6 bg-gray-200 rounded-full" />
<div className="h-4 bg-gray-200 rounded w-24" />
</div>
<div className="h-4 bg-gray-200 rounded w-32" />
</div>
</div>
);
}
```When building components that fetch and display data from APIs with proper loading and error states
- •Use Intersection Observer for infinite scroll, more efficient than scroll event listeners
- •Always flatten paginated data before rendering to handle page boundaries correctly
4. Responsive Layout
Create a responsive layout component: **Layout type:** [DASHBOARD / LANDING PAGE / APP SHELL / etc.] **Framework:** [REACT / VUE / etc.] **Breakpoints:** - Mobile: [BEHAVIOR] - Tablet: [BEHAVIOR] - Desktop: [BEHAVIOR] **Sections:** - [SECTION 1 - e.g. navigation] - [SECTION 2 - e.g. sidebar] - [SECTION 3 - e.g. main content] **Features:** - [FEATURE 1 - e.g. collapsible sidebar] - [FEATURE 2 - e.g. mobile menu] **Styling:** [TAILWIND / CSS GRID / FLEXBOX] **Provide:** 1. Layout component 2. Responsive behavior 3. Navigation components 4. Mobile menu implementation
Layout: Dashboard with sidebar Framework: React Breakpoints: Mobile (stacked, hamburger menu), Tablet (collapsed sidebar icons), Desktop (full sidebar) Sections: Header, Sidebar navigation, Main content area Features: Collapsible sidebar, mobile drawer, active route highlighting Styling: Tailwind CSS
```typescript
// components/DashboardLayout.tsx
'use client';
import { useState, createContext, useContext } from 'react';
import { usePathname } from 'next/navigation';
import Link from 'next/link';
// Sidebar context for collapse state
const SidebarContext = createContext<{
isCollapsed: boolean;
isMobileOpen: boolean;
toggleCollapse: () => void;
toggleMobile: () => void;
}>(null!);
export function useSidebar() {
return useContext(SidebarContext);
}
interface DashboardLayoutProps {
children: React.ReactNode;
}
export function DashboardLayout({ children }: DashboardLayoutProps) {
const [isCollapsed, setIsCollapsed] = useState(false);
const [isMobileOpen, setIsMobileOpen] = useState(false);
return (
<SidebarContext.Provider
value={{
isCollapsed,
isMobileOpen,
toggleCollapse: () => setIsCollapsed(!isCollapsed),
toggleMobile: () => setIsMobileOpen(!isMobileOpen),
}}
>
<div className="min-h-screen bg-gray-50">
{/* Mobile overlay */}
{isMobileOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
onClick={() => setIsMobileOpen(false)}
/>
)}
{/* Sidebar */}
<Sidebar />
{/* Main content */}
<div
className={`transition-all duration-300 ${
isCollapsed ? 'lg:ml-20' : 'lg:ml-64'
}`}
>
<Header />
<main className="p-4 lg:p-8">{children}</main>
</div>
</div>
</SidebarContext.Provider>
);
}
// Navigation items
const navItems = [
{ href: '/dashboard', label: 'Dashboard', icon: HomeIcon },
{ href: '/dashboard/analytics', label: 'Analytics', icon: ChartIcon },
{ href: '/dashboard/users', label: 'Users', icon: UsersIcon },
{ href: '/dashboard/settings', label: 'Settings', icon: SettingsIcon },
];
function Sidebar() {
const pathname = usePathname();
const { isCollapsed, isMobileOpen, toggleMobile } = useSidebar();
return (
<aside
className={`
fixed top-0 left-0 z-50 h-full bg-white border-r border-gray-200
transition-all duration-300
/* Mobile: slide in from left */
${isMobileOpen ? 'translate-x-0' : '-translate-x-full'}
w-64
/* Desktop: always visible, collapse to icons */
lg:translate-x-0
${isCollapsed ? 'lg:w-20' : 'lg:w-64'}
`}
>
{/* Logo */}
<div className="h-16 flex items-center justify-between px-4 border-b border-gray-200">
<Link href="/dashboard" className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-600 rounded-lg" />
{!isCollapsed && (
<span className="font-semibold text-gray-900 hidden lg:block">AppName</span>
)}
</Link>
{/* Mobile close button */}
<button
onClick={toggleMobile}
className="lg:hidden p-2 text-gray-500 hover:text-gray-700"
aria-label="Close menu"
>
<XIcon className="w-5 h-5" />
</button>
</div>
{/* Navigation */}
<nav className="p-4 space-y-1">
{navItems.map((item) => {
const isActive = pathname === item.href;
return (
<Link
key={item.href}
href={item.href}
onClick={() => toggleMobile()}
className={`
flex items-center gap-3 px-3 py-2 rounded-lg transition-colors
${isActive
? 'bg-blue-50 text-blue-700'
: 'text-gray-700 hover:bg-gray-100'
}
`}
>
<item.icon className="w-5 h-5 flex-shrink-0" />
{(!isCollapsed || isMobileOpen) && (
<span className="truncate">{item.label}</span>
)}
</Link>
);
})}
</nav>
</aside>
);
}
function Header() {
const { toggleMobile, toggleCollapse, isCollapsed } = useSidebar();
return (
<header className="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-4 lg:px-8">
{/* Mobile menu button */}
<button
onClick={toggleMobile}
className="lg:hidden p-2 text-gray-500 hover:text-gray-700"
aria-label="Open menu"
>
<MenuIcon className="w-6 h-6" />
</button>
{/* Desktop collapse button */}
<button
onClick={toggleCollapse}
className="hidden lg:block p-2 text-gray-500 hover:text-gray-700"
aria-label={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
{isCollapsed ? (
<ChevronRightIcon className="w-5 h-5" />
) : (
<ChevronLeftIcon className="w-5 h-5" />
)}
</button>
{/* Right side - user menu, etc */}
<div className="flex items-center gap-4">
<button className="p-2 text-gray-500 hover:text-gray-700">
<BellIcon className="w-5 h-5" />
</button>
<div className="w-8 h-8 rounded-full bg-gray-200" />
</div>
</header>
);
}
// Icon components (simplified)
function HomeIcon({ className }: { className?: string }) {
return <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" /></svg>;
}
function ChartIcon({ className }: { className?: string }) {
return <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /></svg>;
}
function UsersIcon({ className }: { className?: string }) {
return <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" /></svg>;
}
function SettingsIcon({ className }: { className?: string }) {
return <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>;
}
function MenuIcon({ className }: { className?: string }) {
return <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" /></svg>;
}
function XIcon({ className }: { className?: string }) {
return <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>;
}
function ChevronLeftIcon({ className }: { className?: string }) {
return <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" /></svg>;
}
function ChevronRightIcon({ className }: { className?: string }) {
return <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /></svg>;
}
function BellIcon({ className }: { className?: string }) {
return <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0.538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" /></svg>;
}
```When building application shells, dashboard layouts, or any responsive multi-section layouts
- •Use CSS transitions for smooth sidebar collapse, transform is more performant than width
- •Close mobile menu on navigation to prevent confusion when page changes
Common Mistakes to Avoid
Not specifying the framework version, React 18 patterns differ from React 17
Forgetting accessibility requirements, keyboard navigation, screen readers, focus management
Not handling all states, loading, error, empty, and success states are all needed
Frequently Asked Questions
Frontend development creates the user-facing layer of applications, what users see and interact with. These prompts help you build components, handle state, implement responsive designs, and solve common UI challenges. The key is specifying your framework, design requirements, and interaction patterns for code that fits your project.
Related Templates
Code Review Prompt Templates
AI prompt templates for thorough code reviews. Get comprehensive feedback on code quality, security, and best practices.
Debugging Prompt Templates
AI prompt templates for debugging code. Identify issues, understand errors, and find solutions faster.
Code Documentation Prompt Templates
AI prompt templates for writing code documentation. Create clear comments, READMEs, and API docs.
Have your own prompt to optimize?