Quasar is a VueJs framework that can build apps for mobile, desktop and the web. It comes with a meticulously crafted, material design component library… and documentation with loads of examples!
Building apps with Quasar is like surfing a wave 🌊. It takes a bit of knowledge before you can stand up, but once you do... It. feels. A-MAZING!.
This guide touches on 11 aspects of Quasar that will set your Quasar journey on fire ️🔥. If you understand these 11 things:
If you're new to Quasar, this article will help get you started so that the docs make more sense.
If you run a team that uses Quasar, this article will be a starting point for new devs.
If you're already a pro with Vue, this post is designed to be easy to skim, so you can cherry pick what you need to know.
That's enough preamble... Let's get to it!
There are three official places you can try a Quasar app in your browser:
There's also Quasar Play. an insanely cool experiment - built by Quasar core team member Popescu Dan - that uses vue-repl with Quasar.
Before we get started, let's install the Quasar cli globally:
npm install -g @quasar/cli
try running quasar
in your terminal to see what it can do!
Now, there's a few ways to build a Quasar app. You'll almost certainly want to use Quasar's CLI:
yarn create quasar
follow the prompts, and be sure to enable linting!
I personally like to use ESLint with the standard
preset (in other words, without prettier!):
✔ Check the features needed for your project: › Linting (vite-plugin-checker + ESLint)
✔ Pick an ESLint preset: › Standard
Straight up, we can run our app in the following modes...
no more configuration required!
spa (the default)
quasar dev
electron (windows, mac, linux)
quasar dev -m electron
ssr
quasar dev -m ssr
bex (browser extension)
quasar dev -m bex
Trouble installing the Quasar CLI? Prefix the above commands with your package manager (e.g. yarn quasar dev)
And that's it!
To build for android or ios, you'll need to do a little more setup (we walk through this in detail in the Quasar Fundamentals Course). Once setup, you can easily get started with the following commands:
Android
quasar dev -m capacitor -T android
iOS
quasar dev -m capacitor -T ios
Notice that we use capacitor to build our mobile app? This means if we have any "mobile app" related problems, or want to add "mobile app" plugins, we research "capacitor". For example instead of "how to add GPS to a Quasar app" we'd search "how to add GPS to a Capacitor app". This way, you'll have a lot more luck finding what you're looking for!
There you have it! It's kinda ridiculous how easy this stuff is right?
Quasar's done a monstrous amount of work to allow us to build apps for any platform, in seconds!
The first thing I do in a new Quasar project, is enable stricter linting.
This will change your life!!!
.eslintrc.js
module.exports = {
// ...
extends: [
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
],
// ...
}
See how we commented out plugin:vue/vue3-strongly-recommended
and commented in plugin:vue/vue3-recommended
? You'll love this 💜.
The main benefit?…
When you save a file, your code will format in a way that's readable.
This is a HUGE productivity win!
Next, you'll want to ensure linting works properly in your editor. We go through this step by step in the Quasar Fundamentals Course.
Every Quasar component, plugin and directive comes with an API card.
If you understand Quasar's API cards, you understand 90% of Quasar.
It's the difference between a dev saying "I find Quasar's docs confusing" vs "Quasar has the best docs on the PLANET!".
Of course, the Quasar Fundamentals Course covers api cards in detail, though we'll touch on all the sections now! Below is an image of the QTable API card:
Let's go over basic examples of how all these categories are used with the QTable component!
Props are the easiest to understand. If you know Vue, chances are you already know how to use props and can skip this section.
Here's how props used in Quasar:
Passing a string
<template>
<q-table title="Friends" />
</template>
Passing a ref
/expression
<script setup>
import { ref } from 'vue'
const fullscreen = ref(false)
</script>
<template>
<q-table
:rows="users"
:fullscreen="fullscreen"
/>
</template>
Boolean (true)
<q-table
:rows="users"
hide-pagination
/>
Boolean (dynamic)
<script setup>
import { ref } from 'vue'
const fullscreen = ref(false)
</script>
<template>
<q-table
:rows="users"
:fullscreen="fullscreen"
>
</template>
Events are the second easiest to understand. If you know Vue, chances are you already know how to use events and can skip this section.
Here's how events are used in Quasar:
<script setup>
function onRowClicked(event, row) {
console.log(`you clicked on "${row.name}"`)
}
</script>
<template>
<q-table
:rows="users"
@row-click="onRowClicked"
/>
</template>
If an event starts with @update:
, then the data on that event can be modeled.
For example: @update:fullscreen
means we can do the following:
<script setup>
import { ref } from 'vue'
const fullscreen = ref(false)
</script>
<template>
<q-table
:rows="users"
v-model:fullscreen="fullscreen"
/>
</template>
In the context of Quasar, think of "Slot" as "I'm taking total control".
Slots allow you to completely rewrite things like table cells, rows, the header, the footer and much more!
If you're new to slots, take as much time as you need to thoroughly understand the following examples:
Basic Slot
<q-table :rows="users">
<template #top-right>
Put literally <q-chip label="anything" /> you like here!
</template>
</q-table>
Named Slots
Some slots are dynamically generated. In the following example, our table has a name
column, and we "tap into" every name
cell to take total control of what it looks like.
<q-table
:rows="users"
:columns="[{ name: 'name', field: 'name', label: 'Name' }]"
>
<!-- In this example, "name" is dynamic -->
<template #body-cell-name>
<!-- We can now write custom code for our "name" data cell -->
<q-td>
You're now in <strong>total control</strong> of this cell!
</q-td>
</template>
</q-table>
Using Slot Scopes
The example above is incomplete for two reasons:
name
valueq-td
loses some of the functionality q-table
provides, because it's missing propsThat second reason is difficult to explain in the scope of this article, and is covered in more detail in the Quasar Fundamentals Course. Basically, we need to give the q-td
more “knowledge” of the tables state.
Let’s fix those two issues to complete the example:
<q-table :rows="users">
<template #body-cell-name="scope">
<!-- passing the scope to q-td solves problem 2 -->
<q-td :props="scope">
<!-- using scope.value solves problem 1 -->
<q-chip :label="scope.value" />
</q-td>
</template>
</q-table>
Using events on the scope
Sometimes, Quasar will give you access to events when using slots.
Read this next paragraph as many times as you need to understand!
"""
When using QTable, If we use quasar's #pagination
slot, we're saying "I'm going to take complete control over how pagination works". The problem is that we would then need to rewrite a lot of code, such as nextPage()
, prevPage()
, firstPage()
, lastPage()
etc. Instead of rewriting this code, Quasar makes those functions available through the scope. For example scope.nextPage()
, scope.prevPage()
, scope.firstPage()
, scope.lastPage()
etc.
"""
Here's a minimal example:
<q-table :rows="users">
<template #pagination="scope">
<q-btn @click="scope.prevPage()" />
<q-btn @click="scope.nextPage()" />
</template>
</q-table>
It doesn't feel right to leave you with an incomplete example! so here's how you can take total control over the design of pagination, while leaning on Quasar's scope
!
<q-table :rows-per-page-options="[2]" :rows="users">
<template #pagination="scope">
<q-btn
color="pink"
icon="first_page"
@click="scope.firstPage()"
:disable="scope.isFirstPage"
flat
round
/>
<q-btn
color="pink"
icon="arrow_back"
:disable="scope.isFirstPage"
@click="scope.prevPage()"
flat
round
/>
<q-pagination
color="pink"
v-model="scope.pagination.page"
:max="scope.pagesNumber"
/>
<q-btn
@click="scope.nextPage()"
color="pink"
icon="arrow_forward"
:disable="scope.isLastPage"
flat
round
/>
<q-btn
color="pink"
icon="last_page"
@click="scope.lastPage()"
:disable="scope.isLastPage"
flat
round
/>
</template>
</q-table>
Understand that example above and you'll never feel powerless using Quasar! 💪
If there's one thing new Quasar developers have trouble understanding it's this:
Quasar does NOT have a main.js
file. Whenever you need to access main.js
, you use a "boot file" instead.
Vue.use()
or app.use()
), you need a boot filemain.js
is accessed, you need a boot fileTo further add clarity, here's a basic overview of how a Quasar app starts:
createApp()
)Said another way, Quasar does all the prep it needs - which will be slightly different depending on the target platform - then it runs the boot files. That's why we don't have a main file when working with Quasar. It's automatically created for us.
So how do we create, and use a boot file?…
We can easily generate a boot file using quasar
(or yarn quasar
if you haven't installed Quasar globally):
quasar new boot some-vue-plugin
Then to use the boot file, we add it to our quasar.config.js
file:
export default configure((ctx) => {
return {
boot: [
'some-plugin' // <<<
],
}
})
And that's it! Anything you place inside the boot function is kinda like adding to your main.js
file!
import SomeVuePlugin from 'some-plugin'
export default boot(({ app, router, store, ssrContext }) => {
app.use(SomeVuePlugin)
})
In the above example, I've destructured the 4 main things you'll want to know about:
app
: your "Vue App" (the result of const app = createApp(rootElement)
)router
: Vue router instancestore
: Pinia store (Vuex is supported, but deprecated)ssrContext
: only available in ssr modeNow let's take a look at some examples...
1. Installing a Vue plugin
This is actually kind of rare these days. Most "Vue Plugins" don't need to be installed but there are a few!
import { boot } from 'quasar/wrappers'
import { createI18n } from 'vue-i18n'
import messages from 'src/i18n'
export default boot(({ app, store }) => {
const i18n = createI18n({
locale: 'en-US',
globalInjection: true,
messages
})
app.use(i18n)
})
2. Installing a Pinia plugin (Pinia ORM)
Remember, we have access to store
(which is the Pinia store) in our boot file. We can use that store to install Pinia plugins:
import { createORM } from 'pinia-orm'
export default boot(({ store }) => {
store.use(createORM())
})
3. Creating a global variable (wretch)
We can easily create global variables within a boot file. This example uses wretch
(a thin wrapper around the fetch
api)
boot/wretch.js
import { boot } from 'quasar/wrappers'
import wretch from 'wretch'
const api = wretch('https://jsonplaceholder.typicode.com/')
export default boot(({ app }) => {
app.config.globalProperties.$wretch = wretch
app.config.globalProperties.$api = api
})
export { api }
We can now use $api
directly in our components, or using this.$api
in the options API.
In a component:
<q-btn
label="Get Posts"
@click="$api.get('posts')"
/>
Options API:
<script setup>
defineOptions({
methods: {
async fetchPosts() {
const data = await this.$api.get('posts').json()
}
}
})
</script>
I personally prefer to use the composition API, and also avoid globals entirely:
<script setup>
import { api } from 'src/boot/wretch'
async fetchPosts() {
const data = await api.get('posts').json()
}
</script>
4. A "side effect import"
Sometimes we just need an import statement that we'd usually put in a main file. For example, when installing UnoCSS we need to import virtual:uno.css
. This is crazy simple to do inside a boot file!
quasar new boot unocss
import 'virtual:uno.css'
NOTE! This is just an example of a "side effect import". There are more steps required to install UnoCSS which we cover in the Quasar Fundamentals Course.
Quasar's utility classes are very well documented! They exist in two places:
The Quasar Fundamentals Course shows many examples! Here, let's cover a couple of the main ones, then you can dig deeper once you get a feel for how Quasar's utility classes work!
Your brand colors can be changed in src/css/quasar.variables.scss
. The main one's you'll want to change are primary
, secondary
and accent
:
$primary : #88b865;
$secondary : #0777BF;
// etc
Primary Colors
Quasar comes with a palette of colors built in. colors from 1-10
are variations on the primary color, 1
being the lightest, 10
the darkest.
For example:
red-1
lightest red:
red-10
darkest red:
indigo-8
a somewhat dark indigo:
If no number is provided, then -6
is used. For example, red
is the same as red-6
Accent Colors
colors from 11-14
are "accent colors".
For example:
teal-11
:
purple-14
:
Using Colors
There are three main ways to use Quasar colors
color="blue-10"
, and in some cases text-color="grey-10"
bg-purple-1
text-blue-grey-10
Here are some examples, showing colors in action!
<template>
<q-btn color="teal" />
<q-chip
label="email"
color="teal-2"
text-color="teal-10"
/>
<q-card class="bg-indigo">
<q-toolbar class="bg-primary text-white">
<q-toolbar-title>
Spy X Family
</q-toolbar-title>
</q-toolbar>
<q-card-section class="text-grey-1">
Elegance!
</q-card-section>
</q-card>
<q-btn
color="accent"
icon="person"
fab
/>
</template>
We also have JavaScript utilities for working with colors!
import { colors } from 'quasar'
const { getPaletteColor } = colors
console.log(getPaletteColor('primary')) // '#1976d2'
console.log(getPaletteColor('red-2')) // '#ffcdd2'
Quasar had utility classes for margins and paddings way before it was cool 😎
Here are some examples, followed by an explanation:
<div class="q-mt-xl" />
<div class="q-mr-md" />
<div class="q-pa-lg" />
<div class="q-pl-xs" />
Here's how we create a spacing class with Quasar:
q-
p
for padding, m
for margint
for top, r
for right, a
for all etc-
xs
, sm
, md
, lg
, xl
or none
In other words: q-[kind][side]-[size]
confused? I don’t blame you! This will make a lot more sense as you look at examples so…
Here's a couple more! We'll also introduce x
(left AND right) and y
(top AND bottom) for the sides.
See if you can visualize where the margins/paddings are in your head while reading these examples:
<div class="q-mt-md q-px-lg" />
<div class="q-ma-sm q-py-lg" />
Quasar has typography utility classes that will cover your basic needs!
The basics
There are utility classes for body text, subtitles and captions:
<div class="text-body1">Great for the body of a blog</div>
<div class="text-subtitle1">A little bit extra...</div>
<div class="text-caption">often used under a photo</div>
Headings
Usually, a h
tag will give you the styling you need. You'll likely want to pair it with your own margins:
<h1 class="q-mt-none q-mb-md">Quasar, All The Platforms!</h1>
Sometimes, we want h
tag sizes, without the h
tag. For that we have utility classes:
<div class="text-h5">Looks good at the top of a card</div>
Font weights
We have weights from thin
through to bolder
:
<div class="text-weight-thin">Thin</div>
<div class="text-weight-light">Light</div>
<div class="text-weight-regular">Regular</div>
<div class="text-weight-medium">Medium</div>
<div class="text-weight-bold">Bold</div>
<div class="text-weight-bolder">Bolder</div>
Alignment
And here are the alignment classes you'll commonly use:
<div class="text-center">center</div>
<div class="text-right">right</div>
<div class="text-left">left</div>
Visibility
Quasar has a lot of helpful visibility classes!
You'll likely be most interested in the platform related utility classes.
Here are some examples:
<!-- "only" classes -->
<div class="desktop-only">You're on a desktop computer!</div>
<div class="platform-android-only">I see you're on android. Nice!</div>
<!-- "hide" classes -->
<div class="touch-hide">This is NOT a touch device</div>
<div class="electron-hide">Looks like this site was built with electron 👀</div>
We also have the basics like hidden
, invisible
and transparent
.
A Couple More!
Here's a couple other utility classes I use all the time:
relative-position
fixed
, fixed-top-right
, fixed-bottom-left
etcabsolute
, absolute-top-right
, absolute-bottom-left
etcscroll
(CSS tweaks to make scroll work well on ALL platforms)cursor-pointer
(for things that are "clickable" in the UI)The following pattern is a great way to make a button float outside a card using absolute
and relative-position
!
<!-- "relative-position" means "absolute" will be relative to this card -->
<q-card class="relative-position q-ma-xl">
<img src="<https://cdn.quasar.dev/img/mountains.jpg>" />
<q-card-section>
<div class="text-h6">Beautiful Mountains</div>
<div class="text-subtitle1">by Quasar Dev</div>
</q-card-section>
<!-- "absolute-top-right" tells this button to go to the top right of the card -->
<!-- Yes, we sneak in a style tag and that's okay! -->
<q-btn
class="absolute-top-right"
style="top: -12px; right: -12px"
fab
icon="fullscreen"
color="accent"
/>
</q-card>
At the bottom of Quasar's docs menu is a little know, yet wildly helpful menu. Quasar Utils:
Quasar offers pockets of functionality for common tasks. The ones I've found most handy are:
openURL()
, debounce()
, exportFile()
, copyToClipboard
etc)Here's how Quasar utilities usually work, using date as an example:
// we import all of `date`
import { date } from 'quasar'
// destructuring to keep only what is needed
const { addToDate } = date
const newDate = addToDate(new Date(), { days: 7, months: 1 })
The important part to note here is const { addToDate } = date
, especially if file size is important for your project. It helps with tree shaking, and means only addToDate
is included and NOT every function in date
.
Here are some useful utils to get you started!
Date
formatDate()
: great for displaying dates! You'll often use it within a computed
extractDate()
: for those times when we have a date string in a weird formatisSameDate()
: the third param is unit
which is helpful! Can easily check if two dates are the same day/year/month etcColor
rgbToHsv()
: if you want total control over a colorlighten()
: particularly helpful on the primary/secondarychangeAlpha()
: add a little opacity to a color!getPaletteColor()
: warning - not cheap to run, but good to know we can extract colors from the paletteOther Utils
openURL()
: takes care of quirks with Capacitor and Electron when opening a urlcopyToClipboard()
: try using this with a yellow Notify
and the mdiContentCopy
icon!debounce()
: stop users from spamming your server!uid()
: quick way to spin up a uuid… though consider using the uuid
library if the id is for the backendGenerating icons for every single platform is a nightmare. Creating the icons is half the battle! We then need to put them in the right location so that capacitor, electron, cordova and your website know how to find them…
For that reason, Quasar comes with a phenomenal tool for generating icons called Icon Genie.
With one command, you can generate every icon you need to deploy your project. Here's the basic setup:
First, install Icon Genie:
npm i -g @quasar/icongenie
Then, put your png icon in the root of your project (or anywhere you like). We recommend the dimensions are at least 1024x1024 px.
Run the icongenie generate
command and tell Icon Genie where your icon is located:
icongenie generate -i ./your-icon.png
You will need to update your index.html
/index.template.html file
!
Take note of the end of the command. It tells you how to update your index
file so that it knows how to find your new icons:
* You will need the following tags in your /index.html or /src/index.template.html:
<link rel="icon" type="image/png" sizes="128x128" href="icons/favicon-128x128.png">
<link rel="icon" type="image/png" sizes="96x96" href="icons/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="icon" type="image/ico" href="favicon.ico">
And that's it! Icon Genie looks through your Quasar project, identifies the platforms you're using, generates the icon files you'll need, and puts them in the right location! Not bad for a single command eh?
This was just an intro. If you need more advanced features take a look at the Command List. Icon Genie can also:
--background
to better generate splash screensPlugins are thoroughly covered in the Quasar Fundamentals Course!
You may want to think of plugins, as "functions that make common tasks in your app a easier". This article will give you a brief overview of how they work, using the Notify Plugin as an example.
All plugins must be installed. Quasar adds a little bit of code to our app to make plugins work, and for that reason we need to install them:
quasar.config.js
export default configure((ctx) => {
// ...
framework: {
plugins: [
'Notify'
]
}
// ...
})
All we did here, was add Notify
to the list of plugins. Notify
is built into Quasar, so no further setup is required.
We can now use this plugin anywhere in our app:
import { Notify } from 'quasar'
Notify.create({
message: '10 Points To Gryffindor!',
color: 'green-6'
})
The code above will even work in a boot file. For example, you may want to notify the user that they're not logged in, and are being redirected to the login page. Of course, there's a whole lot more the Notify plugin has to offer but I'll leave you with the docs for that!
Here are three other Quasar plugins you'll use all the time:
There's a couple of bits and pieces you'll likely need to know about when configuring a Quasar project. Let's cover them here:
By default, quasar uses "hash mode" for routing because it's easier to deploy. This means all route paths will be prefixed with a hash:
/#/friends/quasarcast
This works fine, but if you'd rather use history mode you can enable it:
quasar.config.js > build
build: {
// ...
vueRouterMode: 'history', // available values: 'hash', 'history'
// ...
}
As your project grows, there's a good chance you'll need to tap into Vite. Especially for adding plugins:
quasar.config.js > build
build: {
vitePlugins: [
['unocss/vite', {}],
]
}
You can also extend Vite's config through build.extendViteConf
There's a couple of reasons you may want to change the port:
9000
is already takenWe can easily change it in our config file:
quasar.config.js > devServer
devServer: {
port: 9001
}
Consider using an environment variable to make the port dynamic:
quasar.config.js > devServer
devServer: {
port: process.env.DEV_SERVER_PORT ?? 9000
}
Devs will often use a server proxy when accessing their api to avoid CORS issues. We can do that with Quasar:
quasar.config.js > devServer
devServer: {
proxy: {
'/api': {
target: '<http://jsonplaceholder.typicode.com>',
changeOrigin: true,
rewrite: (path) => path.replace(/^\\/api/, '')
},
},
},
Setting up testing is a breeze with Quasar! We have official App Extensions (AE’s) for:
The docs for both are fantastic, so we won’t cover installation and usage. The rest of this section assumes both of the above AE’s have been installed.
Cypress includes something called "component tests". This is a lightning fast way to test components in isolation with a gorgeous UI.
If you're new to testing, here's how I recommend you start:
yarn test:e2e
(requires Cypress AE): Write end to end tests (e2e) using cypress. Checking the home page loads, and a logging in test is a good place to startyarn test:component
(requires Cypress AE): When you need to create a custom component - especially a "base" component that other components rely on - use component testsyarn test:unit
or yarn test:unit:ui
(requires Vitest AE): When testing composables, and other "javascripty" stuff!Having said that, it depends on where your pain points are...
Often when learning a new language or framework, it’s important to go wide before going deep. We need an overview of what’s possible so we can effectively read the docs, and know where to look when we hit brick walls (that’s why we covered boot files early in this article… not knowing where main.js
is can be a frustrating brick wall!).
I’m hoping this article provided the width you need, to start diving deep and building the apps of your dreams. I also hope you get to feel the love I’ve felt for Quasar. This framework led me to my dream job, gave me the power to build anything I set my mind to, and gave me a standard to strive for (especially when building my own components). You can learn more about my personal story here.
Thankyou for reading ❤️. I’d like to leave you with a couple places to go next:
Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.
More than 200.000 users have already joined us. You are welcome too!
© All rights reserved. Made with ❤️ by BitterBrains, Inc.