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

robot and human hands reaching toward ai text

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

a scrabbled scrabbled scrabbled scrabble

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

a computer screen with a bunch of buttons on it

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

brown wooden blocks on white table

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

a stack of books sitting on top of a wooden table

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

MacBook Pro on table beside white iMac and Magic Mouse

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

text

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.