Skip to content

Commit 6a1132a

Browse files
feat: upgrade to Tailwind CSS v4 with CSS-first configuration
- Remove packages/config package (no longer needed with Tailwind v4) - Convert JavaScript theme config to CSS @theme directive in globals.css - Update tsconfig.json to remove config package references - Simplify tailwind.config.ts to empty object (CSS-first approach) - Add React Router 7 routes.ts file for proper routing - Update documentation to reflect new CSS-first approach - Maintain full compatibility with shadcn/ui components - Support dark mode using @custom-variant and @variant directives This migration follows Tailwind v4's recommended CSS-first configuration approach, eliminating the need for JavaScript config files while maintaining all existing functionality.
1 parent 4ea41cc commit 6a1132a

File tree

16 files changed

+1337
-159
lines changed

16 files changed

+1337
-159
lines changed

.cursorrules/monorepo.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import type { Route } from './+types/home';
3232
- Prefer package-specific scripts for development
3333

3434
## TypeScript Configuration
35-
- Extend base config from `@todo-starter/config`
35+
- Use Tailwind CSS v4's CSS-first configuration with `@theme` directive
3636
- Use path mapping for workspace packages
3737
- Keep tsconfig.json files minimal and focused
3838

@@ -41,4 +41,3 @@ import type { Route } from './+types/home';
4141
- Avoid circular dependencies between packages
4242
- Use proper exports in package.json
4343
- Document package APIs and usage patterns
44-

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ bun dev
7777
### Monorepo Structure
7878
This project uses a monorepo structure with the following packages:
7979

80-
- **@todo-starter/config** - Shared configurations (Tailwind, TypeScript)
80+
8181
- **@todo-starter/ui** - Component library based on shadcn/ui
8282
- **@todo-starter/utils** - Shared utilities, types, and helpers
8383

@@ -95,6 +95,17 @@ Components are organized by feature and follow these principles:
9595
- Proper TypeScript typing
9696
- Accessible by default
9797

98+
## 🎨 Styling
99+
100+
This project uses **Tailwind CSS v4** with CSS-first configuration for modern, efficient styling.
101+
102+
### Tailwind v4 Configuration
103+
- **CSS-first approach**: Theme configuration is defined directly in CSS using the `@theme` directive
104+
- **No JavaScript config needed**: Tailwind v4 eliminates the need for `tailwind.config.js` files in most cases
105+
- **Theme variables**: All design tokens (colors, spacing, etc.) are defined in `apps/todo-app/app/globals.css`
106+
- **Dark mode support**: Uses `@custom-variant` and `@variant` directives for theme switching
107+
- **shadcn/ui compatible**: Maintains full compatibility with shadcn/ui components
108+
98109
## 🧪 Testing
99110

100111
The project includes comprehensive testing setup:
@@ -186,4 +197,3 @@ The app supports:
186197
## 📄 License
187198

