ROI Calculator Page
v1.0.0Added in 1.8.0
An interactive ROI page: team, hourly cost, lost hours and replaced tools in, yearly savings, payback, a breakdown and a savings chart out.
The page is the calculator. The hero rises in once, then the inputs panel asks for the team size (a slider plus a number field with steppers), the average hourly cost (a currency field with presets), the hours lost each week to status updates, searching and tool switching (three sliders), the tools Acme replaces (toggle chips with per-seat prices) and the plan and billing (segmented controls); Reset to defaults puts them back. The results panel, sticky beside the inputs on wide screens, rolls the yearly savings, ROI and hours back to their new values on motion values, shows the payback in days, a stacked breakdown bar with an Acme price marker and a tools-vs-Acme comparison. Get the full report scrolls to the report form; Share this estimate copies a link with the inputs in the query string, which the page reads back on load. Below, a cumulative value vs. cost chart (12, 24 or 36 months) marks the break-even day; hover, tap or drag across it for a crosshair readout, or focus it and use Left / Right / Home / End (Escape clears). The report form checks the work email and company, then shows a drawn PDF preview with Report sent to. A before / after week calendar redraws from the inputs, the assumptions card edits every reduction, adoption, ramp-up and price (Reset assumptions), and customer proof, the FAQ and the closing band follow. On phones and tablets a summary bar pins to the bottom while the inputs are on screen; Details opens the results in a bottom sheet. Reduced motion keeps numbers and panels still.
Built with
The sections this page is made of. In the full-screen preview, hold Alt to outline each one on the page.
Add to a project
npx wingo-ui@latest add roi-calculator-pagePro item: the CLI needs a Pro account. See plans
Usage
// app/(marketing)/roi/page.tsx
"use client"
import Link from "next/link"
import { RoiCalculatorPage } from "@/components/templates/roi-calculator-page"
export default function RoiRoute() {
return (
<RoiCalculatorPage
linkComponent={Link}
ctaTone="brand"
onRequestReport={async (inputs, results, contact) => {
const response = await fetch("/api/roi-report", {
method: "POST",
body: JSON.stringify({ inputs, net: results.net, ...contact }),
})
if (!response.ok) throw new Error("Could not send the report")
}}
/>
)
}Props
designdefault"classic""classic" | "bold" | "minimal"The whole page's look. classic: a dotted glow behind a start-aligned hero and raised panels; bold: a pill header, a giant title on a gradient mesh, an inverted results panel, rounder panels, FAQ cards, an image band and a wordmark footer; minimal: a transparent header, a centered hero, no glow, outlined flat panels, no gray bands and a simple footer. faqProps and ctaProps still win.
inputsRoiInputsControlled calculator values: { teamSize, hourlyCost, tools, statusHours, searchHours, switchHours, plan, billing }. Use with onInputsChange.
defaultInputsdefault"ROI_DEFAULT_INPUTS"Partial<RoiInputs>The values on load and after Reset to defaults: 40 people, $55 an hour, four tools, 2.5 / 3 / 2 hours, Business billed yearly.
onInputsChange(inputs: RoiInputs) => voidFires on every edit, including the values read from a share link.
assumptionsRoiAssumptionsControlled assumptions; use with onAssumptionsChange.
defaultAssumptionsdefault"ROI_DEFAULT_ASSUMPTIONS"Partial<RoiAssumptions>Reductions (40 / 30 / 20%), adoption 80%, half of the hours counted as value, 46 weeks, a 3-month ramp, 2 setup hours and the plan prices.
onAssumptionsChange(assumptions: RoiAssumptions) => voidFires when the assumptions card is edited or reset.
toolsdefault"ROI_TOOLS"RoiTool[]The tools Acme replaces: { id, name, description, price (per seat per month), icon? }.
customersdefault"ROI_CUSTOMERS"RoiCustomer[]Proof cards: { company, icon?, industry, metric, metricLabel, quote, name, role, detail }.
faqdefault"ROI_FAQ"FaqItem[]{ question, answer } pairs about the method.
labelsdefault"DEFAULT_ROI_CALCULATOR_PAGE_LABELS"Partial<RoiCalculatorPageLabels>Every visible string; the ones with numbers in them are functions.
hrefsdefault"DEFAULT_ROI_CALCULATOR_PAGE_HREFS"Partial<RoiCalculatorPageHrefs>{ announcement, signUp, sales, privacy, customers }.
maxTeamdefault500numberThe team size slider's end and the largest value a share link may set.
defaultHorizondefault"36"12 | 24 | 36The chart's time range on load, in months (a number; the playground passes it as text).
readSharedInputsdefaulttruebooleanReads team, rate, tools, status, search, switch, plan and billing from the query string after mount.
showAnnouncementdefaulttruebooleanThe strip above the header bar.
showTimelinedefaulttruebooleanThe savings over time chart (and the report form beside it).
showReportdefaulttruebooleanThe Get the full report form and the buttons that lead to it.
showWeekdefaulttruebooleanThe before / after week calendars.
showAssumptionsdefaulttruebooleanThe method, the editable assumptions and the survey footnote.
showCustomersdefaulttruebooleanThe customer proof cards.
showFaqdefaulttruebooleanThe FAQ.
showCtadefaulttruebooleanThe closing call to action.
showSharedefaulttruebooleanThe Share this estimate button.
showSummaryBardefaulttruebooleanThe bottom summary bar below 1024px and its results sheet.
ctaVariantdefault"inverted""inverted" | "soft" | "split"The closing band: a solid band, a gray band, or copy beside the buttons on a raised panel. Applies to the classic design.
alternatedefaulttruebooleanGray bands on every other section. Applies to the classic and bold designs.
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Accents: the hero badge, the glow, sliders, segmented thumbs and selected tool chips.
ctaTonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Get the full report, Email me the report, Details, the header's Get started and the closing band; brand is the marketing orange.
radiusdefault"md""none" | "sm" | "md" | "lg" | "full"The corners of the form fields.
headerdefaulttruebooleanThe marketing header; off inside a site layout.
footerdefaulttruebooleanThe marketing footer with its newsletter form.
headerPropsPartial<MarketingHeaderProps>Passed to MarketingHeader.
footerPropsPartial<MarketingFooterProps>Passed to MarketingFooter.
faqPropsPartial<FaqSectionProps>Overrides for the FaqSection.
ctaPropsPartial<CtaSectionProps>Overrides for the CtaSection.
onRequestReport(inputs: RoiInputs, results: RoiResult, contact: { email, company }) => (void | Promise<unknown>)The report form. A promise keeps the button loading and a rejection shows an error. Without it the form waits 900 ms and shows the sent state with a drawn PDF preview.
onShare(url: string, inputs: RoiInputs) => voidThe share link was built and copied (or the copy failed).
onSubscribe(email: string) => (void | Promise<unknown>)The footer newsletter; a rejected promise shows an error.
animateIndefaulttruebooleanThe hero rises in once and numbers roll to new values; false keeps the page still (thumbnails). Reduced motion always does.
localedefault"en-US"stringIntl locale for money, hours and percentages.
currencydefault"USD"stringISO currency of the hourly cost, tool prices and results (amounts are not converted).
linkComponentdefault"<a>"(props: LinkComponentProps) => ReactNodeThe router link for every link on the page (next/link in an app).
classNamestringClasses for the root.
classNamesPartial<Record<RoiCalculatorPageSlot, string>>Classes for header, hero, title, inputs, results, timeline, chart, report, week, assumptions, customers, customer, faq, cta, summary and footer.
Parts
computeRoi
functionThe pure estimate: (inputs, assumptions = ROI_DEFAULT_ASSUMPTIONS, tools = ROI_TOOLS) => RoiResult with hours back, time value, tool savings, Acme cost, net yearly savings, ROI %, the multiple, payback days (day by day, with ramp-up, setup and billing days) and a 36-month cumulative timeline.
encodeRoiInputs
functionThe share link's query values for some inputs: { team, rate, tools, status, search, switch, plan, billing }.
decodeRoiInputs
function(params, tools?, maxTeam?) => the valid inputs found in a query string; unknown tools are dropped and numbers clamped.
ROI_SHARE_KEYS
constantThe query keys the share link uses.
ROI_CALCULATOR_SAMPLE
constantAll the sample content in one object: tools, inputs, assumptions, customers and faq.
ROI_DEFAULT_INPUTS
constantThe calculator's starting values.
ROI_DEFAULT_ASSUMPTIONS
constantThe survey-based assumptions and the plan prices (Pro $12 / $10, Business $24 / $20 per seat per month).
ROI_LIMITS
constant{ maxTeam: 500, maxHourly: 400, maxHours: 10 }.
ROI_TOOLS
constantSeven generic tools with typical per-seat prices: task tracker, docs editor, team wiki, automation tool, status meetings app, roadmap tool and video updates.
ROI_CUSTOMERS
constantNorthwind, Umbrella Health and Stark Logistics proof cards.
ROI_FAQ
constantSix questions about the defaults, the value share, hourly cost, payback, renewals and quotes.
DEFAULT_ROI_CALCULATOR_PAGE_LABELS
constantThe English strings.
DEFAULT_ROI_CALCULATOR_PAGE_HREFS
constantThe default link targets.
ROI_CALCULATOR_PAGE_DESIGNS
constantThe design names, classic first: ["classic", "bold", "minimal"].
RoiCalculatorPageProps
typeProps of RoiCalculatorPage.
RoiCalculatorPageDesign
type"classic" | "bold" | "minimal".
RoiInputs
typeThe calculator's values.
RoiAssumptions
typeThe editable assumptions and prices.
RoiResult
typeWhat computeRoi returns.
RoiTool
typeOne tool Acme replaces.
RoiCustomer
typeOne proof card.
RoiReportContact
type{ email, company } from the report form.
RoiCalculatorPageLabels
typeThe strings labels can override.
RoiCalculatorPageHrefs
typeThe link targets hrefs can override.
Examples
Bold design
design="bold": a giant title on a gradient mesh, an inverted results panel, rounder panels, FAQ cards and a wordmark footer.
Minimal design
design="minimal": a transparent header, a centered hero, outlined flat panels, no gray bands and a simple footer.
Small agency in brand orange
defaultInputs for 8 people on Pro, billed monthly, with tone and ctaTone brand and a 12-month chart.
Euro pricing, embedded
locale de-DE and currency EUR without the header and footer, as the page would sit inside an existing site.
Mailer down
An onRequestReport that rejects: fill in the report form and submit to see the error while the values stay in place.
Dependencies
Files
- components/templates/roi-calculator-page.tsx
- components/templates/roi-calculator-page-data.ts