Vue and Tailwind
Summary
The preset-vue item brings the pmndrs colours, fonts and radius to Vue apps
using Tailwind v4. Start with the preset, then add shadcn-vue components and
shared resources as you need them.
New project
Create a Vue and Vite app with the pmndrs preset:
npx shadcn-vue@latest init --preset https://pmndrs.github.io/design-system/r/preset-vue.json \
--template vite --name my-app
cd my-app
Use the full URL for this custom --preset; shadcn-vue uses its own registry
directory.
One command sets up the app, Tailwind and shadcn-vue, installs Inter and
Inconsolata, applies the light and dark palette, and configures @pmndrs.
First component
Add a button:
pnpm dlx shadcn-vue@latest add buttonReplace src/App.vue with a card and a dark-mode toggle:
<script setup lang="ts">
import { ref } from 'vue'
import { Button } from '@/components/ui/button'
const dark = ref(false)
</script>
<template>
<main :class="{ dark }" class="min-h-screen bg-background p-8 text-foreground">
<section class="flex flex-col items-start gap-4 rounded-lg bg-surface-dim p-6">
<h1 class="text-2xl font-semibold">Hello, Poimandres</h1>
<p class="text-on-surface-variant">Shared colours, ready to use.</p>
<Button type="button" :aria-pressed="dark" @click="dark = !dark">
Dark mode
</Button>
</section>
</main>
</template>
The .dark class switches the palette for this root element and its children.
Standard shadcn-vue classes work alongside Material Design 3 colour roles such
as bg-surface-dim and text-on-surface-variant.
Shared resources
The preset configures @pmndrs; add the same logo and Figma tokens used by other
integrations:
pnpm dlx shadcn-vue@latest add @pmndrs/logo @pmndrs/figma-tokensFollow the guidelines and explore the registry catalog. Each item has its own environment and dependencies.
The hosted URLs follow main. preset-vue and palette are new items;
older releases may not include them. See
Versioning before pinning resources.
Use Vue with standard CSS
Use the plain CSS palette and fonts in a Vue app whose components manage their own styles:
Create the app
Start with Vue's official Vite scaffold:
npm create vue@latest my-app -- --typescript
cd my-app
npm install
npm install @fontsource-variable/inter @fontsource-variable/inconsolata
Add the palette
Install the palette item into the app's assets:
pnpm dlx shadcn-vue@latest add https://pmndrs.github.io/design-system/r/palette.jsonThe file contains --md-sys-color-* variables under :root and .dark.
It has no framework dependency. The hosted item follows main.
Replace the starter ./assets/main.css import in src/main.ts with the
fonts and palette:
import '@fontsource-variable/inter'
import '@fontsource-variable/inconsolata'
import './assets/pmndrs.css'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
Use the colour roles
Replace src/App.vue with a card and a dark-mode toggle:
<script setup lang="ts">
import { ref } from 'vue'
const dark = ref(false)
</script>
<template>
<main :class="{ dark }">
<section class="card">
<h1>Hello, Poimandres</h1>
<p>Shared colours, used through <code>CSS variables</code>.</p>
<button type="button" :aria-pressed="dark" @click="dark = !dark">
Dark mode
</button>
</section>
</main>
</template>
<style>
body { margin: 0; }
main {
--radius: 0.625rem;
min-height: 100dvh;
box-sizing: border-box;
padding: 2rem;
font-family: 'Inter Variable', sans-serif;
background: var(--md-sys-color-background);
color: var(--md-sys-color-on-background);
}
.card {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1rem;
padding: 1.5rem;
border-radius: var(--radius);
background: var(--md-sys-color-surface-container-low);
color: var(--md-sys-color-on-surface);
}
h1, p { margin: 0; }
h1 { font-size: 1.5rem; font-weight: 600; }
button {
padding: 0.5rem 1rem;
border: 0;
border-radius: var(--radius);
background: var(--md-sys-color-lime-container);
color: var(--md-sys-color-on-lime-container);
font: inherit;
cursor: pointer;
}
code { font-family: 'Inconsolata Variable', monospace; }
</style>
The .dark class switches the variables on the app's root element; its children
inherit the selected scheme. The spacing and radius follow the shared
foundations, with Inter and Inconsolata.
Run npm run dev to see it.