<calc-input>, a custom input element that accepts mathematical formulas

Recording of the <calc-input> demo.

Continuing my streak of small form-related Custom Elements (like <rich-input>), I built <calc-input>. It’s a custom input element that accepts mathematical formulas — such as 2 + 3 or (2 + 3) * 4 — and automatically toggles between showing the formula on focus and the calculated result on blur.

~

# The need for a smarter numeric input field

I’m currently building a web app that has a lot of numeric inputs whose values need to be sourced from a different document. Sometimes filling in a field is a direct copy-paste of a number, but oftentimes there’s a calculation that needs to be done first — like taking one number, dividing it by 3, and subtracting another number from it.

With a standard <input> element, doing this means constantly round-tripping to a separate calculator app, crunching the numbers there, and copying the result back into the browser. Even worse: if you come back to the form later to double-check your entries, you only see the final number and have no clue which formula or source numbers produced it 😬

Instead of having to do that calculator dance over and over again, I thought of creating a more powerful input element that lets you type the formula directly into the field: <calc-input>

~

# Enter <calc-input>

<calc-input> is a custom input element that accepts mathematical formulas — such as 2 + 3 or (2 + 3) * 4 — and automatically toggles between showing the formula on focus and the calculated result on blur.

To use it, install calc-input from npm (or load it via a CDN) and drop the <calc-input> tag right into your HTML <form>:

<script type="module" src="calc-input"></script>

<form>
	<calc-input name="size" value="2 + 3"></calc-input>
	<button type="submit">Submit</button>
</form>

When focused, the input shows the entered formula (2 + 3). Upon blurring the field, it evaluates the expression and displays the calculated result (5). Re-focusing the field brings back the original formula so you can easily inspect or tweak it 🙂

Out of the box, it supports standard arithmetic operators (+, -, *, /, %, ^ / **), nested parentheses like (2 + 3) * 4, unary operators, decimals, and common math functions (sqrt, abs, round, floor, ceil, min, max, pow). All expressions are evaluated using a safe recursive-descent parser — so no eval() is involved!

Not so fun fact: I initially wanted to name this project formula-input, but turned out that name was already taken when I was tried publishing the package. This lead to the creation of the caniname CLI tool as a spinoff project.

~

# Demo

Give the component a try in the interactive demo below:

See the Pen
<calc-input> demo
by Bramus (@bramus)
on CodePen.

For more extended demos, check out the project website.

~

# How it works under the hood

Behind the scenes, <calc-input name="size"> renders three standard <input type="text"> elements in the Light DOM, toggling which one is visible upon focus:

<calc-input name="size" value="2 + 3">
	<input type="text" name="size" hidden>
	<input type="text" name="size--formula" hidden>
	<input type="text" name="size--result">
</calc-input>

At any given time, two of these inputs have the hidden attribute so the user only ever sees one input field. When blurred, the --result input is visible; when focused, --formula input takes its place for the user to edit.

Because these are regular Light DOM inputs, native HTML form submissions and constraint validation work seamlessly out of the box. When you enter an incomplete or unparsable formula like 2 + and blur the field, <calc-input> keeps showing the formula input, highlights it with an invalid border, and calls setCustomValidity() to prevent the parent <form> from being submitted.

~

# Configuration & Styling

What actually gets submitted to the server is configurable through the submit attribute:

  • submit="formula" (default): Submits all three fields, with the raw formula in the primary name="size" input.
  • submit="result": Submits all three fields, with the evaluated result in the primary name="size" input.
  • submit="formula-only" / submit="result-only": Omits the --formula and --result inputs from form submission so only the primary name="size" field is sent.

If the default -- delimiter in size--formula and size--result doesn’t match your backend naming conventions, you can customize it via the separator attribute (e.g. separator="_" or even separator="").

For styling, <calc-input> uses display: contents and lets its inner <input> elements inherit box and typography properties (border, border-radius, padding, background, color, font, etc.). That way, any CSS styles you apply directly onto calc-input automatically style the underlying inputs:

calc-input {
	border: 2px solid #3b82f6;
	border-radius: 9999px;
	padding: 0.625rem 1.25rem;
}

~

# More info

Check out the links below for the full documentation, more examples, and the source code:

~

# Spread the word

Feel free to reshare one of the following posts on social media to help spread the word:

~

🔥 Like what you see? Want to stay in the loop? Here's how:

I can also be found on 𝕏 Twitter and 🐘 Mastodon but only post there sporadically.

Published by Bramus!

Bramus is a frontend web developer from Belgium, working as a Chrome Developer Relations Engineer at Google. From the moment he discovered view-source at the age of 14 (way back in 1997), he fell in love with the web and has been tinkering with it ever since (more …)

Unless noted otherwise, the contents of this post are licensed under the Creative Commons Attribution 4.0 License and code samples are licensed under the MIT License

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.