Why donate
API Explorer
Upgrade Guide
Creating a New Project
The /quasar.config File
Convert q/app-webpack Project
Browser Compatibility
TypeScript Support
Directory Structure
Commands List
CSS Preprocessors
Page Routing with VueRouter
Lazy Loading - Code Splitting
Handling Assets
Boot Files
Prefetch Feature
API Proxying
Handling Vite
Handling import.meta.env
State Management with Pinia
Lint and Format Code
Testing & Auditing
Developing Mobile Apps
Ajax Requests
Opening Dev Server To Public
Quasar CLI with Vite - @quasar/app-vite v3
Frameless Electron Window

A nice combo is to use frameless Electron window along with QBar component. Here’s why.

Main process

Setting frameless window

In your src-electron/electron-main file, make some edits to these lines:

/src-electron/electron-main file

import {
  // ...other imports
  ipcMain // <-- add this
} from 'electron'

function createWindow() {
  const mainWindow = new BrowserWindow({
    // ...other settings
    frame: false // <-- add this
  })

  // ...
}

// Add this function:
function registerWindowControls() {
  ipcMain.on('window:minimize', event => {
    BrowserWindow.fromWebContents(event.sender)?.minimize()
  })

  ipcMain.on('window:toggle-maximize', event => {
    const win = BrowserWindow.fromWebContents(event.sender)
    if (!win) return

    if (win.isMaximized()) {
      win.unmaximize()
    } else {
      win.maximize()
    }
  })

  ipcMain.on('window:close', event => {
    BrowserWindow.fromWebContents(event.sender)?.close()
  })
}

function getSenderWindow(event) {
  const win = BrowserWindow.fromWebContents(event.sender)

  return event.senderFrame === win?.webContents.mainFrame ? win : undefined
}

app.whenReady().then(async () => {
  registerWindowControls() // <-- call this before createWindow()
  createWindow()
  // ...
})

Resolving the window from event.sender ensures that each renderer controls only its own window. If your app loads remote content or accepts navigation away from its packaged UI, also validate the sender of every IPC message before performing privileged actions.

The preload script

Expose the window API to the renderer process through /src-electron/electron-preload:

/src-electron/electron-preload

import { contextBridge, ipcRenderer } from 'electron'

// notice "myWindowAPI" (can be anything as long as we reference
// the same name that we use in the renderer process)
contextBridge.exposeInMainWorld('myWindowAPI', {
  minimize() {
    ipcRenderer.send('window:minimize')
  },
  toggleMaximize() {
    ipcRenderer.send('window:toggle-maximize')
  },
  close() {
    ipcRenderer.send('window:close')
  }
})

Renderer process

Handling window dragging

A frameless window needs a draggable region. Use the q-electron-drag and q-electron-drag--exception Quasar CSS helper classes.

<q-bar class="q-electron-drag"> ... </q-bar>

The class allows the user to drag the window from that region.

Interactive children must not trigger dragging. QBtn is excluded automatically; add q-electron-drag--exception to other interactive children.

Example of adding an exception to an icon:

<q-bar class="q-electron-drag">
  <q-icon name="map" class="q-electron-drag--exception" />

  <div>My title</div>
</q-bar>

Minimize, maximize and close app

Full example


In the example above, notice that we add q-electron-drag to our QBar and we also add handlers for the minimize, maximize and close app buttons by using the injected window.myWindowAPI Object (from the Electron preload script).

Some .vue file

// We guard the Electron API calls, but this
// is only needed if we build same app with other
// Quasar Modes as well (SPA/PWA/Cordova/SSR...)

export default {
  setup() {
    // we rely upon
    function minimize() {
      if (import.meta.env.QUASAR_ELECTRON_MODE) {
        window.myWindowAPI.minimize()
      }
    }

    function toggleMaximize() {
      if (import.meta.env.QUASAR_ELECTRON_MODE) {
        window.myWindowAPI.toggleMaximize()
      }
    }

    function closeApp() {
      if (import.meta.env.QUASAR_ELECTRON_MODE) {
        window.myWindowAPI.close()
      }
    }

    return { minimize, toggleMaximize, closeApp }
  }
}

We can also hide the header window bar for non-Electron Quasar modes:

<template>
  <q-bar v-if="isElectron" class="q-electron-drag">
    <q-icon name="laptop_chromebook" />
    <div>Google Chrome</div>

    <q-space />

    <q-btn dense flat icon="minimize" @click="minimize" />
    <q-btn dense flat icon="crop_square" @click="toggleMaximize" />
    <q-btn dense flat icon="close" @click="closeApp" />
  </q-bar>
</template>

<script setup>
  const isElectron = import.meta.env.QUASAR_ELECTRON_MODE
  // ...
</script>