Do you ever get tired of ads, email requests, and “are you a human?” CAPTCHAs when you’re just trying to find something simple online?
I sure do.
When I was building this blog, I was having a lot of fun adding my recipes! …but I kept running into the same annoying problem: Nutrition Labels.
It was surprisingly hard to find a site that would let me generate a nice, high-resolution nutrition label that I could actually screenshot or download without feeling like I was about to accidentally install a virus.
So… I built my own!
The code ended up being pretty simple, and now I have a little nutrition label generator that I can use whenever I need one for a recipe post.
Want to try it yourself? Head to the nutrition label generator and plug in your own numbers.

What It Does
You enter the nutrition information for a recipe (calories, fat, sodium, all the usual) and a nutrition label renders live next to the form.
I styled it to look like the familiar Nutrition Facts labels you see on packaged food. Once you’ve entered everything, you can simply screenshot the label and add it to your blog post.
Nothing fancy, but it saves me a surprising amount of time!
How Are the Daily Values Calculated
I had honestly never thought much about how the percentages on nutrition labels were calculated. It turns out the FDA provides standard Daily Values that can be used as reference points!
For the nutrients I’m including, I have an object like this:
const DailyValues = {
fat: 78, // g
satFat: 20, // g
cholesterol: 300, // mg
sodium: 2300, // mg
carbs: 275, // g
fiber: 28, // g
addedSugars: 50, // g
vitaminD: 20, // mcg
calcium: 1300, // mg
iron: 18, // mg
potassium: 4700, // mg
};
Then I use a small helper function to do the actual calculation:
function percentDailyValues(valueString, reference) {
const valueNumber = parseFloat(valueString);
if (!valueNumber || valueNumber <= 0) return null;
return Math.round((valueNumber / reference) * 100);
}
The value the user enters starts out as valueString, which is just text. I use parseFloat() to turn that text into a number called valueNumber.
If there isn’t a valid positive number, the function returns null. Otherwise, it calculates the percentage by dividing the entered amount by the corresponding Daily Value and multiplying by 100.
So, for example, if you enter 39 grams of fat:
39 ÷ 78 × 100 = 50%
And that’s the number that appears next to Fat on the label.
Matching the FDA Format
This was probably my favorite part: making the label actually look like a nutrition label!
This is where frontend development gets a little artistic and creative.
I built each section using <div> elements and used borderBottom with different thicknesses to recreate the lines on a real Nutrition Facts label.
For example, I wanted Nutrition Facts to be large and bold with the thickest line underneath it:
<div
style={{
fontSize: "30px",
fontWeight: 900,
lineHeight: "30px",
borderBottom: "10px solid #000",
paddingBottom: "2px",
}}
>
Nutrition Facts
</div>
Then, once I got to the individual nutrients, I made a reusable Row() function. It takes care of putting the nutrient name and amount on one side and the % Daily Value on the other.
This kept me from having to manually build every single row.
And how are the input boxes so perfectly shaped & spaced apart?
Every input shares one class, so they’re all identical by default:
const inputClass =
"w-full px-3 py-1.5 rounded-lg border text-sm focus:outline-none border-border bg-surface text-text";
And I used the grid wrapper to align them by pairs like so:
<div className="grid grid-cols-2 gap-4">
<Field label="Total Fat (g)" value={fat} onChange={setFat} />
<Field label="Saturated Fat (g)" value={satFat} onChange={setSatFat} />
</div>
How Does It Read Input?
For the form itself, I made a reusable Field() component.
Every time I use a Field, I give it its own state value and setter function. For example, the Protein field looks like this:
<Field
label="Protein (g)"
value={protein}
onChange={setProtein}
placeholder="3"
/>
The label is what appears above the input box. The value is tied to protein, which is the state variable holding whatever the user has typed so far. And placeholder is just an example number that disappears once you start typing.
The onChange prop is what connects the input to the React state:
function Field({ label, value, onChange, placeholder }) {
return (
<label className="block">
<span className={labelClass}>{label}</span>
<input
type="text"
inputMode="decimal"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className={inputClass}
/>
</label>
);
}
The important part is:
onChange={(e) => onChange(e.target.value)}
onChange is a browser event that fires whenever the input changes. I grab the text from the event with e.target.value and pass it to whichever setter function belongs to that particular field.
So the whole process is basically:
You type → the browser fires onChange → we grab the text → we call the field’s setter → React updates the state → the nutrition label re-renders.
And that’s pretty much it!
It’s a relatively small project, but it’s one of those little tools that makes my life easier every time I write a recipe post. Plus, it was a fun excuse to build something instead of searching for yet another website to do it for me.
Thanks for reading!
