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)