The Frontend: React, Tailwind, and Not Looking Like ChatGPT
This was my comfort zone. React, TypeScript, Tailwind — I know these tools. After a week of fighting Python, building the frontend felt like coming home.
But that doesn't mean it was easy. The challenge wasn't the technology. It was making a chat app that didn't look like every other AI chat interface on the internet.
The Problem with AI Chat UIs
Photo by Igor Omilaev on Unsplash
You know what I'm talking about. White background. Centered text box. Gray bubbles. Maybe a sidebar with conversation history. It's the ChatGPT layout, and every AI app copies it because it works. But when every app looks the same, none of them stand out.
I wanted Not Another Chatbot to feel different from the first pixel. Characters are the star of the show, so the UI needed to put them front and center.
Two Ways to Browse Characters
Photo by Brett Jordan on Unsplash
I built two browse modes because different users want different things:
The Carousel View — A large, swipeable carousel using Embla Carousel. Each character gets a full card with their image, name, and description. It's visual, it's tactile, it feels like browsing a collection rather than scrolling a list. This is the default landing experience.
The Grid View — A searchable, sortable grid for users who know what they want. Search by name, sort alphabetically or by popularity (usage count). Practical and fast.
Having both wasn't over-engineering — it was about giving users a choice between discovery and efficiency.
The Chat Interface
Photo by Levart_Photographer on Unsplash
The chat page is where the magic happens, and it's built from three main components:
- ChatInterface — The main panel. Character header at the top, scrollable message area in the middle, input at the bottom. Clean and focused.
- CharacterSidebar — A collapsible side panel showing character details and the LLM parameter sliders. This is where users can tune temperature, top-p, and other settings in real-time.
- MessageBubble — Renders each message with markdown support. Character messages and user messages have distinct styling so the conversation flows naturally.
The sidebar with parameter sliders was the component I had the most fun building. It turns a technical concept — LLM hyperparameters — into something tactile. Drag a slider, send a message, immediately see how the response changes. It makes the technology feel approachable.
Design Wasn't Planned — It Evolved
Photo by Brett Jordan on Unsplash
I'll be honest: I didn't start with a design system or a Figma file. The design evolved as I built. I'd add a component, look at it, decide it felt generic, and iterate until it had personality.
The dark theme came early. Light mode for a chat app with fictional characters felt too corporate — too LinkedIn. Dark mode felt more like a game, more immersive, more fun.
Animations came later. Framer Motion for route transitions. CSS keyframes for the typing indicator. Small details that make the app feel alive — the way messages fade in, the way the typing indicator pulses while waiting for the stream to start.
Every design decision came from the same question: does this feel like ChatGPT? If the answer was yes, I changed it.
The Stack That Made It Fast
Photo by Gabriel Salter on Unsplash
- shadcn/ui + Radix UI — Pre-built, accessible, customizable components. Not a component library you install — components you copy into your project and own. Perfect for someone who wants control without starting from scratch.
- Tailwind CSS v4 — Utility-first styling. I could iterate on design without context-switching between files. Change a class, see the result instantly.
- Vite — Fast dev server with HMR. The dev proxy to the FastAPI backend at localhost:8001 meant I could develop frontend and backend simultaneously without CORS issues.
- Framer Motion — Route transitions and micro-interactions. Small touch, big impact on how polished the app feels.
Responsive Design
Photo by Domenico Loia on Unsplash
The sidebar collapses on mobile. The carousel adapts to touch gestures. The grid reflows. None of this was glamorous work, but it mattered — a chat app that only works on desktop is half a chat app.
What I'd Change
Photo by Claudio Schwarz on Unsplash
The frontend is the part I'm most comfortable with, but it's also where I cut corners to ship fast. State management is local — no Redux, no Zustand. Each page manages its own state and fetches its own data. For a project this size, that's fine. For something bigger, it would become painful fast.
The Chat.tsx legacy component is still in the codebase — a simulated chat demo I built early for testing. It's not used in routing anymore. I should clean it up, but it's a reminder of how the project started: rough, experimental, throwaway code that became real.
Next up: the part of the project that almost broke me — deployment.