The Frontend Tech Stack
Meet Your Toolkit
Open interactive version (quiz + challenge)Real-world analogy
Building a frontend is like throwing a party. React is the venue, Vite is the fast setup crew, Tailwind is the decorator, React Router is the floor plan, React Query is the caterer who brings food from the kitchen (backend), and Mantine/Radix are the pre-made party supplies!
What is it?
A modern React frontend uses multiple libraries working together: Vite for building, Tailwind for styling, React Query for data fetching, React Hook Form + Zod for forms, and component libraries for pre-built UI elements.
Real-world relevance
This exact tech stack (React + Vite + Tailwind + React Query + Zod) is used by thousands of production apps. It's considered one of the best modern React setups.
Key points
- React + Vite — React is the UI library that builds your interface from reusable components, while Vite is the lightning-fast build tool with instant hot module replacement. Together they form the foundation of modern frontend development — save a file and see changes in milliseconds instead of waiting seconds for webpack.
- Tailwind CSS — Style your components with utility class names like 'bg-blue-500 text-white p-4 rounded-lg' directly in your JSX. No separate CSS files to maintain and no naming conflicts between components. Tailwind only includes the classes you actually use in production, keeping your CSS bundle tiny.
- React Query (TanStack) — TanStack Query handles all server state management: fetching, caching, background refetching, and error handling. Instead of writing useState plus useEffect plus loading state boilerplate, one useQuery call gives you everything. It also deduplicates identical requests made by multiple components.
- React Hook Form + Zod — React Hook Form manages form state with minimal re-renders using uncontrolled inputs and refs for high performance. Zod provides schema-based validation with full TypeScript type inference. Together they create forms where validation rules are defined once and enforced consistently across your application.
- React Router — React Router enables client-side navigation between pages without full browser reloads. Define routes declaratively, link between them with the Link component, and extract URL parameters with useParams. Navigation feels instant because only the changed component re-renders while the shell stays in place.
- Zustand or Redux — When multiple components need to share the same data (like user authentication state or a shopping cart), global state management tools step in. Zustand is lightweight with minimal boilerplate — great for most apps. Redux is more structured with actions and reducers for complex enterprise state logic.
- Component Libraries — Pre-built UI libraries like Radix UI, shadcn/ui, or Mantine provide professionally designed, fully accessible components: buttons, modals, dropdowns, tabs, and more. They handle keyboard navigation, screen readers, and edge cases so you can focus on building your app's unique features instead.
- Storybook for Component Development — Storybook lets you develop and test UI components in isolation outside your main application. Each component gets a visual story showing its different states: loading, error, empty, and filled. It serves as living documentation that stays up to date and makes it easy to review UI changes in pull requests.
Code example
// React Query — fetch data the smart way 🧠
import { useQuery } from '@tanstack/react-query';
function UserList() {
const { data, isLoading, error } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
staleTime: 5 * 60 * 1000, // cache for 5 minutes!
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Oops! Something broke 💥</p>;
return (
<ul className="space-y-2">
{data.map(user => (
<li key={user.id} className="bg-white p-4 rounded-lg shadow">
{user.name}
</li>
))}
</ul>
);
}
// React Hook Form + Zod — bulletproof forms 🛡️
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email('Invalid email!'),
password: z.string().min(8, 'Too short!'),
});
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input {...register('password')} type="password" />
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">Login</button>
</form>
);
}
// React Router — navigate like a pro 🗺️
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
</BrowserRouter>
);
}
// Zustand — global state 🌍
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
}));
function Counter() {
const { count, increment, decrement } = useStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}Line-by-line walkthrough
- 1. React Query — fetch data the smart way 🧠
- 2. Importing required dependencies
- 3.
- 4. Declaring a function
- 5. Declaring a variable
- 6.
- 7.
- 8.
- 9.
- 10.
- 11. Conditional check
- 12. Conditional check
- 13.
- 14. Returning a value
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22. Closing expression
- 23. Closing block
- 24.
- 25. React Hook Form + Zod — bulletproof forms 🛡️
- 26. Importing required dependencies
- 27. Importing required dependencies
- 28. Importing required dependencies
- 29.
- 30. Declaring a variable
- 31.
- 32.
- 33.
- 34.
- 35. Declaring a function
- 36. Declaring a variable
- 37.
- 38.
- 39.
- 40. Returning a value
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48. Closing expression
- 49. Closing block
- 50.
- 51. React Router — navigate like a pro 🗺️
- 52. Importing required dependencies
- 53.
- 54. Declaring a function
- 55. Returning a value
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67. Closing expression
- 68. Closing block
- 69.
- 70. Zustand — global state 🌍
- 71. Importing required dependencies
- 72.
- 73. Declaring a variable
- 74.
- 75.
- 76.
- 77.
- 78.
- 79. Declaring a function
- 80. Declaring a variable
- 81. Returning a value
- 82.
- 83.
- 84.
- 85.
- 86.
- 87. Closing expression
- 88. Closing block
Spot the bug
const schema = z.object({
email: z.string().email(),
age: z.string().min(18),
});Need a hint?
Look at the type used for 'age' and what min() means for strings...
Show answer
Age should be z.number().min(18), not z.string().min(18). For strings, .min(18) checks string LENGTH (18+ characters), not the numeric value. Fix: change z.string().min(18) to z.number().min(18).
Explain like I'm 5
Building a website is like throwing a birthday party! React is the room, Vite sets up super fast, Tailwind decorates everything pretty, React Router is the map of rooms, and React Query is the person who brings snacks from the kitchen whenever you're hungry!
Fun fact
Vite (pronounced 'veet') means 'fast' in French. And it IS fast — 10-100x faster than the old create-react-app setup. The creator, Evan You, also created Vue.js! 🏎️
Hands-on challenge
Build a 'Pokemon Card Viewer' — fetch data from pokeapi.co and display a card with the Pokemon's name, image, types, and stats. Add a search bar that filters by name, a loading skeleton, and an error state that shows a funny 'Pokemon escaped!' message.
More resources
- Vite Getting Started (Vite Official)
- Tailwind CSS Documentation (Tailwind CSS Official)
- Vite in 100 Seconds (Fireship)