Building a calculator is often one of the first projects you tackle when learning a new JavaScript framework. It's the perfect exercise: it combines state management (tracking numbers and operations), UI logic, and event handling.
But why stop at a basic, functional calculator? In this article, we'll not only build the logic for a calculator using Vue.js 3 and its fantastic Composition API, but more importantly, we'll give it a modern, professional look inspired by a popular design, leveraging the power of CSS Grid.
The final result is live here, and all the code is available in this GitHub repository:
➡️ https://github.com/VincentCapek/calculator
Step 1: Setting Up the Vue.js Project
To get started, nothing beats Vue's official project scaffolding tool:
npm create vue@latest
For this project, we don't need complex features like Vue Router or Pinia. I simply enabled ESLint and Prettier to ensure clean, consistent code.
Once the project is created, navigate into the directory, install the dependencies, and start the development server:
cd your-project-name npm install npm run dev
We'll be working in a single file for this entire tutorial: src/App.vue.
Step 2: The Calculator Logic with the Composition API
This is the "brain" of our application. Thanks to the Composition API and the <script setup>
syntax, our logic is both reactive and highly readable.
We need a few state variables to keep track of what's happening:
import { ref } from 'vue'; // The main display of the calculator const current = ref(''); // The previous number, stored after an operator is clicked const previous = ref(null); // The selected operator (+, -, etc.) const operator = ref(null); // A flag to know if an operator was just clicked const operatorClicked = ref(false);
ref()
is Vue 3's way of creating reactive variables. Whenever their .value changes, the UI updates automatically. It's like magic!
Next, we define the functions that bring our calculator to life:
-
clear()
: Resets everything. This is our "AC" button. -
append(char)
: Adds a digit or a decimal point to the current value. -
setOperator(op)
: Stores the current value, saves the operator, and gets ready for the next number. -
calculate()
: Performs the calculation and updates the display.
Here's the complete script, which also includes a little bonus: handling division by zero.
<script setup> import { ref } from 'vue'; const current = ref(''); const previous = ref(null); const operator = ref(null); const operatorClicked = ref(false); const clear = () => { current.value = ''; previous.value = null; operator.value = null; operatorClicked.value = false; }; const append = (char) => { if (operatorClicked.value) { current.value = ''; operatorClicked.value = false; } if (char === '.' && current.value.includes('.')) return; current.value = `${current.value}${char}`; }; const setOperator = (op) => { if (current.value === '') return; if (operator.value !== null) { calculate(); } previous.value = current.value; operator.value = op; operatorClicked.value = true; }; const calculate = () => { if (operator.value === null || previous.value === null) return; let result; const prev = parseFloat(previous.value); const curr = parseFloat(current.value); // Handle division by zero if (operator.value === '÷' && curr === 0) { current.value = 'Error'; previous.value = null; operator.value = null; return; } switch (operator.value) { case '+': result = prev + curr; break; case '-': result = prev - curr; break; case 'x': result = prev * curr; break; case '÷': result = prev / curr; break; } current.value = String(result); previous.value = null; operator.value = null; }; </script>
Step 3: The HTML Structure (The Skeleton)
For better semantics and accessibility, we'll use appropriate HTML elements:
- An
<input type="text" readonly>
for the display screen. - Real
<button>
elements for all the keys.
The template is directly tied to our logic. Notice how we call the functions with arguments directly from the @click event handler, which is a clean and robust approach.
<template> <div class="calculator"> <input type="text" class="calculator-screen" :value="current || '0'" readonly /> <div class="calculator-keys"> <!-- Operators --> <button type="button" class="operator" @click="setOperator('+')">+</button> <!-- ... other operators --> <!-- Digits --> <button type="button" @click="append('7')">7</button> <!-- ... other digits --> <!-- Special Functions --> <button type="button" class="all-clear" @click="clear">AC</button> <button type="button" class="equal-sign operator" @click="calculate">=</button> </div> </div> </template>
Step 4: The Magic of CSS (The Design)
This is where our calculator truly comes to life !
1. The Background and "Glassmorphism" Effect
The page body gets a beautiful linear gradient, while the calculator itself has a semi-transparent background with a backdrop-filter. This creates the trendy "glassmorphism" effect, making it look like frosted glass.
body { background: linear-gradient(to right, #6190e8, #a7bfe8); } .calculator { box-shadow: 0 0 40px 0px rgba(0, 0, 0, 0.15); background-color: rgba(255, 255, 255, .75); backdrop-filter: blur(5px); }
2. A Perfect Layout with CSS Grid
The button layout is managed with display: grid, making it robust and easy to maintain. grid-gap creates a uniform spacing between each button.
.calculator-keys { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 20px; padding: 20px; }
3. The Spanning "Equals" Button
The highlight of this design is the = button, which spans multiple rows. This is incredibly simple to achieve with the grid-area property. The syntax grid-area: row-start / col-start / row-end / col-end; gives us complete control.
.equal-sign { /* Starts on grid row 2, column 4, and spans down to row 6, column 5 */ grid-area: 2 / 4 / 6 / 5; height: 100%; }
4. Hover Effects that Respect Contrast
This is a crucial UX detail. A generic grey :hover state on all buttons would ruin the design of the colored ones (white text on a light grey background is nearly unreadable). The solution is to define specific hover states for each button type, simply by making their base color slightly lighter.
/* Hover for numeric buttons */ button:hover { background-color: #f0f0f0; } /* SPECIFIC hover for operators */ .operator:hover { background-color: #469dcb; /* A lighter blue */ } /* SPECIFIC hover for the "AC" button */ .all-clear:hover { background-color: #f26f74; /* A lighter red */ }
Conclusion
And there you have it! We've built a calculator that is not only functional, thanks to the reactive power of Vue.js 3, but is also visually stunning using modern CSS techniques.
This project is a great example of how fundamentals are key, but with a bit of attention to design and user experience, you can turn a simple learning exercise into a portfolio piece you can be proud of.
Thanks for reading, and happy coding !
Top comments (0)