# ROI Calculator Page

> An interactive ROI page: team, hourly cost, lost hours and replaced tools in, yearly savings, payback, a breakdown and a savings chart out.

- Type: React template (Templates)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add roi-calculator-page`
- Docs: https://wingo-ui.com/templates/roi-calculator-page

## Interaction

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.

## Installation

```bash
npx wingo-ui@latest add roi-calculator-page
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/roi-calculator-page` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/templates/roi-calculator-page.tsx`
- `components/templates/roi-calculator-page-data.ts`

npm dependencies: `npm i lucide-react motion d3-shape`

Wingo UI dependencies (installed automatically):

- [Marketing Section](https://wingo-ui.com/components/marketing-section) ([Markdown](https://wingo-ui.com/components/marketing-section.md))
- [Marketing Header](https://wingo-ui.com/components/marketing-header) ([Markdown](https://wingo-ui.com/components/marketing-header.md))
- [Marketing Footer](https://wingo-ui.com/components/marketing-footer) ([Markdown](https://wingo-ui.com/components/marketing-footer.md))
- [FAQ Section](https://wingo-ui.com/components/faq-section) ([Markdown](https://wingo-ui.com/components/faq-section.md))
- [CTA Section](https://wingo-ui.com/components/cta-section) ([Markdown](https://wingo-ui.com/components/cta-section.md))
- [Avatar](https://wingo-ui.com/components/avatar) ([Markdown](https://wingo-ui.com/components/avatar.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Currency Input](https://wingo-ui.com/components/currency-input) ([Markdown](https://wingo-ui.com/components/currency-input.md))
- [Drawer](https://wingo-ui.com/components/drawer) ([Markdown](https://wingo-ui.com/components/drawer.md))
- [Field](https://wingo-ui.com/components/field) ([Markdown](https://wingo-ui.com/components/field.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))
- [Number Input](https://wingo-ui.com/components/number-input) ([Markdown](https://wingo-ui.com/components/number-input.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Slider](https://wingo-ui.com/components/slider) ([Markdown](https://wingo-ui.com/components/slider.md))
- [Chart kit](https://wingo-ui.com/components/chart-kit) ([Markdown](https://wingo-ui.com/components/chart-kit.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// 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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `design?` | `"classic" \| "bold" \| "minimal"` | `"classic"` | 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. |
| `inputs?` | `RoiInputs` |  | Controlled calculator values: { teamSize, hourlyCost, tools, statusHours, searchHours, switchHours, plan, billing }. Use with onInputsChange. |
| `defaultInputs?` | `Partial<RoiInputs>` | `"ROI_DEFAULT_INPUTS"` | 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) => void` |  | Fires on every edit, including the values read from a share link. |
| `assumptions?` | `RoiAssumptions` |  | Controlled assumptions; use with onAssumptionsChange. |
| `defaultAssumptions?` | `Partial<RoiAssumptions>` | `"ROI_DEFAULT_ASSUMPTIONS"` | 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) => void` |  | Fires when the assumptions card is edited or reset. |
| `tools?` | `RoiTool[]` | `"ROI_TOOLS"` | The tools Acme replaces: { id, name, description, price (per seat per month), icon? }. |
| `customers?` | `RoiCustomer[]` | `"ROI_CUSTOMERS"` | Proof cards: { company, icon?, industry, metric, metricLabel, quote, name, role, detail }. |
| `faq?` | `FaqItem[]` | `"ROI_FAQ"` | { question, answer } pairs about the method. |
| `labels?` | `Partial<RoiCalculatorPageLabels>` | `"DEFAULT_ROI_CALCULATOR_PAGE_LABELS"` | Every visible string; the ones with numbers in them are functions. |
| `hrefs?` | `Partial<RoiCalculatorPageHrefs>` | `"DEFAULT_ROI_CALCULATOR_PAGE_HREFS"` | { announcement, signUp, sales, privacy, customers }. |
| `maxTeam?` | `number` | `500` | The team size slider's end and the largest value a share link may set. |
| `defaultHorizon?` | `12 \| 24 \| 36` | `"36"` | The chart's time range on load, in months (a number; the playground passes it as text). |
| `readSharedInputs?` | `boolean` | `true` | Reads team, rate, tools, status, search, switch, plan and billing from the query string after mount. |
| `showAnnouncement?` | `boolean` | `true` | The strip above the header bar. |
| `showTimeline?` | `boolean` | `true` | The savings over time chart (and the report form beside it). |
| `showReport?` | `boolean` | `true` | The Get the full report form and the buttons that lead to it. |
| `showWeek?` | `boolean` | `true` | The before / after week calendars. |
| `showAssumptions?` | `boolean` | `true` | The method, the editable assumptions and the survey footnote. |
| `showCustomers?` | `boolean` | `true` | The customer proof cards. |
| `showFaq?` | `boolean` | `true` | The FAQ. |
| `showCta?` | `boolean` | `true` | The closing call to action. |
| `showShare?` | `boolean` | `true` | The Share this estimate button. |
| `showSummaryBar?` | `boolean` | `true` | The bottom summary bar below 1024px and its results sheet. |
| `ctaVariant?` | `"inverted" \| "soft" \| "split"` | `"inverted"` | The closing band: a solid band, a gray band, or copy beside the buttons on a raised panel. Applies to the classic design. |
| `alternate?` | `boolean` | `true` | Gray bands on every other section. Applies to the classic and bold designs. |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Accents: the hero badge, the glow, sliders, segmented thumbs and selected tool chips. |
| `ctaTone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Get the full report, Email me the report, Details, the header's Get started and the closing band; brand is the marketing orange. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | The corners of the form fields. |
| `header?` | `boolean` | `true` | The marketing header; off inside a site layout. |
| `footer?` | `boolean` | `true` | The marketing footer with its newsletter form. |
| `headerProps?` | `Partial<MarketingHeaderProps>` |  | Passed to MarketingHeader. |
| `footerProps?` | `Partial<MarketingFooterProps>` |  | Passed to MarketingFooter. |
| `faqProps?` | `Partial<FaqSectionProps>` |  | Overrides for the FaqSection. |
| `ctaProps?` | `Partial<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) => void` |  | The 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. |
| `animateIn?` | `boolean` | `true` | The hero rises in once and numbers roll to new values; false keeps the page still (thumbnails). Reduced motion always does. |
| `locale?` | `string` | `"en-US"` | Intl locale for money, hours and percentages. |
| `currency?` | `string` | `"USD"` | ISO currency of the hourly cost, tool prices and results (amounts are not converted). |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` | `"<a>"` | The router link for every link on the page (next/link in an app). |
| `className?` | `string` |  | Classes for the root. |
| `classNames?` | `Partial<Record<RoiCalculatorPageSlot, string>>` |  | Classes for header, hero, title, inputs, results, timeline, chart, report, week, assumptions, customers, customer, faq, cta, summary and footer. |

## Parts

### computeRoi (function)

The 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 (function)

The 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 (constant)

The query keys the share link uses.

### ROI_CALCULATOR_SAMPLE (constant)

All the sample content in one object: tools, inputs, assumptions, customers and faq.

### ROI_DEFAULT_INPUTS (constant)

The calculator's starting values.

### ROI_DEFAULT_ASSUMPTIONS (constant)

The 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 (constant)

Seven 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 (constant)

Northwind, Umbrella Health and Stark Logistics proof cards.

### ROI_FAQ (constant)

Six questions about the defaults, the value share, hourly cost, payback, renewals and quotes.

### DEFAULT_ROI_CALCULATOR_PAGE_LABELS (constant)

The English strings.

### DEFAULT_ROI_CALCULATOR_PAGE_HREFS (constant)

The default link targets.

### ROI_CALCULATOR_PAGE_DESIGNS (constant)

The design names, classic first: ["classic", "bold", "minimal"].

### RoiCalculatorPageProps (type)

Props of RoiCalculatorPage.

### RoiCalculatorPageDesign (type)

"classic" | "bold" | "minimal".

### RoiInputs (type)

The calculator's values.

### RoiAssumptions (type)

The editable assumptions and prices.

### RoiResult (type)

What computeRoi returns.

### RoiTool (type)

One tool Acme replaces.

### RoiCustomer (type)

One proof card.

### RoiReportContact (type)

{ email, company } from the report form.

### RoiCalculatorPageLabels (type)

The strings labels can override.

### RoiCalculatorPageHrefs (type)

The 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.

## See also

- [Pricing Page](https://wingo-ui.com/components/pricing-page) ([Markdown](https://wingo-ui.com/components/pricing-page.md))
- [Startups Page](https://wingo-ui.com/templates/startups-page) ([Markdown](https://wingo-ui.com/templates/startups-page.md))
- [Enterprise Page](https://wingo-ui.com/templates/enterprise-page) ([Markdown](https://wingo-ui.com/templates/enterprise-page.md))
- [Area chart](https://wingo-ui.com/components/area-chart) ([Markdown](https://wingo-ui.com/components/area-chart.md))
- [Slider](https://wingo-ui.com/components/slider) ([Markdown](https://wingo-ui.com/components/slider.md))

---

Canonical: https://wingo-ui.com/templates/roi-calculator-page
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
