04
Frontend
React, Next.js, Tailwind, shadcn/ui. How I build interfaces without knowing CSS. Real examples from my projects.
ReactNext.jsTailwind
What even IS a frontend?
Let me start from the very beginning. The frontend is what the user sees and clicks. When you open a website and see buttons, text, images, tables, forms — that is all frontend. The colors, the layout, the animations, the way a dropdown menu opens — frontend. Think of it this way: if you can see it in your browser, someone built it as frontend code. The backend is invisible. The frontend is everything visible.
Let me start from the very beginning. The frontend is what the user sees and clicks. When you open a website and see buttons, text, images, tables, forms — that is all frontend. The colors, the layout, the animations, the way a dropdown menu opens — frontend. Think of it this way: if you can see it in your browser, someone built it as frontend code. The backend is invisible. The frontend is everything visible.
Why I can build beautiful interfaces without knowing CSS
I do not know CSS. I cannot center a div from memory. I have no idea what flexbox properties do without looking them up. And yet I have built complex user interfaces with sortable tables, analytics dashboards, multi-step forms, and responsive layouts. The secret is simple: I use shadcn/ui and Tailwind CSS, and I describe what I want to see instead of how to code it. Claude handles the implementation. I handle knowing what the user needs.
I do not know CSS. I cannot center a div from memory. I have no idea what flexbox properties do without looking them up. And yet I have built complex user interfaces with sortable tables, analytics dashboards, multi-step forms, and responsive layouts. The secret is simple: I use shadcn/ui and Tailwind CSS, and I describe what I want to see instead of how to code it. Claude handles the implementation. I handle knowing what the user needs.
React explained simply: everything is a component
React is the most popular way to build frontends. The core idea is dead simple: everything is a component. A button is a component. A table is a component. A sidebar is a component. A whole page is a component made of smaller components.
Think of it like LEGO. You build small pieces (a button, a card, a header), and then you snap them together to make bigger things (a dashboard page). Once you build a component, you can reuse it anywhere. Built a nice-looking card component? Use it on the dashboard, on the settings page, in a modal — anywhere.
You do not need to understand React deeply to start. After your third or fourth project, these concepts click naturally because you have seen them used in context dozens of times.
React is the most popular way to build frontends. The core idea is dead simple: everything is a component. A button is a component. A table is a component. A sidebar is a component. A whole page is a component made of smaller components.
Think of it like LEGO. You build small pieces (a button, a card, a header), and then you snap them together to make bigger things (a dashboard page). Once you build a component, you can reuse it anywhere. Built a nice-looking card component? Use it on the dashboard, on the settings page, in a modal — anywhere.
You do not need to understand React deeply to start. After your third or fourth project, these concepts click naturally because you have seen them used in context dozens of times.
Props and State — the only two concepts you really need
Props are data you pass into a component from the outside. Like telling a card component: here is the title, here is the description, here is the image. The card does not decide what to show — it receives that information.
State is data that can change. When you click a button and something changes on the page — a counter goes up, a modal opens, a tab switches — that is state. The page is reacting to a change. That is literally why it is called React.
These two concepts cover about 80% of what you need to know. Props go in, state changes, the screen updates. That is React.
Props are data you pass into a component from the outside. Like telling a card component: here is the title, here is the description, here is the image. The card does not decide what to show — it receives that information.
State is data that can change. When you click a button and something changes on the page — a counter goes up, a modal opens, a tab switches — that is state. The page is reacting to a change. That is literally why it is called React.
These two concepts cover about 80% of what you need to know. Props go in, state changes, the screen updates. That is React.
shadcn/ui — why it is amazing
Imagine you need a professional-looking data table, or a dropdown menu, or a date picker. You could build these from scratch, which would take hours. Or you could use shadcn/ui — a library of pre-built components that look professional out of the box. Buttons, tables, modals, dropdowns, tabs, cards, dialogs, tooltips, date pickers, forms — all ready to use.
The best part? You just tell Claude to use them. "I need a data table with sortable columns using shadcn/ui." Done. "Add a dropdown menu for user actions — edit, delete, duplicate." Done. The components look clean, they are accessible, they follow modern design patterns. I never had to become a designer because shadcn/ui does the design part for me.
Imagine you need a professional-looking data table, or a dropdown menu, or a date picker. You could build these from scratch, which would take hours. Or you could use shadcn/ui — a library of pre-built components that look professional out of the box. Buttons, tables, modals, dropdowns, tabs, cards, dialogs, tooltips, date pickers, forms — all ready to use.
The best part? You just tell Claude to use them. "I need a data table with sortable columns using shadcn/ui." Done. "Add a dropdown menu for user actions — edit, delete, duplicate." Done. The components look clean, they are accessible, they follow modern design patterns. I never had to become a designer because shadcn/ui does the design part for me.
Tailwind CSS — styling without writing CSS files
Traditional CSS means writing styles in separate files and connecting them to your HTML. Tailwind flips this: you describe styles right on the element using class names. Instead of writing CSS, you write things like
I do not write CSS. I do not have CSS files. I describe what something should look like — "white card with rounded corners and a subtle shadow, some padding" — and Claude translates that into Tailwind classes. Or I just say "make it look like a professional dashboard card" and Claude picks the right combination.
Some common Tailwind patterns you will see everywhere:
-
-
-
-
-
-
Traditional CSS means writing styles in separate files and connecting them to your HTML. Tailwind flips this: you describe styles right on the element using class names. Instead of writing CSS, you write things like
className="bg-white p-4 rounded-lg shadow" — and that gives you a white background, some padding, rounded corners, and a shadow.I do not write CSS. I do not have CSS files. I describe what something should look like — "white card with rounded corners and a subtle shadow, some padding" — and Claude translates that into Tailwind classes. Or I just say "make it look like a professional dashboard card" and Claude picks the right combination.
Some common Tailwind patterns you will see everywhere:
-
p-4 = padding-
mb-6 = margin bottom-
text-lg font-bold = larger bold text-
bg-blue-500 text-white = blue background, white text-
rounded-lg shadow = rounded corners with shadow-
flex items-center justify-between = horizontal layout, centered, spaced outNext.js — pages are just files
Next.js is the framework I use for React projects. The most beautiful thing about it: pages are just files. Put a file at
Layouts that wrap multiple pages go in
No configuration. No router setup. No route definitions. Files equal pages. That is the entire routing system, and it is incredible.
Next.js is the framework I use for React projects. The most beautiful thing about it: pages are just files. Put a file at
app/dashboard/page.tsx and you have a /dashboard page. Put a file at app/settings/page.tsx and you have a /settings page. Put a file at app/campaigns/[id]/page.tsx and you have dynamic pages like /campaigns/123, /campaigns/456, etc.Layouts that wrap multiple pages go in
layout.tsx files. Want a sidebar that appears on every page inside /dashboard? Create app/dashboard/layout.tsx with the sidebar, and every page inside that folder automatically gets it.No configuration. No router setup. No route definitions. Files equal pages. That is the entire routing system, and it is incredible.
Real example: How I built the SMTPCloud dashboard page
Here is what I actually told Claude when I needed the main dashboard page for SMTPCloud:
"I need a dashboard page that shows email sending stats for the logged-in user. At the top, show 4 stat cards: total emails sent, delivery rate percentage, open rate percentage, and bounce rate. Below that, show a line chart of emails sent per day for the last 30 days. Below the chart, show a table of recent campaigns with columns for name, status, sent count, open rate, and date. Use shadcn/ui components and Recharts for the chart."
That single prompt produced a complete dashboard page. Cards with numbers and trend indicators, a responsive line chart, and a data table with status badges. One prompt, one page. Then I iterated: "Add a date range picker to filter the chart" and "Make the campaign names clickable, linking to the campaign detail page." Three prompts total for a professional dashboard.
Here is what I actually told Claude when I needed the main dashboard page for SMTPCloud:
"I need a dashboard page that shows email sending stats for the logged-in user. At the top, show 4 stat cards: total emails sent, delivery rate percentage, open rate percentage, and bounce rate. Below that, show a line chart of emails sent per day for the last 30 days. Below the chart, show a table of recent campaigns with columns for name, status, sent count, open rate, and date. Use shadcn/ui components and Recharts for the chart."
That single prompt produced a complete dashboard page. Cards with numbers and trend indicators, a responsive line chart, and a data table with status badges. One prompt, one page. Then I iterated: "Add a date range picker to filter the chart" and "Make the campaign names clickable, linking to the campaign detail page." Three prompts total for a professional dashboard.
TanStack Query — data fetching that just works
Your frontend needs to get data from your backend API. You could write fetch calls manually and manage loading states, error states, caching, and refetching yourself. Or you could use TanStack Query (formerly React Query) and let it handle all of that.
I told Claude: "I need to fetch the list of campaigns from my API at /api/campaigns. It should show a loading spinner while it loads, cache the data so it does not refetch every time I navigate back, and show an error message if the request fails."
TanStack Query handles all of that — caching, background refetching, loading states, error states, retries. Claude sets it up and I get a smooth user experience without managing any of that complexity manually. The user sees a spinner, then the data appears. If they navigate away and come back, it shows the cached data instantly and quietly refetches in the background. Magic.
Your frontend needs to get data from your backend API. You could write fetch calls manually and manage loading states, error states, caching, and refetching yourself. Or you could use TanStack Query (formerly React Query) and let it handle all of that.
I told Claude: "I need to fetch the list of campaigns from my API at /api/campaigns. It should show a loading spinner while it loads, cache the data so it does not refetch every time I navigate back, and show an error message if the request fails."
TanStack Query handles all of that — caching, background refetching, loading states, error states, retries. Claude sets it up and I get a smooth user experience without managing any of that complexity manually. The user sees a spinner, then the data appears. If they navigate away and come back, it shows the cached data instantly and quietly refetches in the background. Magic.
State management — when things change on the page
When you click a button and something changes on the page, that is state management. A simple example: you have a list of campaigns, and the user clicks "Delete" on one. The list needs to update. That is state changing.
For simple things, React has built-in state with
But here is the truth: I have never had to think deeply about state management. I describe what should happen — "when the user clicks delete, remove the campaign from the list and show a success toast" — and Claude picks the right approach. Sometimes it is
When you click a button and something changes on the page, that is state management. A simple example: you have a list of campaigns, and the user clicks "Delete" on one. The list needs to update. That is state changing.
For simple things, React has built-in state with
useState. For complex things — like managing which filters are active, what page of results you are on, whether a modal is open — you might need something more organized.But here is the truth: I have never had to think deeply about state management. I describe what should happen — "when the user clicks delete, remove the campaign from the list and show a success toast" — and Claude picks the right approach. Sometimes it is
useState, sometimes it is TanStack Query cache invalidation, sometimes it is a context provider. The tool choices do not matter. The behavior does.Forms — React Hook Form + Zod validation
Forms are everywhere in web apps. Login forms, campaign creation forms, settings forms, contact forms. I use React Hook Form for handling form logic and Zod for validation rules.
I describe what fields I need and what rules they should follow: "Create a campaign form with: name (required, max 100 characters), subject line (required), sender email (must be a valid email), scheduled date (optional, must be in the future), and a rich text editor for the email body."
Claude sets up the form with proper validation, error messages that appear under each field, and a submit handler that sends the data to the API. If the user forgets a required field or enters an invalid email, they see a clear error message immediately — no page reload, no confusing behavior.
Forms are everywhere in web apps. Login forms, campaign creation forms, settings forms, contact forms. I use React Hook Form for handling form logic and Zod for validation rules.
I describe what fields I need and what rules they should follow: "Create a campaign form with: name (required, max 100 characters), subject line (required), sender email (must be a valid email), scheduled date (optional, must be in the future), and a rich text editor for the email body."
Claude sets up the form with proper validation, error messages that appear under each field, and a submit handler that sends the data to the API. If the user forgets a required field or enters an invalid email, they see a clear error message immediately — no page reload, no confusing behavior.
Building a card component — a real walkthrough
Let me show you how simple building a component can be. Say you need a stats card that shows a metric — like "Emails Sent: 12,345" with a trend indicator.
I tell Claude: "Build a StatsCard component that takes a title, value, and change percentage. Show the title in small gray text, the value in large bold text, and the change percentage in green if positive or red if negative. Use shadcn/ui Card and Tailwind."
Claude builds it. Now I can use that component anywhere:
-
-
One component, reused across the whole dashboard. That is the power of React components — build once, use everywhere.
Let me show you how simple building a component can be. Say you need a stats card that shows a metric — like "Emails Sent: 12,345" with a trend indicator.
I tell Claude: "Build a StatsCard component that takes a title, value, and change percentage. Show the title in small gray text, the value in large bold text, and the change percentage in green if positive or red if negative. Use shadcn/ui Card and Tailwind."
Claude builds it. Now I can use that component anywhere:
-
-
One component, reused across the whole dashboard. That is the power of React components — build once, use everywhere.
Charts and graphs with Recharts
Every dashboard needs charts. I use Recharts — a charting library for React. I said "show me a line chart of emails sent per day for the last 30 days" and Claude built this whole analytics section with a responsive chart, tooltips on hover, and proper axis labels.
Need a bar chart comparing campaign performance? "Show a bar chart with campaign names on the X axis and open rates on the Y axis, color the bars based on whether they are above or below the average." Need a pie chart showing email status breakdown? Just describe it.
The key insight: you describe what data to visualize and how, not the chart configuration. Claude handles the Recharts API, the data formatting, the responsive container, the color schemes. You handle knowing what metrics matter.
Every dashboard needs charts. I use Recharts — a charting library for React. I said "show me a line chart of emails sent per day for the last 30 days" and Claude built this whole analytics section with a responsive chart, tooltips on hover, and proper axis labels.
Need a bar chart comparing campaign performance? "Show a bar chart with campaign names on the X axis and open rates on the Y axis, color the bars based on whether they are above or below the average." Need a pie chart showing email status breakdown? Just describe it.
The key insight: you describe what data to visualize and how, not the chart configuration. Claude handles the Recharts API, the data formatting, the responsive container, the color schemes. You handle knowing what metrics matter.
The SMTPCloud Dashboard frontend: 149 TSX files
Let me give you real scale so you understand what is possible. The SMTPCloud Dashboard has 149 TSX files. That is 149 React components and pages. But each one is a small, manageable piece:
- Campaign list page
- Campaign detail page
- Campaign creation wizard (multi-step form)
- Recipient management
- Analytics dashboard with multiple chart types
- Domain management and DNS verification
- API key generation and management
- User settings and profile
- Billing and subscription pages
- Admin panel with user management
149 files sounds massive, but remember — each file is one component or one page. A button component might be 30 lines. A table component might be 80 lines. A full page might be 150 lines. You build them one at a time, and they add up into a complete application.
Let me give you real scale so you understand what is possible. The SMTPCloud Dashboard has 149 TSX files. That is 149 React components and pages. But each one is a small, manageable piece:
- Campaign list page
- Campaign detail page
- Campaign creation wizard (multi-step form)
- Recipient management
- Analytics dashboard with multiple chart types
- Domain management and DNS verification
- API key generation and management
- User settings and profile
- Billing and subscription pages
- Admin panel with user management
149 files sounds massive, but remember — each file is one component or one page. A button component might be 30 lines. A table component might be 80 lines. A full page might be 150 lines. You build them one at a time, and they add up into a complete application.
Quality Monitoring — a different frontend approach
Quality Monitoring uses React 18 with Vite instead of Next.js, and it has a feature-based architecture. There are separate folders for dashboard, issues, team management, admin panel, returns tracking, and insights. Each feature has its own components, hooks, and utilities.
The dashboard alone has multiple chart types, filterable tables, date range pickers, and role-based views that show different data depending on whether you are an admin, team lead, or agent. An admin sees everything. A team lead sees their team. An agent sees their own metrics. Same dashboard component, different data based on who is logged in.
Quality Monitoring uses React 18 with Vite instead of Next.js, and it has a feature-based architecture. There are separate folders for dashboard, issues, team management, admin panel, returns tracking, and insights. Each feature has its own components, hooks, and utilities.
The dashboard alone has multiple chart types, filterable tables, date range pickers, and role-based views that show different data depending on whether you are an admin, team lead, or agent. An admin sees everything. A team lead sees their team. An agent sees their own metrics. Same dashboard component, different data based on who is logged in.
Responsive design: looks good on phone AND desktop
Making your app look good on phones, tablets, and desktops is handled by Tailwind utility classes. Classes prefixed with
One column on phone. Two on tablet. Three on desktop. One line of code.
Claude handles this automatically when I say "make it responsive" or "it should work well on mobile too." I have never manually written a media query in my life. Tailwind's responsive prefixes handle everything.
Making your app look good on phones, tablets, and desktops is handled by Tailwind utility classes. Classes prefixed with
md: apply only on medium screens and up, lg: on large screens. So a grid that shows one column on mobile and three on desktop is:className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"One column on phone. Two on tablet. Three on desktop. One line of code.
Claude handles this automatically when I say "make it responsive" or "it should work well on mobile too." I have never manually written a media query in my life. Tailwind's responsive prefixes handle everything.
The real skill: describing what you want to see
The real skill in frontend development with AI is describing what you want to see, not how to code it. Instead of saying "create a React component with useState and useEffect that fetches data and maps over it to render table rows," I say:
"I want a table that shows campaign stats — name, status, sent count, open rate. It should be sortable by clicking column headers. Add a search box at the top that filters by campaign name. Show a loading skeleton while the data loads. If there are no campaigns, show an empty state with an illustration and a 'Create Campaign' button."
Claude knows the implementation details. You know what the user needs to see. That division of labor is everything.
The real skill in frontend development with AI is describing what you want to see, not how to code it. Instead of saying "create a React component with useState and useEffect that fetches data and maps over it to render table rows," I say:
"I want a table that shows campaign stats — name, status, sent count, open rate. It should be sortable by clicking column headers. Add a search box at the top that filters by campaign name. Show a loading skeleton while the data loads. If there are no campaigns, show an empty state with an illustration and a 'Create Campaign' button."
Claude knows the implementation details. You know what the user needs to see. That division of labor is everything.
Common frontend mistakes I learned the hard way
After building 20+ projects, here are the mistakes I see (and made myself):
1. Not thinking mobile-first — always check how it looks on a phone. Most people forget this until someone complains.
2. Too many API calls — if your page makes 15 separate API requests on load, it will be slow. Ask Claude to batch or combine them.
3. Not handling loading states — users stare at a blank screen and think it is broken. Always show a spinner or skeleton.
4. Not handling error states — the API will fail sometimes. Show a friendly error message, not a white screen.
5. Building the perfect UI upfront — build the ugly version first. Get the data showing, get the interactions working. Then polish.
6. Ignoring accessibility — shadcn/ui handles a lot of this for you, but always make sure buttons are clickable and text is readable.
The fix for all of these is the same: be explicit with Claude. "Add a loading skeleton. Add an error state with a retry button. Make sure it works on mobile. Combine these three API calls into one."
After building 20+ projects, here are the mistakes I see (and made myself):
1. Not thinking mobile-first — always check how it looks on a phone. Most people forget this until someone complains.
2. Too many API calls — if your page makes 15 separate API requests on load, it will be slow. Ask Claude to batch or combine them.
3. Not handling loading states — users stare at a blank screen and think it is broken. Always show a spinner or skeleton.
4. Not handling error states — the API will fail sometimes. Show a friendly error message, not a white screen.
5. Building the perfect UI upfront — build the ugly version first. Get the data showing, get the interactions working. Then polish.
6. Ignoring accessibility — shadcn/ui handles a lot of this for you, but always make sure buttons are clickable and text is readable.
The fix for all of these is the same: be explicit with Claude. "Add a loading skeleton. Add an error state with a retry button. Make sure it works on mobile. Combine these three API calls into one."
Build ugly first, polish later
This deserves its own section because it is that important. Do not try to design the perfect UI upfront. Build the ugly version first. Get the data showing, get the interactions working, make sure the page does what it needs to do.
Then tell Claude: "This works but it looks rough. Clean up the layout, add proper spacing, make the table look professional, add hover effects on the rows, and put a nice header at the top."
Polishing an existing working page is ten times easier than trying to build a perfect page from scratch. Get it working, then make it pretty. Every single time.
This deserves its own section because it is that important. Do not try to design the perfect UI upfront. Build the ugly version first. Get the data showing, get the interactions working, make sure the page does what it needs to do.
Then tell Claude: "This works but it looks rough. Clean up the layout, add proper spacing, make the table look professional, add hover effects on the rows, and put a nice header at the top."
Polishing an existing working page is ten times easier than trying to build a perfect page from scratch. Get it working, then make it pretty. Every single time.