All work
2026BOTHIn Progress

Foodie Praks

A food ordering website built with vanilla HTML, CSS, and JavaScript. No frameworks. Responsive across desktop, tablet, and mobile.

Foodie Praks

Stack

HTML5 · CSS3

Result

Responsive design

Year

2026

Status

In Progress

HighlightsResponsive designCSS Grid & FlexboxCustom animationsMobile-first

Hungry people are impatient. Design for that.

A food ordering interface is one of the most UX-sensitive surfaces in consumer software. Users are hungry, impatient, making decisions based on appetite. Visual hierarchy, load time, and interaction clarity all matter more than on a utility app.

This project was a focused exercise in frontend craft: build a food ordering UI that actually feels good to use, without a framework carrying the weight.

Just me, just vanilla

Design and engineering, sole contributor. I designed the interface and built it with vanilla HTML, CSS, and JavaScript. No frameworks, no component libraries. The constraint was intentional.

Two modes: browsing vs. knowing what you want

Food ordering has two distinct modes. Exploration (I don't know what I want yet) and retrieval (I know exactly what I want). Most food apps optimize for retrieval and neglect exploration. I wanted to design for both.

Visual hierarchy leads with large food photography. Category filters are persistent and accessible. Prices and add-to-cart actions are always visible without scrolling into a detail view.

Responsive layout uses CSS Grid for the menu layout and Flexbox for components. The layout reflows across breakpoints without media query hacks. Each breakpoint is a natural fit for the content, not a forced adjustment.

Micro-interactions cover hover states on cards, cart badge animation on add, and smooth category filter transitions. Built with CSS transitions and minimal JavaScript, keeping the page fast.

The visual design decisions

The food card is the atomic unit of the interface, and the decisions about what lives on it (and what doesn't) matter more than they might seem.

Each card shows the food image, the item name, the price, and an add-to-cart button. That's it. No ratings, no description, no "learn more." The choice is deliberate: if someone is in retrieval mode and already knows they want the butter chicken, they should be able to add it to the cart without navigating away from the menu. Every extra tap in a mobile ordering flow has a real cost. Detail pages are for exploration; the card is for action. The add button is always visible on the card so that the two modes can coexist: an explorer can read descriptions on a detail page, while someone who knows what they want never has to.

Color and typography were chosen to support appetite rather than work against it. Warm tones (ambers, deep reds, off-whites) are associated with food environments for good reason: they tend to activate appetite in a way that cool palettes don't. The typeface needed to hold up at small sizes because prices appear in a constrained space alongside the item name. A clean sans-serif with open numerals was the right call: legible at 13px, no squinting at whether that's a 3 or an 8.

For category filters, I chose pill-style horizontal scroll over a dropdown or sidebar tab approach. The reasoning is behavioral: people scan horizontally on their phones. A horizontal strip of pills maps to that scanning pattern directly. A dropdown hides the available categories behind an interaction, which adds friction and makes the category set feel opaque. A sidebar works on desktop but competes with the menu content on mobile. Pills are scannable, touch-friendly, and already a familiar pattern from every map app and social platform people use daily.

The empty state for filtered views was a real design decision, not an afterthought. When you tap a category filter and nothing matches, what you show matters. A blank white space reads as a broken page. Instead, the empty state includes a short message that names what happened ("No items in this category") and a visual prompt to try another filter. It acknowledges the user's action, explains the result, and gives them a clear next step. Empty states are the moments when apps feel either thoughtful or broken. This one needed to feel thoughtful.

Why I didn't reach for a framework

Frameworks abstract away the fundamentals in ways that can become a crutch. Building without React forces you to think carefully about DOM management, event delegation, and state. Skills that transfer directly to understanding what frameworks actually do. It also keeps the build zero-dependency and fast.

Third-party card components impose their own spacing, sizing, and breakpoint assumptions. Building the grid from scratch meant total control over food card proportions, which is critical when the images are the primary UI element.

What vanilla JS forced me to think about

Without a framework managing state, the cart is a plain JavaScript object held in memory. I wrote every piece of that logic: add an item, remove an item, increment a quantity, decrement it, recalculate the total, update the badge count on the cart icon. None of it is complicated, but doing it manually makes something very clear: this is exactly what a state management library does, just with more ceremony. For a project of this scope, the ceremony would have been overhead. Writing it by hand took maybe two hours and left me with a much cleaner mental model of what React's useState and Redux are actually solving at a larger scale.

Event delegation was another explicit choice. Rather than attaching a click listener to every add-to-cart button on every card (which would mean re-attaching listeners whenever the menu filters change) I attached one listener to the menu container and let it handle all add-to-cart clicks via event bubbling. It's more performant, it's simpler to maintain when cards are added or removed from the DOM, and it's one of those patterns that vanilla JS teaches you directly because you feel the cost of doing it wrong.

For theming, I used CSS custom properties rather than a preprocessor. No Sass, no PostCSS, no design token pipeline. Just --color-primary and --spacing-md at the :root level, referenced throughout the stylesheet. For a single-developer project with a fixed design, that's the right level of tooling. It's the same underlying mechanism that design token systems are built on. I just didn't need the abstraction layer on top of it.

What shipped

Fully responsive across mobile, tablet, and desktop. Smooth category filtering and cart interactions with no layout shift. Zero external dependencies: pure HTML5, CSS3, and JavaScript.

Cart state that survives a page refresh

The cart is currently ephemeral and resets on page reload. I'd add localStorage persistence so users don't lose their selection. I'd also prototype the mobile ordering flow more thoroughly. Desktop was the primary design surface and the mobile experience, while functional, would benefit from a mobile-first rethink of the checkout flow.

What I'd do differently

The mobile checkout flow deserves a dedicated design pass. Right now it's the desktop flow reflowed to a narrower viewport, which is functional but not optimized for how people actually order food on their phones. Mobile checkout has different thumb-reach constraints, a different relationship to the keyboard, and a different context (people ordering from their couch or standing in a kitchen). A proper mobile-first rethink of that flow would mean reconsidering the step structure, not just the layout.

The category filtering could be more useful. Currently it filters by cuisine type, which covers exploration well. But dietary restriction filtering (vegetarian, vegan, gluten-free) is a meaningfully different need that the current system doesn't address. Someone with a dietary restriction isn't browsing by cuisine; they're filtering for safety first and preference second. Those should be composable: I want vegetarian items in the Thai category.

I'd also add a search bar as a third mode alongside explore and retrieve. Typing "noodles" and getting live-filtered results is faster than scanning through categories when someone has a specific craving but isn't sure which category it falls under. Live filtering on a small dataset like a restaurant menu is a natural fit for vanilla JS: no API call needed, just filtering the in-memory menu array on every keystroke.

Built with

HTML5CSS3JavaScriptWeb DesignFrontendResponsive