Building with Tailwind CSS
Gadget web apps come pre-configured with Tailwind CSS v4, the latest version of the popular utility-first CSS framework. Tailwind is integrated with the shadcn component system and ready to use out of the box.
Learn more
For more information about Tailwind CSS v4, visit the official Tailwind CSS documentation.
Migrating from Tailwind v3 to v4
If you have an existing Gadget app using Tailwind v3, this guide will walk you through upgrading to v4. For comprehensive details on all changes between versions, see the official Tailwind v4 upgrade guide.
Tailwind provides an upgrade CLI tool that can automate parts of this migration. However, Gadget apps have specific configurations that require the manual steps below.
Migration steps
1. Update dependencies
First, remove the old Tailwind v3 dependencies that are no longer needed:
terminal yarn remove tailwindcss-animate postcss autoprefixer
terminal yarn add @tailwindcss/vite@latest tailwindcss@latest tw-animate-css
2. Upgrade @gadgetinc/react
Update @gadgetinc/react to version 0.24.0 or higher. This version includes the necessary CSS for shadcn autocomponents in Tailwind v4.
3. Note your custom configuration
Before removing your config file, review your tailwind.config.js (or tailwind.config.ts) and note any custom configuration you've added, such as:
- Custom colors
- Custom spacing values
- Custom fonts
- Plugin configurations
You'll migrate these to CSS in a later step.
4. Remove tailwind.config.js
Delete your tailwind.config.js (or tailwind.config.ts) file. The configuration will now live in your CSS file.
5. Update your CSS file
Replace your existing web/app.css (or equivalent) with the new v4 format.
Here's a complete example of a v4-compatible app.css:
css/* web/app.css */ @import "tailwindcss"; @import "@gadgetinc/react/auto/shadcn.css"; @import "tw-animate-css"; @custom-variant dark (&:is(.dark *)); @theme inline { --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); --color-card-foreground: var(--card-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-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); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); } :root { --radius: 0.625rem; --background: oklch(1 0 0); --foreground: oklch(0.141 0.005 285.823); --card: oklch(1 0 0); --card-foreground: oklch(0.141 0.005 285.823); --popover: oklch(1 0 0); --popover-foreground: oklch(0.141 0.005 285.823); --primary: oklch(0.21 0.006 285.885); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.967 0.001 286.375); --secondary-foreground: oklch(0.21 0.006 285.885); --muted: oklch(0.967 0.001 286.375); --muted-foreground: oklch(0.552 0.016 285.938); --accent: oklch(0.967 0.001 286.375); --accent-foreground: oklch(0.21 0.006 285.885); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.92 0.004 286.32); --input: oklch(0.92 0.004 286.32); --ring: oklch(0.705 0.015 286.067); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); --chart-4: oklch(0.828 0.189 84.429); --chart-5: oklch(0.769 0.188 70.08); --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823); --sidebar-primary: oklch(0.21 0.006 285.885); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.967 0.001 286.375); --sidebar-accent-foreground: oklch(0.21 0.006 285.885); --sidebar-border: oklch(0.92 0.004 286.32); --sidebar-ring: oklch(0.705 0.015 286.067); } .dark { --background: oklch(0.141 0.005 285.823); --foreground: oklch(0.985 0 0); --card: oklch(0.21 0.006 285.885); --card-foreground: oklch(0.985 0 0); --popover: oklch(0.21 0.006 285.885); --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.985 0 0); --primary-foreground: oklch(0.21 0.006 285.885); --secondary: oklch(0.274 0.006 286.033); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.274 0.006 286.033); --muted-foreground: oklch(0.705 0.015 286.067); --accent: oklch(0.274 0.006 286.033); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(0.274 0.006 286.033); --input: oklch(0.274 0.006 286.033); --ring: oklch(0.442 0.017 285.786); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); --chart-4: oklch(0.627 0.265 303.9); --chart-5: oklch(0.645 0.246 16.439); --sidebar: oklch(0.21 0.006 285.885); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.274 0.006 286.033); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(0.274 0.006 286.033); --sidebar-ring: oklch(0.442 0.017 285.786); } @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground font-sans antialiased; } }
The @import "@gadgetinc/react/auto/shadcn.css" directive is required if you are using Gadget's shadcn autocomponents. Make sure you have
@gadgetinc/react version 0.24.0 or higher.
6. Migrate custom theme configuration
If you had custom theme configuration in your old tailwind.config.js, migrate it to the @theme directive in your CSS.
Old (tailwind.config.js):
New (app.css):
css/* Add to your @theme block in web/app.css */ @theme { --color-brand-50: #eff6ff; --color-brand-100: #dbeafe; --spacing-128: 32rem; }
7. Remove postcss config and use the Tailwind Vite plugin
Delete your postcss.config.js file if you have one.
Update your vite.config.ts to use the Tailwind Vite plugin:
8. Remove reset.min.css
In your web/root.tsx file, remove the reset.min.css stylesheet link if present:
Tailwind v4 includes its own preflight styles, so this external reset is no longer needed.
9. Update ChatGPT widget CSS (if applicable)
If your app has a ChatGPT connection with a custom widget, update the import syntax in web/chatgpt/chatgpt.css:
Old:
css@import url("../app.css");
New:
css@import "../app.css";
The url() function is not compatible with Tailwind v4's CSS imports.
Breaking changes
For a complete list of breaking changes and deprecated features, see the official Tailwind v4 upgrade guide.