# 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](https://tailwindcss.com). ## 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](https://tailwindcss.com/docs/upgrade-guide). Tailwind provides an [upgrade CLI tool](https://tailwindcss.com/docs/upgrade-guide#using-the-upgrade-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: ```bash yarn remove tailwindcss-animate postcss autoprefixer ``` ```bash 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`):** ```typescript // tailwind.config.js module.exports = { theme: { extend: { colors: { brand: { 50: "#eff6ff", 100: "#dbeafe", }, }, spacing: { 128: "32rem", }, }, }, }; ``` **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: ```typescript // vite.config.ts import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [tailwindcss()], }); ``` #### 8\. Remove reset.min.css  In your `web/root.tsx` file, remove the reset.min.css stylesheet link if present: ```tsx // Remove this from your links function in web/root.tsx export const links = () => [{ rel: "stylesheet", href: "https://assets.gadget.dev/assets/reset.min.css" }]; ``` 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](https://tailwindcss.com/docs/upgrade-guide).