svelte-calculator Svelte Themes

Svelte Calculator

A simple, customizable, and composable calculator component.

svelte-calculator

A Svelte 5 component library for building calculators. Includes standard and scientific operations, token-based expression validation, and evaluation via mathjs.

Install

npm install svelte-calculator

Usage

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

Components

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.

Operations

All operations are exported from svelte-calculator/operations in three groups:

Group Exports
digit zeronine, 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.

Examples

Accessing calculator state

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>

Scientific calculator with 2nd-mode buttons

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>

Custom operations

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" />

CSS custom properties

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;
}

License

MIT

Top categories

Loading Svelte Themes