<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 primaryname="size"input.submit="result": Submits all three fields, with the evaluated result in the primaryname="size"input.submit="formula-only"/submit="result-only": Omits the--formulaand--resultinputs from form submission so only the primaryname="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.