188199
This project is licensed under the MIT License - see the LICENSE file for details.
189-
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Generated by React Router
2+
3+
import "react-router";
4+
5+
declare module "react-router" {
6+
interface Future {
7+
unstable_middleware: false
8+
}
9+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Generated by React Router
2+
3+
import "react-router"
4+
5+
declare module "react-router" {
6+
interface Register {
7+
pages: Pages
8+
routeFiles: RouteFiles
9+
}
10+
}
11+
12+
type Pages = {
13+
"/": {
14+
params: {};
15+
};
16+
};
17+
18+
type RouteFiles = {
19+
"root.tsx": {
20+
id: "root";
21+
page: "/";
22+
};
23+
"routes/home.tsx": {
24+
id: "routes/home";
25+
page: "/";
26+
};
27+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Generated by React Router
2+
3+
declare module "virtual:react-router/server-build" {
4+
import { ServerBuild } from "react-router";
5+
export const assets: ServerBuild["assets"];
6+
export const assetsBuildDirectory: ServerBuild["assetsBuildDirectory"];
7+
export const basename: ServerBuild["basename"];
8+
export const entry: ServerBuild["entry"];
9+
export const future: ServerBuild["future"];
10+
export const isSpaMode: ServerBuild["isSpaMode"];
11+
export const prerender: ServerBuild["prerender"];
12+
export const publicPath: ServerBuild["publicPath"];
13+
export const routeDiscovery: ServerBuild["routeDiscovery"];
14+
export const routes: ServerBuild["routes"];
15+
export const ssr: ServerBuild["ssr"];
16+
export const unstable_getCriticalCss: ServerBuild["unstable_getCriticalCss"];
17+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Generated by React Router
2+
3+
import type { GetInfo, GetAnnotations } from "react-router/internal";
4+
5+
type Module = typeof import("../root.js")
6+
7+
type Info = GetInfo<{
8+
file: "root.tsx",
9+
module: Module
10+
}>
11+
12+
type Matches = [{
13+
id: "root";
14+
module: typeof import("../root.js");
15+
}];
16+
17+
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }>;
18+
19+
export namespace Route {
20+
// links
21+
export type LinkDescriptors = Annotations["LinkDescriptors"];
22+
export type LinksFunction = Annotations["LinksFunction"];
23+
24+
// meta
25+
export type MetaArgs = Annotations["MetaArgs"];
26+
export type MetaDescriptors = Annotations["MetaDescriptors"];
27+
export type MetaFunction = Annotations["MetaFunction"];
28+
29+
// headers
30+
export type HeadersArgs = Annotations["HeadersArgs"];
31+
export type HeadersFunction = Annotations["HeadersFunction"];
32+
33+
// unstable_middleware
34+
export type unstable_MiddlewareFunction = Annotations["unstable_MiddlewareFunction"];
35+
36+
// unstable_clientMiddleware
37+
export type unstable_ClientMiddlewareFunction = Annotations["unstable_ClientMiddlewareFunction"];
38+
39+
// loader
40+
export type LoaderArgs = Annotations["LoaderArgs"];
41+
42+
// clientLoader
43+
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
44+
45+
// action
46+
export type ActionArgs = Annotations["ActionArgs"];
47+
48+
// clientAction
49+
export type ClientActionArgs = Annotations["ClientActionArgs"];
50+
51+
// HydrateFallback
52+
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
53+
54+
// Component
55+
export type ComponentProps = Annotations["ComponentProps"];
56+
57+
// ErrorBoundary
58+
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
59+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Generated by React Router
2+
3+
import type { GetInfo, GetAnnotations } from "react-router/internal";
4+
5+
type Module = typeof import("../home.js")
6+
7+
type Info = GetInfo<{
8+
file: "routes/home.tsx",
9+
module: Module
10+
}>
11+
12+
type Matches = [{
13+
id: "root";
14+
module: typeof import("../../root.js");
15+
}, {
16+
id: "routes/home";
17+
module: typeof import("../home.js");
18+
}];
19+
20+
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }>;
21+
22+
export namespace Route {
23+
// links
24+
export type LinkDescriptors = Annotations["LinkDescriptors"];
25+
export type LinksFunction = Annotations["LinksFunction"];
26+
27+
// meta
28+
export type MetaArgs = Annotations["MetaArgs"];
29+
export type MetaDescriptors = Annotations["MetaDescriptors"];
30+
export type MetaFunction = Annotations["MetaFunction"];
31+
32+
// headers
33+
export type HeadersArgs = Annotations["HeadersArgs"];
34+
export type HeadersFunction = Annotations["HeadersFunction"];
35+
36+
// unstable_middleware
37+
export type unstable_MiddlewareFunction = Annotations["unstable_MiddlewareFunction"];
38+
39+
// unstable_clientMiddleware
40+
export type unstable_ClientMiddlewareFunction = Annotations["unstable_ClientMiddlewareFunction"];
41+
42+
// loader
43+
export type LoaderArgs = Annotations["LoaderArgs"];
44+
45+
// clientLoader
46+
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
47+
48+
// action
49+
export type ActionArgs = Annotations["ActionArgs"];
50+
51+
// clientAction
52+
export type ClientActionArgs = Annotations["ClientActionArgs"];
53+
54+
// HydrateFallback
55+
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
56+
57+
// Component
58+
export type ComponentProps = Annotations["ComponentProps"];
59+
60+
// ErrorBoundary
61+
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
62+
}

apps/todo-app/app/globals.css

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,58 @@
11
@import 'tailwindcss';
22

3-
@layer base {
4-
:root {
5-
--background: 0 0% 100%;
6-
--foreground: 222.2 84% 4.9%;
7-
--card: 0 0% 100%;
8-
--card-foreground: 222.2 84% 4.9%;
9-
--popover: 0 0% 100%;
10-
--popover-foreground: 222.2 84% 4.9%;
11-
--primary: 222.2 47.4% 11.2%;
12-
--primary-foreground: 210 40% 98%;
13-
--secondary: 210 40% 96%;
14-
--secondary-foreground: 222.2 47.4% 11.2%;
15-
--muted: 210 40% 96%;
16-
--muted-foreground: 215.4 16.3% 46.9%;
17-
--accent: 210 40% 96%;
18-
--accent-foreground: 222.2 47.4% 11.2%;
19-
--destructive: 0 84.2% 60.2%;
20-
--destructive-foreground: 210 40% 98%;
21-
--border: 214.3 31.8% 91.4%;
22-
--input: 214.3 31.8% 91.4%;
23-
--ring: 222.2 84% 4.9%;
24-
--radius: 0.5rem;
25-
}
3+
@theme {
4+
/* Colors - Light mode (default) */
5+
--color-background: hsl(0 0% 100%);
6+
--color-foreground: hsl(222.2 84% 4.9%);
7+
--color-card: hsl(0 0% 100%);
8+
--color-card-foreground: hsl(222.2 84% 4.9%);
9+
--color-popover: hsl(0 0% 100%);
10+
--color-popover-foreground: hsl(222.2 84% 4.9%);
11+
--color-primary: hsl(222.2 47.4% 11.2%);
12+
--color-primary-foreground: hsl(210 40% 98%);
13+
--color-secondary: hsl(210 40% 96%);
14+
--color-secondary-foreground: hsl(222.2 47.4% 11.2%);
15+
--color-muted: hsl(210 40% 96%);
16+
--color-muted-foreground: hsl(215.4 16.3% 46.9%);
17+
--color-accent: hsl(210 40% 96%);
18+
--color-accent-foreground: hsl(222.2 47.4% 11.2%);
19+
--color-destructive: hsl(0 84.2% 60.2%);
20+
--color-destructive-foreground: hsl(210 40% 98%);
21+
--color-border: hsl(214.3 31.8% 91.4%);
22+
--color-input: hsl(214.3 31.8% 91.4%);
23+
--color-ring: hsl(222.2 84% 4.9%);
24+
25+
/* Border radius */
26+
--radius: 0.5rem;
27+
--radius-lg: var(--radius);
28+
--radius-md: calc(var(--radius) - 2px);
29+
--radius-sm: calc(var(--radius) - 4px);
30+
}
2631

27-
.dark {
28-
--background: 222.2 84% 4.9%;
29-
--foreground: 210 40% 98%;
30-
--card: 222.2 84% 4.9%;
31-
--card-foreground: 210 40% 98%;
32-
--popover: 222.2 84% 4.9%;
33-
--popover-foreground: 210 40% 98%;
34-
--primary: 210 40% 98%;
35-
--primary-foreground: 222.2 47.4% 11.2%;
36-
--secondary: 217.2 32.6% 17.5%;
37-
--secondary-foreground: 210 40% 98%;
38-
--muted: 217.2 32.6% 17.5%;
39-
--muted-foreground: 215 20.2% 65.1%;
40-
--accent: 217.2 32.6% 17.5%;
41-
--accent-foreground: 210 40% 98%;
42-
--destructive: 0 62.8% 30.6%;
43-
--destructive-foreground: 210 40% 98%;
44-
--border: 217.2 32.6% 17.5%;
45-
--input: 217.2 32.6% 17.5%;
46-
--ring: 212.7 26.8% 83.9%;
32+
/* Dark mode variant */
33+
@custom-variant dark (&:where(.dark, .dark *));
34+
35+
@layer base {
36+
@variant dark {
37+
--color-background: hsl(222.2 84% 4.9%);
38+
--color-foreground: hsl(210 40% 98%);
39+
--color-card: hsl(222.2 84% 4.9%);
40+
--color-card-foreground: hsl(210 40% 98%);
41+
--color-popover: hsl(222.2 84% 4.9%);
42+
--color-popover-foreground: hsl(210 40% 98%);
43+
--color-primary: hsl(210 40% 98%);
44+
--color-primary-foreground: hsl(222.2 47.4% 11.2%);
45+
--color-secondary: hsl(217.2 32.6% 17.5%);
46+
--color-secondary-foreground: hsl(210 40% 98%);
47+
--color-muted: hsl(217.2 32.6% 17.5%);
48+
--color-muted-foreground: hsl(215 20.2% 65.1%);
49+
--color-accent: hsl(217.2 32.6% 17.5%);
50+
--color-accent-foreground: hsl(210 40% 98%);
51+
--color-destructive: hsl(0 62.8% 30.6%);
52+
--color-destructive-foreground: hsl(210 40% 98%);
53+
--color-border: hsl(217.2 32.6% 17.5%);
54+
--color-input: hsl(217.2 32.6% 17.5%);
55+
--color-ring: hsl(212.7 26.8% 83.9%);
4756
}
4857
}
4958

@@ -55,4 +64,3 @@
5564
@apply bg-background text-foreground;
5665
}
5766
}
58-

apps/todo-app/app/routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { type RouteConfig, index } from '@react-router/dev/routes';
2+
3+
export default [
4+
index('routes/home.tsx')
5+
] satisfies RouteConfig;

apps/todo-app/tailwind.config.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
import baseConfig from '@todo-starter/config/tailwind';
2-
import type { Config } from 'tailwindcss';
3-
4-
export default {
5-
...baseConfig,
6-
content: [
7-
'./app/**/*.{js,jsx,ts,tsx}',
8-
'../../packages/ui/src/**/*.{js,jsx,ts,tsx}'
9-
]
10-
} satisfies Config;
1+
// Tailwind CSS v4 uses CSS-first configuration
2+
// Theme configuration is now in app/globals.css using @theme directive
3+
// This file is no longer needed but kept for compatibility if needed
114

5+
export default {};

0 commit comments

Comments
 (0)