npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vuetiwatch

v0.2.2

Published

Free, plug-and-play themes for Vuetify 4 — a Bootswatch for Vuetify

Readme

Vuetiwatch

npm bundle license

🎨 Free, plug-and-play themes for Vuetify 4 — what Bootswatch is to Bootstrap.

The same landing page in nine of the nineteen Vuetify 4 themes

See all nineteen themes →

Vuetify ships one look. Vuetiwatch ships nineteen, and they differ in more than hue: radius, border weight, density, component variants, typography, icons and motion all move together.

npm i vuetiwatch      # or: bun add / yarn add / pnpm add

Themes

| Name | Mode | Character | | --- | --- | --- | | classic | light | Stock Vuetify. Material Design, Roboto, familiar elevation | | paper | light | Flat editorial — warm paper, ink accents, hairline borders, serif headings | | slate | light | Dense dashboard — muted slate blue, compact density, outlined cards | | atlas | light | Calm admin — soft neutrals, one cool accent, dense tables, hairlines not shadows | | atlasDark | dark | The same panel after dark — soft greys, never pure black | | atlasSepia | light | The same panel on warm paper and brown ink, for tired eyes | | calm | light | Low contrast, desaturated naturals, no shadows, a lot of air | | lux | light | Editorial luxury — hairline rules, wide tracking, small caps, muted gold | | soft | light | Pastel and generous — large radii, diffuse tinted shadows, hearts for stars | | candy | light | Plush and pillowy — warm cream, padded shapes, controls that squash | | clay | light | Inflated pastel shapes with a triple shadow. Toy-like | | morph | light | Neumorphic — one continuous surface, shaped only by light and shadow | | sketchy | light | Hand-drawn wobble and pencil greys, for mockups and mirth | | brutalist | light | Thick black rules, acid lime, hard unblurred shadows | | liquidGlass | light | Capsule controls and refracting panes over a calm wash, after iOS 26 | | darkGlass | dark | Frosted translucent surfaces on a deep violet ground | | aurora | dark | Iridescent gradients on near-black — the showcase theme | | neon | dark | Terminal black, cyan and magenta glow, zero radius, monospace headings |

classic is the control. It opts out of the core stylesheet entirely — not one rule in this package matches a page running it — so switching to it shows exactly what the other eighteen add.

How to install and apply a theme in Vuetify 4

Register the themes in createVuetify, then add the plugin. defaultTheme picks the one that loads; useTheme().change(name) switches at runtime.

1. Register the themes. They are plain Vuetify ThemeDefinitions.

// plugins/vuetify.ts
import { createVuetify } from 'vuetify'
import { vuetiwatchThemes } from 'vuetiwatch'

import 'vuetify/styles'
import 'vuetiwatch/styles.css'

export const vuetify = createVuetify({
  theme: { defaultTheme: 'paper', themes: vuetiwatchThemes },
})

2. Install the plugin, after Vuetify.

// main.ts
import { createVuetiwatch, themeList } from 'vuetiwatch'

createApp(App)
  .use(vuetify)
  .use(createVuetiwatch(vuetify, { themes: themeList }))
  .mount('#app')

That is the whole setup. Step 1 alone gives you colours and fonts; the stylesheet and the plugin add everything a ThemeDefinition cannot express — radius, border weight, component variants, icons and per-theme surface treatment.

The plugin swaps Vuetify's global component defaults when the theme changes, since those are global rather than per-theme. Your own createVuetify({ defaults }) is preserved; theme defaults layer on top. It also exposes useVuetiwatch() — see switching themes — and accepts defaults, attribute and transitions to turn any of it off.

Switching themes

Vuetify's own theme.change() works as it always did. The plugin adds one thing on top of it:

<script setup lang="ts">
import { useVuetiwatch } from 'vuetiwatch'

const { themes, current, change } = useVuetiwatch()
</script>

<template>
  <button v-for="t in themes" :key="t.name" @click="change(t.name, $event)">
    {{ t.meta.title }}
  </button>
</template>

useVuetiwatch() also returns siblings: the variants of the theme that is running, or an empty array when it has none. A theme declares what it belongs to through meta.family, so a light/dark switch never has to know which themes go together:

<script setup lang="ts">
const { siblings, current, change } = useVuetiwatch()
</script>

<template>
  <v-btn-toggle v-if="siblings.length > 1" :model-value="current?.name">
    <v-btn
      v-for="t in siblings"
      :key="t.name"
      :value="t.name"
      :text="t.meta.variant"
      @click="change(t.name, $event)"
    />
  </v-btn-toggle>
</template>

A theme can also offer accents — named colour sets it repaints itself in, declared in meta.accents and applied through setAccent():

<script setup lang="ts">
const { accents, accent, setAccent } = useVuetiwatch()
</script>

<template>
  <button
    v-for="item in accents"
    :key="item.id"
    :style="{ background: item.colors.primary }"
    @click="setAccent(item.id)"
  />
</template>

Presets rather than a colour picker: every one is measured against the theme's ground, and the id is shared across the family, so the choice survives a light/dark switch.

Pass the event and the new theme opens as a circle from the control that was pressed, through the View Transitions API — Baseline since October 2025. Where the API is missing, where the visitor asked for reduced motion, or where you passed transitions: false, the same call switches instantly, so it is always safe to use. Each theme sets its own pace through vw-theme-transition: neon repaints in 180 ms, calm takes 560.

Motion

Movement is the fourth axis these themes travel on, after colour, shape and type. brutalist snaps in two steps, lux eases over a quarter-second, candy overshoots and settles — and a checkbox, a toggle button or a rating star pops when it turns on, the way a like button does.

None of it costs a byte of JavaScript: themes state a character in a handful of variables and the core layer spends it on the same interactions everywhere. Only transform is animated, so it stays on the compositor. prefers-reduced-motion: reduce zeroes the variables themselves in one place, which covers every theme at once — including one you wrote yourself.

Fonts

Themes name the families they want but never load them — that stays your call. meta.fonts lists what each expects, and every family has a system fallback, so skipping this degrades gracefully rather than breaking.

<link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">

Shipping only a few themes

Pass just those to the plugin and import their stylesheets instead of the combined one. The whole stylesheet is about 6.6 kB gzipped; the core layer plus one theme is under 3 kB.

import { createVuetiwatch, paper } from 'vuetiwatch'
import 'vuetiwatch/styles/core.css'
import 'vuetiwatch/styles/paper.css'

app.use(createVuetiwatch(vuetify, { themes: [paper] }))

core.css is always needed. A theme file exists only where a theme needs more than its ThemeDefinition can express, so classic has none. Every file also ships as *.min.css.

Writing your own theme

defineTheme() takes a name, the meta a picker needs, a Vuetify ThemeDefinition and the component defaults that go with it; everything left out is inherited from Vuetify's own light or dark. Register it alongside the rest, and it behaves like a theme that shipped with the package — including the switch animation and the light/dark family.

Documentation

  • Writing your own themedefineTheme, overlay transitions, text selection, per-theme icons
  • Theme variables — every vw-* custom property the stylesheet reads, and what it moves
  • What wins — props, theme defaults, your defaults, Vuetify, and where your own CSS lands
  • Contrast — how the palettes are measured against WCAG 2 and APCA, and the one stated exception

Requirements

  • Vuetify 4.0+. v3 is not supported: runtime font switching (--v-font-body, --v-font-heading) and shadow-color exist only in v4, and without them the themes lose most of what separates them.
  • Vue 3.5+

License

MIT