Short answer: a peptide blend calculator is a single-peptide calculator run once per component, with one shared diluent volume. Each component's concentration is its own mass ÷ the shared volume, and any volume you draw carries every component in a ratio that was fixed when the vial was made. The arithmetic is simple. The bugs live in the inputs: labels that don't say whether "10 mg" means each component or the total, "total" vs "per-component" targets, mixed units, and IU entries with no conversion factor.
Disclosure: I'm affiliated with Peptide Library, which is published by Superfime LLC and has a blend mode in its web calculator. This post is about modelling and validating the math, and the code is standalone. It's for research and lab recordkeeping only. It is not medical advice, and no number here is a suggested amount of anything for anyone.
Why blends deserve their own post
A single-peptide reconstitution calculator has one mass, one volume and one target. A blend vial has N masses, one volume, and a target that can mean two different things. Search behaviour shows people want this specifically: in Ahrefs' US data (pulled 13 November 2026), "peptide calculator for blends" shows about 800 monthly searches and "peptide blend calculator" about 600 (KD 25). Many calculators that rank for those terms either don't support more than one component or quietly treat the blend as one substance. Both give wrong per-component figures.
The model
Say a vial contains components A, B, … with masses m_A, m_B, … (mg), and diluent volume V (mL) is added.
| Quantity | Formula |
|---|---|
| Concentration of component i | c_i = m_i ÷ V (mg/mL) |
| Total concentration | c_total = Σ m_i ÷ V |
| Ratio (fixed) | m_A : m_B : … (independent of V) |
| Component i in a draw of v mL | c_i × v |
| U-100 syringe units for v mL | v × 100 |
Two consequences matter for UI design:
- Water changes concentrations, never the ratio. Doubling V halves every c_i, but A:B stays the same. A calculator should show the ratio as a fixed property of the vial, not something the user can edit after mixing.
- You can't target one component without moving the others. Pick a volume to hit a quantity of B, and A comes along in proportion. The calculator should always display every component for any computed draw, never only the one the user asked about.
A worked example (hypothetical lab numbers, arithmetic only)
Hypothetical test vial: A = 6 mg, B = 2 mg, diluent V = 2 mL.
- c_A = 6 ÷ 2 = 3 mg/mL; c_B = 2 ÷ 2 = 1 mg/mL; c_total = 4 mg/mL; ratio A:B = 3:1.
- A 0.10 mL draw (10 units on a U-100 scale) contains 0.30 mg (300 mcg) of A and 0.10 mg (100 mcg) of B.
Now the two target modes, using a test input of 400 mcg:
- Total mode ("400 mcg of blend"): v = 0.4 mg ÷ 4 mg/mL = 0.10 mL. Split by ratio: 300 mcg A + 100 mcg B.
- Per-component mode ("400 mcg of B"): v = 0.4 mg ÷ 1 mg/mL = 0.40 mL, which also carries 1.2 mg of A.
Same input, a 4× difference in volume. That's why the mode must be an explicit, visible choice. Peptide Library's blend tool, for example, has a "Total dose / Per component" toggle. In your own build, never default it silently.
The label problem
Before any arithmetic, a blend calculator needs a clean answer to: is the labelled mass per component or total? A "10 mg" two-component vial could hold 10 mg of each (20 mg total) or 10 mg total (perhaps 5 + 5, perhaps not). Vendors aren't consistent, and Peptide Library's FAQ on how blend labels can mislead makes the same point. The calculator can't resolve it. It can refuse to guess: require per-component masses, or require the user to pick "each" vs "total" plus a ratio.
A typed implementation
Dependency-free TypeScript. Everything runs internally in micrograms (1 mg = 1,000 mcg by SI prefix definition; see NIST's prefix table). Every result also returns warnings.
type MassUnit = "mcg" | "mg" | "iu"; interface Component { name: string; amount: number; // as labelled unit: MassUnit; mcgPerIU?: number; // required if unit === "iu"; substance-specific } interface BlendInput { components: Component[]; diluentMl: number; } type Target = | { mode: "total"; mcg: number } | { mode: "component"; name: string; mcg: number }; const MAX_COMPONENTS = 8; function toMcg(c: Component): number { if (c.unit === "mcg") return c.amount; if (c.unit === "mg") return c.amount * 1000; if (!c.mcgPerIU || c.mcgPerIU <= 0) { throw new Error(`${c.name}: IU needs a substance-specific mcg-per-IU factor`); } return c.amount * c.mcgPerIU; } export function validate(input: BlendInput): string[] { const errors: string[] = []; const n = input.components.length; if (n < 2) errors.push("A blend needs at least 2 components; use single mode."); if (n > MAX_COMPONENTS) errors.push(`At most ${MAX_COMPONENTS} components.`); const names = input.components.map(c => c.name.trim().toLowerCase()); if (new Set(names).size !== names.length) errors.push("Duplicate component names."); for (const c of input.components) { if (!Number.isFinite(c.amount) || c.amount <= 0) errors.push(`${c.name}: amount must be > 0.`); if (c.unit === "iu" && !(c.mcgPerIU! > 0)) errors.push(`${c.name}: IU without a conversion factor.`); } if (!Number.isFinite(input.diluentMl) || input.diluentMl <= 0) errors.push("Diluent volume must be > 0 mL."); return errors; } export function concentrations(input: BlendInput) { const errs = validate(input); if (errs.length) throw new Error(errs.join(" ")); const masses = input.components.map(c => ({ name: c.name, mcg: toMcg(c) })); const totalMcg = masses.reduce((s, m) => s + m.mcg, 0); return { totalMcgPerMl: totalMcg / input.diluentMl, perComponent: masses.map(m => ({ name: m.name, mcgPerMl: m.mcg / input.diluentMl, fraction: m.mcg / totalMcg, // the fixed ratio })), }; } export function draw(input: BlendInput, target: Target) { const conc = concentrations(input); const warnings: string[] = []; let ml: number; if (target.mode === "total") { ml = target.mcg / conc.totalMcgPerMl; } else { const c = conc.perComponent.find(p => p.name === target.name); if (!c) throw new Error(`Unknown component ${target.name}`); ml = target.mcg / c.mcgPerMl; } if (ml > input.diluentMl) warnings.push("Requested volume exceeds the vial's contents."); const units = ml * 100; // U-100 scale if (Math.abs(units - Math.round(units)) > 1e-9) { warnings.push(`${units.toFixed(2)} units falls between whole-unit marks.`); } return { ml, units, contains: conc.perComponent.map(p => ({ name: p.name, mcg: p.mcgPerMl * ml })), warnings, }; } Design choices worth copying:
-
containsalways lists every component. A UI that shows only the targeted component hides the most important fact about a blend. -
fractioncomes from mass, not from user input. If users can type a ratio and masses separately, the two will eventually disagree. - IU is per component. One blend can mix IU-labelled and mass-labelled components, and each IU needs its own factor. Never apply one global factor.
- Round only at display time. Compute in full precision and format at the end, so rounding errors don't compound across components.
Test vectors
These are hypothetical arithmetic checks, not amounts anyone should use.
| Case | Input | Expected |
|---|---|---|
| Ratio is volume-independent | A 6 mg, B 2 mg; V = 1, 2, 4 mL | fraction A = 0.75 every time |
| Total mode | same vial, V = 2 mL, total 400 mcg | 0.10 mL, 10 units; A 300 mcg, B 100 mcg |
| Component mode | same vial, V = 2 mL, B 400 mcg | 0.40 mL; A 1,200 mcg |
| Unit mixing | A 6 mg + B 2,000 mcg, V = 2 mL | identical to A 6 mg + B 2 mg |
| Between marks | A 5 mg, B 5 mg, V = 3 mL, total 250 mcg | 0.075 mL, 7.5 units → warning |
| Over-draw | V = 1 mL, request needing 1.2 mL | warning, not a silent cap |
| Bad input | 1 component, 0 mL, IU without factor, duplicate names | rejected with a reason each |
If your total-mode and component-mode tests pass for the same vial but the "Ratio is volume-independent" test fails, you're probably recomputing the fraction from rounded concentrations. Use masses.
What the math does not tell you
A calculator assumes every component dissolves fully, the labelled masses are accurate, and nothing degrades. Those are lab questions, not arithmetic ones. Whether two peptides are chemically compatible and stable in one solution (pH, aggregation, degradation over time) has to be established empirically for that combination, and per-component identity and purity testing is a separate concern from the math. A blend calculator also says nothing about clinical evidence. Laboratory data on a component, a combination's popularity, and human clinical evidence are different things. For many combinations sold as blends, no published human study of the combination exists.
Where to try it
If you want to check your implementation against an existing tool, Peptide Library's peptide research calculator has Single, Blend and Cost modes in the browser. Blend mode handles up to eight components with a total vs per-component toggle (on the website, blend mode is part of the Pro plan). Its two-component blend page shows a per-component concentration table at several diluent volumes. That table makes a handy cross-check for the "ratio is volume-independent" test, and the page deliberately shows concentrations only, with no dose table.
Takeaways
- Model a blend as N masses sharing one volume. Concentrations are per component, and the ratio is fixed at mixing.
- Make "total" vs "per component" an explicit choice, because it can change the computed volume by a large factor.
- Refuse ambiguous inputs (each vs total labelling, IU without a factor, one-component "blends") instead of guessing.
- Always show every component in the result.
Research and educational use only. Not medical advice. Nothing in this post is a recommended amount, schedule or use of any substance. Affiliation: Peptide Library is published by Superfime LLC.


