How to Add Shadcn/UI to an Electron-Vite App in 5 Easy Steps
In this easy-to-follow guide, I’ll show you how to set up Shadcn/UI in an Electron-Vite project from scratch, in 5 simple steps.
If you’re building cross-platform desktop apps using JavaScript, React, or TypeScript, chances are you’ve tried Electron. But setting it up from scratch can be complex, especially when you want a fast, modern developer experience.
That’s where Electron-Vite comes in — a powerful boilerplate that combines the simplicity of Vite with the power of Electron. It makes development lightning-fast and much easier to configure. I use it in most of my projects because:
- It’s fast (thanks to Vite)
- It supports TypeScript and React out of the box
- It has a clean, modern structure with separate folders for main, preload, and renderer processes
- It’s easy to extend with things like Tailwind and UI libraries (like I’ll do today)
What is Shadcn and Why Use It?
Shadcn/UI is a modern component library for React built using Radix UI, Tailwind CSS, and other open-source tools.
Here’s why it’s fantastic:
- It gives you beautiful, accessible UI components out of the box
- It’s customizable — everything is just React + Tailwind
- You own the code — Shadcn copies components into your project, not a package dependency
Shadcn works great in React and Vite projects. However, setting it up in an Electron-Vite app isn’t entirely straightforward, and that’s precisely why I’m writing this guide.
Setting Up Shadcn in an Electron-Vite Project (From Scratch)
Let’s start from zero. You’ll have a working Electron desktop app styled with Tailwind CSS and Shadcn/UI by the end.
Step 1: Create a New Electron Project
I’ll use the official quick-start template from @quick-start/electron.
npm create @quick-start/electron
Follow the prompts (I usually go with the default settings). Then run:
cd <project_name>
npm install
npm run dev
This will launch a basic Electron app using Vite. You’re now ready to customize it.
Step 2: Add Tailwind CSS
Tailwind is a utility-first CSS framework required for Shadcn components to work.
Install Tailwind dependencies:
npm install -D tailwindcss @tailwindcss/vite
Then, update your electron.vite.config.ts
to include Tailwind in the renderer process:
// electron.vite.config.ts
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {
resolve: {
alias: {
'@renderer': resolve('src/renderer/src')
}
},
plugins: [react(), tailwindcss()]
}
})
Update the file src/renderer/src/assets/main.css
by adding at the top:
@import "tailwindcss";
Make sure this file is imported into your main renderer entry file (src/renderer/src/main.tsx
). If you are using a different CSS file in your project, update that one instead.
Step 3: Prepare for Shadcn Setup
Before initializing Shadcn, we need to adjust the TypeScript and Vite configs to avoid path errors.
Update tsconfig.node.json
to look like this:
{
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"],
"compilerOptions": {
"composite": true,
"types": ["electron-vite/node"],
"baseUrl": ".",
"paths": {
"@/*": ["./src/renderer/src/*"]
},
"moduleResolution": "bundler"
}
}
Create the file vite.config.ts
. Shadcn expects a vite.config.ts
file, so copy the contents of your electron.vite.config.ts
into a new file called vite.config.ts
in the project root.
Now, update tsconfig.json
to make sure the main config also includes path aliases:
{
"files": [],
"references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }],
"compilerOptions": {
"composite": true,
"types": ["electron-vite/node"],
"baseUrl": ".",
"paths": {
"@/*": ["./src/renderer/src/*"]
}
}
}
Step 4: Initialize Shadcn
Now you’re ready to install Shadcn!
npx shadcn@latest init
Follow the prompts. It will ask about your preferred style (the default is default
). Take a look at the options on the official Shadcn style page.
This will create some files. Edit the created components.json
file so that the aliases
part looks like:
"aliases": {
"components": "@/components",
"utils": "@renderer/lib/utils",
"ui": "@/components/ui",
"lib": "@renderer/lib",
"hooks": "@renderer/hooks"
},
Step 5: Add Your First Shadcn Component
Let’s test everything by adding a button:
npx shadcn@latest add button
This will create a file like src/renderer/src/components/ui/button.tsx
. You can now use it in your React components.
Note: Make sure the utility function
cn
is correctly imported from @renderer/lib/utils
You’re Done!
Now run your app:
npm run dev
You can now edit your App.tsx
file, adding a button, like this:
import { Button } from '@renderer/components/ui/button'
function App(): React.JSX.Element {
return (
<div className="flex flex-col items-center justify-center min-h-svh">
<Button variant="default" size="lg">
ShadCN Button
</Button>
</div>
)
}
export default App
And you’ll see a beautiful red button in the middle of the screen!
Note: not seeing the cursor as a pointer when hovering over the button? This is because of Tailwind v4 updates, as explained here: https://ui.shadcn.com/docs/tailwind-v4 (Buttons now use the default cursor). Also, take a look at this issue, https://github.com/shadcn-ui/ui/issues/6843
Pro Tip: Avoid Duplicating Vite Configs
If you’ve been following along, you probably noticed you ended up with two config files for Vite: electron.vite.config.ts
and vite.config.ts
. We could use only vite.config.ts
and tell electron-vite
to use that as the main config file. This will require some changes:
Update the package.json
scripts to tell Electron Vite to use the custom config file. Add --config ./vite.config.ts
to all relevant scripts:
"scripts": {
"format": "prettier --write .",
"lint": "eslint --cache .",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite --config ./vite.config.ts preview",
"dev": "electron-vite --config ./vite.config.ts dev",
"build": "npm run typecheck && electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win",
"build:mac": "electron-vite --config ./vite.config.ts build && electron-builder --mac",
"build:linux": "electron-vite --config ./vite.config.ts build && electron-builder --linux"
}
Now, update the electron-builder.yml
file to make sure to omit the now-unused electron.vite.config.ts
in the packaged app. So, in the electron-builder.yml
, remove this line:
- '!electron.vite.config.{js,ts,mjs,cjs}'
And replace it with this:
- '!vite.config.{js,ts,mjs,cjs}'
So it’ll look something like this:
files:
- '!**/.vscode/*'
- '!src/*'
- '!vite.config.{js,ts,mjs,cjs}' # This is the one you want now
- '!{.eslintcache,eslint.config.mjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
And finally, fix the tsconfig.node.json
to tell TypeScript to include the new config file instead of the old one.
"include": ["vite.config.ts", "src/main/**/*", "src/preload/**/*"]
We can now safely delete electron.vite.config.ts
(or archive it, just in case). The whole app will use vite.config.ts
, and we’ll never have to deal with duplicated config again.
Final Thoughts
Getting Shadcn/UI working in an Electron-Vite app isn’t too hard, but there are a few extra steps beyond a typical React/Vite project.
This guide walks you through everything step-by-step to:
- Set up Electron-Vite
- Add Tailwind CSS
- Configure TypeScript correctly
- Initialize Shadcn/UI
- Use your first component
Now you can build great-looking desktop apps with powerful UI and modern dev tools.