
A few months ago, I got tired of clicking through Subway’s website to figure out what I was actually eating. Their PDFs are fine if you want to look up one thing. But if you want to build a custom sandwich—add this bread, skip that cheese, double the chicken, then see how it fits into your daily calories good luck.
So, I built this calorie calculator.
Not a fancy app with a backend. Just a single HTML file with a ton of JavaScript, a massive JSON‑like data structure, and a stubborn refusal to let a bad UI ruin my lunch.
I’m going to walk you through how I built it, what broke along the way, and what I’d do differently next time.
The Problem
You’d have to manually add bread calories + meat calories + veggie calories (most are zero, but olives and avocado aren’t) + sauce calories. Then double it for footlong. Then remember that cheese adds fat and sodium. Then realize you forgot the salt and pepper.
It’s tedious and error‑prone. And it’s exactly the kind of problem a simple web tool can solve.
Step 1: Collecting the Data
I needed a complete, consistent dataset. Every bread, every protein, every cheese, vegetable, condiment, seasoning, side, soup, dessert, and pre‑made sandwich. Plus, salads, wraps, protein pockets, and No Bready Bowls.
I started with Subway’s official US nutrition PDF (2026 version). Then I cross‑referenced with their online menu, third‑party aggregators, and even some store‑level ingredient sheets.
Total items ended up at around 300+.
Here’s the rough breakdown:
- Breads: 12
- Pre‑made sandwiches: 30+
- Proteins (a la carte): 20+
- Cheeses: 5
- Vegetables: 15
- Condiments: 25+ (each with Normal and Light versions)
- Seasonings: 3
- Sides: 2
- Salads: 25+
- Wraps: 25+
- No Bready Bowls: 25+
- Protein Pockets: 4
- Soups: 3
- Desserts: 7
- Sidekicks: 8 Each item needed the same set of nutrition fields:
Calories, total fat, saturated fat, trans fat, cholesterol, sodium, Total carbs, dietary fiber, sugars, added sugars, Protein, Vitamin A (mcg), Vitamin C (mg), Calcium (mg), Iron (mg).
I also tracked serving size in grams where available, though that’s mostly for reference.
What I learned: Never trust a single source. Cross‑reference everything and if you’re building a public tool, be transparent about where the data comes from and what’s estimated.
Step 2: Structuring the Data – The JavaScript Object That Runs Everything
I built one massive JavaScript object called subwayMenu. It has arrays for each category: breads, proteins, cheeses, vegetables, condiments, etc.
Here’s what a single bread item looks like:
javascript
{
id: 'artisan-italian-bread-6inch',
name: '6" Artisan Italian Bread',
servingSize_g: 71,
calories: 210,
totalFat: 2,
saturatedFat: 1,
transFat: 0,
cholesterol: 0,
sodium: 380,
totalCarbs: 39,
dietaryFiber: 1,
sugars: 3,
addedSugars: 2,
protein: 8,
vitaminA_mcg: 0,
vitaminC_mg: 0,
calcium_mg: 1040,
iron_mg: 16.2,
category: 'bread'
}
Every item follows the same structure. That’s critical because later I loop through these objects to calculate totals, display values, and filter by search.
The size multiplier logic:
For sandwiches, users can choose 6‑inch or footlong. The calculator multiplies bread, proteins, cheeses, vegetables, condiments, and seasonings by 2 when footlong is selected. Pre‑made sandwiches, salads, wraps, and bowls are already sized – they don’t get multiplied again. That logic lives in the calculateTotalNutrition function, where I check a sizeAffected flag for each category.
The quantity system:
Users can add multiple of the same item (e.g., double meat, extra cheese). I store quantities in a separate currentSelection object. For each category, it’s a dictionary mapping item IDs to integers. Zero means not selected.`
javascript
let currentSelection = {
sandwichSize: '6inch',
bread: null,
proteins: {},
cheeses: {},
vegetables: {},
condiments: {},
// ... etc
};
This structure makes it easy to add, remove, or adjust quantities without rebuilding the whole UI from scratch.
Step 3: The UI – Dropdowns, Tabs, and a Whole Lot of Dynamic Rendering
I wanted the interface to be clean but powerful. No external libraries just plain HTML, CSS, and vanilla JavaScript.
The layout:
Left side: Builder with collapsible categories (Bread, Proteins, Cheeses, etc.) and a tab switcher for different menu types (Sandwich, Salads, Wraps, etc.)
Right side: Results section with a nutrition label, calorie progress bar, current selection list, and action buttons.
The tab system:
On mobile, tabs become a dropdown toggle. On desktop, they could have been buttons, but I kept the dropdown consistent across all devices to simplify CSS. The active tab determines which set of categories (sandwich builder vs. pre‑made salads vs. wraps) is visible.
Collapsible categories:
Each category (e.g., “Proteins”) is a
Search inside categories:
Every category has a search input that filters items client‑side. No backend calls. The subwaySearchCategory function reads the input, filters the items array, and calls populateCategory to re‑render only that section.
The item list:
Each item is rendered as a row with a checkbox (or radio for bread), name, calories, and quantity controls (plus/minus buttons). The quantity buttons call subwayAdjustQuantity, which updates the currentSelection object and re‑renders the category and the current selection list.
Why re‑render instead of DOM manipulation?
Because it’s simpler, I rebuild the category HTML every time something changes. For a category with 20 items, that’s fine. For the whole page, I’d need to be more careful. But here, re‑rendering is fast enough that users don’t notice a delay.
Step 4: The Nutrition Calculation
The calculateTotalNutrition function is the heart of the tool.
It does four things:
Initializes a totals object with zeros for every nutrition field.
Applies the size multiplier (2 for footlong) to categories that scale with sandwich size.
Loops through every selected category – bread, proteins, cheeses, vegetables, condiments, etc. And adds the item’s nutrition multiplied by quantity and multiplier.
Returns the final totals and a list of ingredients (for the ingredients section).
The math is straightforward addition. No fancy rounding until the end, where I round to one decimal place to keep the display clean.
The tricky part:
Some items have tiny values (e.g., 0.5g fiber). When you multiply by quantity and size, you get fractions. I store everything as numbers and only round when displaying. That keeps internal calculations accurate.
The FDA daily values:
I hardcoded a fdaDailyValues object based on a 2,000‑calorie diet. The nutrition label shows % Daily Value for each nutrient. I calculate that as (nutrient_value / fda_daily_value) * 100. For vitamins and minerals, I use the actual mcg/mg values and compare against the FDA standards.
Step 5: The Current Selection Panel
One thing I’ve always hated about other calculators: you add items, but you can’t see what you’ve added without scrolling back up.
So, I added a “Current Selection” panel on the right side (bottom in mobile). It lists every item you’ve chosen, with quantity and calories. And here’s the kicker: each item has a small “X” button that removes it directly from the selection.
That removal calls subwayRemoveItemFromSelection, which updates the currentSelection object, then re‑renders the affected category (to uncheck the item) and the selection panel itself.
It’s a small quality‑of‑life feature, but it makes a huge difference when you’re tweaking a meal.
Step 6: The Save Function
I added a “Save Nutrition Data” button that exports everything as a plain text file.
The subwaySaveNutritionInfo function:
Calls calculateTotalNutrition to get current totals and ingredients.
Builds a formatted string with meal details, ingredients, nutrition facts, and daily progress.
Creates a Blob, generates a download link, and triggers a download.
Shows a temporary “Saved!” feedback on the button.
I chose plain text because it’s simple, works everywhere, and users can paste it into a note or spreadsheet. No external dependencies.
What Broke (And How I Fixed It)
1. The bread radio group:
Originally, I used radio buttons (only one bread allowed). But dynamically re‑rendering radios with JavaScript and keeping their state was a nightmare. I switched to checkboxes but enforced “only one” in the selection logic. The UI still looks like radios, but under the hood, it’s a checkbox group with manual deselection.
2. The footlong multiplier:
At first, I applied the multiplier to everything including pre‑made salads and wraps. That was wrong, Salads and wraps have their own fixed nutrition. I had to add a sizeAffected flag to each category and only multiply bread, proteins, cheeses, vegetables, condiments, and seasonings.
3. Search resetting on item change:
Every time a user added or removed an item, the category would re‑render and lose the search term. I fixed that by storing the search term in a variable and re‑applying it after re‑render. The subwaySearchCategory function now caches the filtered results.
4. The save button not working in some browsers:
Some browsers block programmatic clicks on download links. I switched to using a Blob and URL.createObjectURL, which works everywhere. Also added a fallback alert if something fails.
The Bottom Line
Building this calculator was a lesson in data wrangling, UI design for real‑world users, and the joy of solving your own problem.
It’s not perfect and the code is a little messy. The data could be more complete, but it works. You can build a custom Subway meal, see exactly what you’re eating, and save it for later.
If you’re a developer thinking about building a similar tool for another restaurant chain, here’s my advice:
Start with the data: Clean, consistent data is 80% of the work.
Build the logic first then the UI: Get the calculation engine working in the console before you worry about buttons.
Test with real users: I handed this to a few friends and watched where they got confused. That’s how I learned about the footlong multiplier bug.
Don’t overcomplicate it: You don’t need a backend, a database, or a build system. A single HTML file with inline CSS and JS is fine for a tool like this.
Now, whenever I walk into Subway, I know exactly what I’m ordering. And if I’m not sure, I open this calculator on my phone and build it before I get to the counter.
That’s the whole point, tools should make your life easier, not harder.
I hope this walkthrough helps someone else build something useful. And if you just wanted to use the calculator, well, now you know what’s under the hood.


Top comments (0)