A Svelte 5 component library for building calculators. Includes standard and scientific operations, token-based expression validation, and evaluation via mathjs.
npm install svelte-calculator
<script>
import * as Calculator from 'svelte-calculator'
import { digit, basic, specific } from 'svelte-calculator/operations'
</script>
<Calculator.Root>
<Calculator.Container>
<Calculator.Display />
<Calculator.Body variant="basic">
<Calculator.Button operation={specific.allClear} variant="function" label="AC" />
<Calculator.Button operation={specific.toggleSign} variant="function" label="+/−" />
<Calculator.Button operation={specific.percent} variant="function" label="%" />
<Calculator.Button operation={basic.divide} variant="operator" />
<Calculator.Button operation={digit.seven} />
<Calculator.Button operation={digit.eight} />
<Calculator.Button operation={digit.nine} />
<Calculator.Button operation={basic.multiply} variant="operator" />
<Calculator.Button operation={digit.four} />
<Calculator.Button operation={digit.five} />
<Calculator.Button operation={digit.six} />
<Calculator.Button operation={basic.subtract} variant="operator" />
<Calculator.Button operation={digit.one} />
<Calculator.Button operation={digit.two} />
<Calculator.Button operation={digit.three} />
<Calculator.Button operation={basic.add} variant="operator" />
<Calculator.Button operation={digit.zero} span={2} />
<Calculator.Button operation={digit.decimal} />
<Calculator.Button operation={basic.equals} variant="operator" />
</Calculator.Body>
</Calculator.Container>
</Calculator.Root>
Each operation has a default label, so label is only needed when you want to override it. The entire button can also
be replaced with a child snippet:
<Calculator.Button operation={digit.one}>
{#snippet child({ onclick, style })}
<button {onclick} {style} class="my-button">1</button>
{/snippet}
</Calculator.Button>
| Component | Description |
|---|---|
Calculator.Root |
Root provider. Creates CalculatorState and sets context. |
Calculator.Container |
Outer wrapper. Accepts an optional class prop. |
Calculator.Display |
Shows the current expression and result. |
Calculator.Body |
CSS grid for buttons. variant="basic" = 4 columns, variant="scientific" = 10 columns. |
Calculator.Button |
Dispatches an operation on click. Props: operation, secondary, label, secondaryLabel, variant, span, child. |
All operations are exported from svelte-calculator/operations in three groups:
| Group | Exports |
|---|---|
digit |
zero–nine, decimal |
basic |
add, subtract, multiply, divide, equals |
specific |
sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh, log10, ln, log2, squareRoot, cubeRoot, nthRoot, square, cube, power, exp, expPower, factorial, reciprocal, absolute, percent, pi, euler, random, openParen, closeParen, clear, allClear, backspace, toggleSign, second, radDeg, memoryAdd, memorySubtract, memoryRecall, memoryClear |
DEFAULT_OPERATIONS is also exported as a flat array of all the above.
State is available anywhere inside <Calculator> via context.
<script>
import { getCalculatorContext } from 'svelte-calculator'
const { calculator } = getCalculatorContext();
const expression = $derivedy(calculator.expression.map(token => token.display).join(''));
</script>
<p>{calculator.result}</p>
<p>{expression}</p>
secondary lets a button switch operations when the 2nd key is active.
<Calculator.Body variant="scientific">
<Calculator.Button operation={specific.second} variant="function" label="2nd" />
<Calculator.Button
operation={specific.sin}
secondary={specific.asin}
variant="function"
/>
<Calculator.Button
operation={specific.cos}
secondary={specific.acos}
variant="function"
/>
<Calculator.Button
operation={specific.tan}
secondary={specific.atan}
secondaryLabel="tan⁻¹" <!-- optional, overrides the secondary operation's default label -->
variant="function"
/>
<!-- ... -->
</Calculator.Body>
Extend the Operation class and register it as a hook.
import { Operation } from 'svelte-calculator/operations'
import type { CalculatorContext } from 'svelte-calculator'
class DoubleResult extends Operation<'custom.double'> {
constructor() {
super('custom.double', '×2')
}
execute(context: CalculatorContext) {
const value = Number(context.calculator.result)
if (!isNaN(value)) {
context.calculator.result = String(value * 2)
}
}
}
export const doubleResult = new DoubleResult()
<script>
import * as Calculator from 'svelte-calculator'
import { getCalculatorContext } from 'svelte-calculator'
import { doubleResult } from './my-operations'
const { calculator } = getCalculatorContext()
calculator.addHook(doubleResult)
</script>
<Calculator.Button operation={doubleResult} variant="function" />
All visual styles are exposed as CSS custom properties. Override them on the container or any parent element.
:root {
/* Container */
--calc-font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif, system-ui;
--calc-bg: #1c1c1e;
--calc-border-radius: 2rem;
--calc-padding: 1rem;
/* Display */
--calc-display-padding: 20px 12px 12px;
--calc-display-min-height: 72px;
--calc-display-gap: 4px;
--calc-expression-color: #fff;
--calc-expression-font-size: 48px;
--calc-expression-font-weight: 300;
--calc-result-color: #a5a5a5;
--calc-result-font-size: 18px;
--calc-result-font-weight: 300;
/* Grid */
--calc-grid-gap: 8px;
--calc-grid-padding: 0 12px 12px;
/* Body (overrides for buttons inside the grid) */
--calc-body-btn-border-radius: 9999px;
--calc-body-btn-font-family: inherit;
--calc-body-btn-font-size: 15px;
--calc-body-btn-wide-border-radius: 8px;
--calc-body-btn-wide-padding: 0 16px;
--calc-body-btn-op-font-size: 20px;
--calc-body-btn-min-height: 42px;
/* Button (default) */
--calc-btn-bg: #333336;
--calc-btn-color: #fff;
--calc-btn-border-radius: 50%;
--calc-btn-font-family: inherit;
--calc-btn-font-size: 22px;
--calc-btn-font-weight: 400;
--calc-btn-active-brightness: 1.4;
/* Button (function variant) */
--calc-btn-fn-bg: #a5a5a5;
--calc-btn-fn-color: #1c1c1e;
--calc-btn-fn-font-weight: 500;
/* Button (operator variant) */
--calc-btn-op-bg: #ff9f0a;
--calc-btn-op-font-size: 26px;
--calc-btn-op-font-weight: 500;
/* Button (wide / span > 1) */
--calc-btn-wide-border-radius: 32px;
--calc-btn-wide-padding: 0 24px;
}
MIT