Setup Uitimate
Unlike shadcn/ui, our setup is much easier and requires almost no installation steps. You're ready to go as long as you have:
- a React-based project (e.g., Next.js, Remix, etc.),
- TypeScript, and
- Tailwind CSS v4 enabled.
We've prepared a starter project for you. However, you should still review all the content and follow the setup steps below, as some parts of our setup differ significantly from traditional "installation" or "configuration" processes you may have seen in other documentation.
1️⃣ Configure path alias in your project
By default, our component source code and many demo examples use file paths like:
import ... from "#/..."
Here, # is an alias that must be configured in advance for our code to work in your project.
DEEP DIVE: Why #? Why not @?
Using # is actually the best practice, because it leverages Node.js's native subpath imports ⎯
entries in the "imports" field of your package.json must start with # to distinguish them from package names,
so import foo from '#src/foo.js' works out of the box across Node.js, test runners, and bundlers, without plugins or polyfills.
In contrast, @-based aliases (e.g. @/src) must be manually configured for each tool ⎯
Vite requires explicit setup in the configuration file (and matching paths in tsconfig.json),
while Webpack, ESLint, and others similarly demand custom overrides.
Additionally, @ overlaps with npm's scoped-package syntax (@scope/pkg),
but # is reserved for internal subpath imports and can't clash with external packages,
ensuring consistent resolution and a unified setup across your entire toolchain
(i.e., you can now also use # in your build tool configuration files without hassle!)
Since using # is best practice, we currently don't consider using any other symbol in our code and setup.
Now follow the steps below to set up the corresponding files:
tsconfig.json
Usually you will have this configuration file for the TypeScript, but the name can be slightly different (e.g., maybe it's called tsconfig.app.json) depending on your choice of scaffolding tool (e.g., Vite, Webpack, …).
{
"compilerOptions": {
"paths": {
"#/*": ["app/*"], // doesn't need to be `app`, you may change it to whatever you want (e.g., `src`)
// ...
},
// ...
},
// ...
}
the build tool configuration file
This means that you're using something like Vite or Webpack, so you need to tell it what # means to allow it to analyze and/or compile correctly. Below is an example for Vite, but this is extremely common and easy to do in all other build tools:
// ...
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [
// ...
tsconfigPaths()
],
// ....
});
package.json
{
"imports": {
"#/*": "./app/*" // doesn't need to be `app`, you may change it to whatever you want (e.g., `src`)
// ...
},
// ...
}
2️⃣ Configure path preferences on this site
Many of our code examples use specific import paths (see below). If these paths don't match your local setup, it can be a hassle to use our code without refactoring.
Currently, there're only two path preferences (and probably won't have more in the future), so please follow the instructions below to ensure they match your local environment setup:
import ... from '#/helpers/...'
We built some helpers to make the code more maintainable, so it makes sense to have a folder named helpers to store them. If the name helpers doesn't fit your case, you can adjust it below:
Next, you need to download the "helpers" folder to your project (PLEASE ensure that it is placed under the folder that you configured to represent #.)
Watch how to use "Download helpers" correctly if this is your first time
This is VERY DIFFERENT from traditional "installation" because we use the File System API. You'll see this approach in other places on our site as well.
Your browser will ask for permission—just accept all prompts, and it will literally save files to your local folder (i.e., no zip file involved).
If you're curious why we use this API, the simple answer is: because we adopt a VPM (Virtual Package Management) approach.
In fact, explaining all the relevant concepts would require an article, so a slightly longer answer is: under the VPM architecture, no NPM command can be used to install our components (or the relevant files), so how can you use the component then?
One way is to create a CLI tool to do the work, but that means you need to learn more tools, and it can be more complicated than you expect depending on your development environment. That can be frustrating! (note: it's freaking frustrated to me when I used shadcn/ui's whatever installation or setup)
That's why we use the File System API: you can literally just do a few clicks ⎯ and often just one click ⎯ and the component will be in your project. Incredibly easy!
You might ask, "But does using that API mean it can download the component regardless of the development environment's complexity?"
Well, we've certainly found that it's much better, and even though there are some complexities that aren't covered, this feature is evolving, so it shouldn't be a problem! Plus, we use that API for other purposes as well, such as in the future, making you get the component (or the relevant info/files) update without any hassle.
import ... from '#/components/ui/...'
The pathcomponents/ui is the default. In this structure,components means you only place component-related files under that folder, and ui stands for the component library, which is where we recommend you store all our components. If the default doesn't fit your case, you can adjust it below:
3️⃣ Download base dependencies
Since our code uses some dependencies under the hood, you need to install them in your project:
4️⃣ Configure Tailwind
Make sure you've set up Tailwind CSS v4 in your project, and copy the following configuration into a CSS file. You can choose where to place and import this file, but it must be included in your project (typically, it should be imported in your application's entry file). Otherwise, all our components will not be styled correctly.
@import 'tailwindcss' prefix(tw);
@plugin 'tailwindcss-animate';
@plugin "@tailwindcss/typography";
@custom-variant dark (&:where(.dark, .dark *));
/**
The reason we link the vars defined under `@layer base` is simply because the current limitation of `@theme`:
cuz we can't declare a class under `@theme` such as `.dark {...}`, otherwise we can simply do stuff like:
```
.dark {
--color-background: var(--tw-color-white);
}
```
*/
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
}
/**
Currently, all the Shadcn vars defined under :root and .dark are only meant to be used in the `@theme` block.
So you don't really access these vars in other places.
*/
@layer base {
/**
Once the var is defined at here, it can be used literally anywhere,
even in `@theme` which could be declared before this; this is because
the var-resolving works at runtime.
It doesn't matter where you define it, cuz it gets evaluated when the style is applied.
eg.,
For below declartion in `@theme`,
```
--color-sidebar: hsl(var(--sidebar-background))
```
`--sidebar-background` will only be resolved when browser is rendering `--color-sidebar`
*/
:root {
--popover: var(--tw-color-white);
--popover-foreground: var(--tw-color-zinc-950);
--background: var(--tw-color-white);
--foreground: var(--tw-color-zinc-950);
--primary: var(--tw-color-zinc-900);
--primary-foreground: var(--tw-color-zinc-50);
--secondary: var(--tw-color-zinc-100);
--secondary-foreground: var(--tw-color-zinc-900);
--muted: var(--tw-color-zinc-100);
--muted-foreground: var(--tw-color-zinc-500);
--accent: var(--tw-color-zinc-100);
--accent-foreground: var(--tw-color-zinc-900);
--destructive: var(--tw-color-red-500);
--destructive-foreground: var(--tw-color-zinc-50);
--border: var(--tw-color-zinc-200);
--input: var(--tw-color-zinc-200);
--ring: var(--tw-color-zinc-950);
--chart-1: var(--tw-color-sky-600);
--chart-2: var(--tw-color-green-600);
--chart-3: var(--tw-color-orange-600);
--chart-4: var(--tw-color-indigo-600);
--chart-5: var(--tw-color-red-600);
--radius: 0.5rem;
}
/**
This means that when html element has class `dark`, the following vars will be applied.
*/
.dark {
--popover: var(--tw-color-zinc-950);
--popover-foreground: var(--tw-color-zinc-50);
--background: var(--tw-color-zinc-950);
--foreground: var(--tw-color-zinc-50);
--primary: var(--tw-color-zinc-50);
--primary-foreground: var(--tw-color-zinc-900);
--secondary: var(--tw-color-zinc-800);
--secondary-foreground: var(--tw-color-zinc-50);
--muted: var(--tw-color-zinc-800);
--muted-foreground: var(--tw-color-zinc-400);
--accent: var(--tw-color-zinc-800);
--accent-foreground: var(--tw-color-zinc-50);
--destructive: var(--tw-color-red-900);
--destructive-foreground: var(--tw-color-zinc-50);
--border: var(--tw-color-zinc-800);
--input: var(--tw-color-zinc-800);
--ring: var(--tw-color-zinc-300);
--chart-1: var(--tw-color-sky-400);
--chart-2: var(--tw-color-green-400);
--chart-3: var(--tw-color-orange-400);
--chart-4: var(--tw-color-indigo-400);
--chart-5: var(--tw-color-red-400);
}
/*
After upgraded to Tailwind v4, this is one of few styles provided by Tailwind as default:
The default border color has changed to `currentColor` in v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--tw-color-gray-200, currentColor);
}
* {
/*
This can be confused, but "the 2nd border" comes from `--color-border`
*/
@apply tw:border-border;
}
body {
@apply tw:bg-background tw:text-foreground;
}
}
/*
Only use this way that the custom class can be used with the prefix: `tw:flex-center`.
(ie., you can't use `@layer utilities` to have the prefix)
*/
@utility flex-center {
@apply tw:flex tw:items-center tw:justify-center;
}Notice that we use the prefix tw in the code above (short for Tailwind), which we recommend as best practice: all your Tailwind classes should be used by prepending tw: (e.g., tw:text-center).
5️⃣ Party time 🎉
Now, just head to any of our component pages to explore. If you want to use a component, go to its setup page and follow the instructions. You're ready to start using Uitimate!