PricingCalculator
Interactive duration × workload pricing calculator for the pricing page.
Status: stable. Group: marketing. Markdown version: /design-system/components/pricing-calculator.md
When to use
PricingCalculator is the custom-engagement estimator on the pricing page: two single-select SelectableCardGroups (duration: 2 weeks to 12 months; workload: 2-5 days/week, or 25/50/75/100% allocations of a 5-day week when the 'Show as percentages' Switch is on; switching views moves the selection to the nearest option, and short 2-week/1-month engagements offer only 4-5 days or 75-100%) recompute total hours, allocation, the effective hourly rate, and the total investment on every change. Partnership selections add a note under the total: '1 day per week is free' in the days view, '25% off the standard rate' in the percentage view. Only the 6/12-month partnership tiers at a workload of at least 75% of a week (4+ days, or 75%/100%) earn the 90 EUR/h partnership rate; those selections carry Partnership badges and a distinct discounted price-card treatment. A 'Custom duration and pace' Switch replaces both groups with two text fields, months (0.5 or more, no upper limit) and days a week (1 to 5), each accepting any decimal in range and a decimal comma; switching on carries the preset over, switching off lands on the nearest preset. A custom term earns the partnership rate from 6 months at 4+ days a week, past 12 months too, and its note always reads '25% off the standard rate'. An optional Budget field below them fits the months to the budget at the current pace; while it is set, changing the days recomputes the months and changing the months recomputes the days (1 to 5), rounded down so the price stays within the budget, and the total card says how much of the budget the term uses (or how far a clamp pushes it over). A budget that reaches 6+ months at 4+ days is fitted at the partnership rate. Out-of-range values show an error after blur and keep the last valid price. It complements the fixed packages; it does not replace them.
When not to use
- Do not use for fixed-scope package pricing (packages section owns that)
- Do not duplicate the pricing model outside pricingMath.ts.
- Don't use it for fixed-scope package pricing; that's the packages section of PricingPageContent.
Look-alikes
The contract names no look-alike with a deciding condition.
Props
| Prop | Type | Default | Description | Required |
|---|---|---|---|---|
className | string | none | Optional wrapper class. | No |
selectionFromUrl | boolean | false | Start from the ?duration=6m&days=4 selection in the page URL (the same parameters the calculator's own CTA emits), so a link can arrive with a tier already chosen; ?duration=custom&months=3.5&days=2.5 opens the custom term, and &budget=40000 adds the budget it was fitted to. Defaults to false. A missing or unknown duration keeps the defaults; with a valid preset duration, a days value that matches neither view falls back to the heaviest day option for that duration. | No |
Variants
No variant dimensions and no props with a fixed set of values.
Invalid combinations
The contract records no invalid combinations.
Keyboard
| Key | Behaviour |
|---|---|
| Tab | Moves focus to the control and on to the next focusable element. (standard) |
| Arrow keys | Named in the contract without a description. |
| Space | Activates or toggles the focused control. (standard) |
| Enter | Activates the focused control. (standard) |
Rows marked (standard) name a key the contract lists without a description; the behaviour shown is the usual one for that key.
Rules
Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.
| Level | Rule | How it is checked |
|---|---|---|
| MUST | ARIA: Section labelled by the calculator title. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: Native fieldset/legend + radio semantics from SelectableCardGroup. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: CTA is a real link to /contact. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: Visually hidden role=status region restates the selection after each change in the view the reader sees (days per week in the days view, allocation percentage in the percentage view) plus the same partnership note as the visible total card; when a duration change clamps the workload it starts with the new workload. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: The "Show as percentages" Switch swaps the workload radios between days and percentage allocations. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: Workload radios are added/removed on duration change; selection is clamped programmatically and focus is restored if the focused radio unmounts. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | ARIA: The "Custom duration and pace" Switch swaps both radio groups for two labelled text inputs (inputmode decimal) with helper text naming the range; an invalid value sets aria-invalid and an error message after blur, and the status region keeps announcing the last valid term; the optional Budget field is a labelled text input (inputmode numeric) whose changes rewrite the months or days fields, and the status region adds the budget line. | Not yet verified: no evidence record. WCAG 4.1.2. |
| MUST | Required stories produce no axe violations at the configured severity. | Automated by npm run test:stories:matrix:ci. WCAG 2.5.8. |
| MUST | The accessibility tree (role, name, state) matches the committed snapshot for every required story. | Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2. |
| MUST | The component remains legible and operable under a real browser forced-colors: active pass. | Automated by npm run test:stories:hc. |
| MUST | Every documented keyboard interaction works as specified. | Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2. |
| MUST | A human completed an end-to-end accessibility review of this component. | Manual review, last on 2026-11-04. |
| SHOULD | Keep the pricing model in pricingMath.ts as the single source of truth; the component only renders its output. | Not stated in the contract. |
| SHOULD NOT | Don't hardcode currency formatting; totals format via Intl.NumberFormat on the active language. | Not stated in the contract. |
Install and import
- In-repo only. PricingCalculator is not exported from
@digitaltableteur/react, so there is no public import path.
Links
- Machine-readable contract (Design System Contract 1.0)
- Markdown version of this page
- Component index as JSON
- All components
- Source: The implementation lives in a private repository. This page, its Markdown twin and the contract are generated from it at build time and are the public source of truth.