DEV Community

Cover image for Build a Sleek, Modern Calculator with Vue.js 3 and CSS Grid
A0mineTV
A0mineTV

Posted on

Build a Sleek, Modern Calculator with Vue.js 3 and CSS Grid

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

Preview of the final calculator

Step 1: Setting Up the Vue.js Project

To get started, nothing beats Vue's official project scaffolding tool:

npm create vue@latest
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

 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);
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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%;
}
Enter fullscreen mode Exit fullscreen mode

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 */
}
Enter fullscreen mode Exit fullscreen mode

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)