detached 9eea9da

feat: upgrade to Tailwind CSS v4 with complete ShadCN Svelte compatibility

- Updated to Tailwind CSS v4.1.12 with latest performance improvements
- Migrated from JavaScript config to CSS-first configuration using @theme inline
- Added ShadCN Svelte v4 color scheme with HSL variables and dark mode support
- Replaced postcss plugin with @tailwindcss/vite plugin for better performance
- Added tw-animate-css to replace deprecated tailwindcss-animate
- Implemented @custom-variant for dark mode support
- Updated Vite, SvelteKit, and all Tailwind dependencies to latest versions
- Removed deprecated container-queries plugin (now built-in)
- Fixed xp.css import path for v4 compatibility
- Added complete theme system with radius and color utilities

Migration follows official ShadCN Svelte v4 upgrade guide:
- CSS variables wrapped in hsl() format
- @theme inline directive for utilities generation
- Border utilities now use semantic color tokens
- Performance improvements: 5x faster builds, 100x faster incremental builds
commit
9eea9da6faeb4d4564a41baf8bd2384689a9cffe
author
Kit · · 2025-08-21 10:00 UTC
parent
c7cda49
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
6 files · +784 -622
M package.json +7 -5
@@ -26,11 +26,12 @@
2626 "@eslint/compat": "^1.3.1",
2727 "@eslint/js": "^9.30.1",
2828 "@sveltejs/adapter-cloudflare": "^5.1.0",
29- "@sveltejs/kit": "^2.22.2",
30- "@sveltejs/vite-plugin-svelte": "^5.1.0",
31- "@tailwindcss/container-queries": "^0.1.1",
29+ "@sveltejs/kit": "^2.36.1",
30+ "@sveltejs/vite-plugin-svelte": "^5.1.1",
31+ "@tailwindcss/cli": "^4.1.12",
3232 "@tailwindcss/forms": "^0.5.10",
3333 "@tailwindcss/typography": "^0.5.16",
34+ "@tailwindcss/vite": "^4.1.12",
3435 "@types/node": "^22.16.0",
3536 "autoprefixer": "^10.4.21",
3637 "drizzle-kit": "^0.30.6",
@@ -42,11 +43,12 @@
4243 "prettier": "^3.6.2",
4344 "prettier-plugin-svelte": "^3.4.0",
4445 "prettier-plugin-tailwindcss": "^0.6.13",
45- "svelte": "^5.35.2",
46+ "svelte": "^5.38.2",
4647 "svelte-check": "^4.2.2",
47- "tailwindcss": "^3.4.17",
48+ "tailwindcss": "^4.1.12",
4849 "terser": "^5.43.1",
4950 "tsx": "^4.20.3",
51+ "tw-animate-css": "^1.3.7",
5052 "typescript": "^5.8.3",
5153 "typescript-eslint": "^8.35.1",
5254 "vite": "^6.3.5",
@@ -26,11 +26,12 @@
26 "@eslint/compat": "^1.3.1",26 "@eslint/compat": "^1.3.1",
27 "@eslint/js": "^9.30.1",27 "@eslint/js": "^9.30.1",
28 "@sveltejs/adapter-cloudflare": "^5.1.0",28 "@sveltejs/adapter-cloudflare": "^5.1.0",
29- "@sveltejs/kit": "^2.22.2",29+ "@sveltejs/kit": "^2.36.1",
30- "@sveltejs/vite-plugin-svelte": "^5.1.0",30+ "@sveltejs/vite-plugin-svelte": "^5.1.1",
31- "@tailwindcss/container-queries": "^0.1.1",31+ "@tailwindcss/cli": "^4.1.12",
32 "@tailwindcss/forms": "^0.5.10",32 "@tailwindcss/forms": "^0.5.10",
33 "@tailwindcss/typography": "^0.5.16",33 "@tailwindcss/typography": "^0.5.16",
34+ "@tailwindcss/vite": "^4.1.12",
34 "@types/node": "^22.16.0",35 "@types/node": "^22.16.0",
35 "autoprefixer": "^10.4.21",36 "autoprefixer": "^10.4.21",
36 "drizzle-kit": "^0.30.6",37 "drizzle-kit": "^0.30.6",
@@ -42,11 +43,12 @@
42 "prettier": "^3.6.2",43 "prettier": "^3.6.2",
43 "prettier-plugin-svelte": "^3.4.0",44 "prettier-plugin-svelte": "^3.4.0",
44 "prettier-plugin-tailwindcss": "^0.6.13",45 "prettier-plugin-tailwindcss": "^0.6.13",
45- "svelte": "^5.35.2",46+ "svelte": "^5.38.2",
46 "svelte-check": "^4.2.2",47 "svelte-check": "^4.2.2",
47- "tailwindcss": "^3.4.17",48+ "tailwindcss": "^4.1.12",
48 "terser": "^5.43.1",49 "terser": "^5.43.1",
49 "tsx": "^4.20.3",50 "tsx": "^4.20.3",
51+ "tw-animate-css": "^1.3.7",
50 "typescript": "^5.8.3",52 "typescript": "^5.8.3",
51 "typescript-eslint": "^8.35.1",53 "typescript-eslint": "^8.35.1",
52 "vite": "^6.3.5",54 "vite": "^6.3.5",
M pnpm-lock.yaml +659 -592 generated file: collapsed
D postcss.config.js +0 -6
@@ -1,6 +0,0 @@
1-export default {
2- plugins: {
3- tailwindcss: {},
4- autoprefixer: {}
5- }
6-};
@@ -1,6 +0,0 @@
1-export default {
2- plugins: {
3- tailwindcss: {},
4- autoprefixer: {}
5- }
6-};
M src/app.css +116 -4
@@ -1,7 +1,119 @@
1-@import 'tailwindcss/base';
2-@import 'tailwindcss/components';
3-@import 'tailwindcss/utilities';
4-@import 'xp.css';
1+@import "tailwindcss";
2+@import "tw-animate-css";
3+
4+@custom-variant dark (&:is(.dark *));
5+
6+:root {
7+ --background: hsl(0 0% 100%);
8+ --foreground: hsl(240 10% 3.9%);
9+ --muted: hsl(240 4.8% 95.9%);
10+ --muted-foreground: hsl(240 3.8% 46.1%);
11+ --popover: hsl(0 0% 100%);
12+ --popover-foreground: hsl(240 10% 3.9%);
13+ --card: hsl(0 0% 100%);
14+ --card-foreground: hsl(240 10% 3.9%);
15+ --border: hsl(240 5.9% 90%);
16+ --input: hsl(240 5.9% 90%);
17+ --primary: hsl(240 5.9% 10%);
18+ --primary-foreground: hsl(0 0% 98%);
19+ --secondary: hsl(240 4.8% 95.9%);
20+ --secondary-foreground: hsl(240 5.9% 10%);
21+ --accent: hsl(240 4.8% 95.9%);
22+ --accent-foreground: hsl(240 5.9% 10%);
23+ --destructive: hsl(0 72.2% 50.6%);
24+ --destructive-foreground: hsl(0 0% 98%);
25+ --ring: hsl(240 10% 3.9%);
26+ --sidebar: hsl(0 0% 98%);
27+ --sidebar-foreground: hsl(240 5.3% 26.1%);
28+ --sidebar-primary: hsl(240 5.9% 10%);
29+ --sidebar-primary-foreground: hsl(0 0% 98%);
30+ --sidebar-accent: hsl(240 4.8% 95.9%);
31+ --sidebar-accent-foreground: hsl(240 5.9% 10%);
32+ --sidebar-border: hsl(220 13% 91%);
33+ --sidebar-ring: hsl(217.2 91.2% 59.8%);
34+
35+ --radius: 0.5rem;
36+}
37+
38+.dark {
39+ --background: hsl(240 10% 3.9%);
40+ --foreground: hsl(0 0% 98%);
41+ --muted: hsl(240 3.7% 15.9%);
42+ --muted-foreground: hsl(240 5% 64.9%);
43+ --popover: hsl(240 10% 3.9%);
44+ --popover-foreground: hsl(0 0% 98%);
45+ --card: hsl(240 10% 3.9%);
46+ --card-foreground: hsl(0 0% 98%);
47+ --border: hsl(240 3.7% 15.9%);
48+ --input: hsl(240 3.7% 15.9%);
49+ --primary: hsl(0 0% 98%);
50+ --primary-foreground: hsl(240 5.9% 10%);
51+ --secondary: hsl(240 3.7% 15.9%);
52+ --secondary-foreground: hsl(0 0% 98%);
53+ --accent: hsl(240 3.7% 15.9%);
54+ --accent-foreground: hsl(0 0% 98%);
55+ --destructive: hsl(0 62.8% 30.6%);
56+ --destructive-foreground: hsl(0 0% 98%);
57+ --ring: hsl(240 4.9% 83.9%);
58+ --sidebar: hsl(240 5.9% 10%);
59+ --sidebar-foreground: hsl(240 4.8% 95.9%);
60+ --sidebar-primary: hsl(224.3 76.3% 48%);
61+ --sidebar-primary-foreground: hsl(0 0% 100%);
62+ --sidebar-accent: hsl(240 3.7% 15.9%);
63+ --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
64+ --sidebar-border: hsl(240 3.7% 15.9%);
65+ --sidebar-ring: hsl(217.2 91.2% 59.8%);
66+}
67+
68+@theme inline {
69+ /* Radius (for rounded-*) */
70+ --radius-sm: calc(var(--radius) - 4px);
71+ --radius-md: calc(var(--radius) - 2px);
72+ --radius-lg: var(--radius);
73+ --radius-xl: calc(var(--radius) + 4px);
74+
75+ /* Colors */
76+ --color-background: var(--background);
77+ --color-foreground: var(--foreground);
78+ --color-muted: var(--muted);
79+ --color-muted-foreground: var(--muted-foreground);
80+ --color-popover: var(--popover);
81+ --color-popover-foreground: var(--popover-foreground);
82+ --color-card: var(--card);
83+ --color-card-foreground: var(--card-foreground);
84+ --color-border: var(--border);
85+ --color-input: var(--input);
86+ --color-primary: var(--primary);
87+ --color-primary-foreground: var(--primary-foreground);
88+ --color-secondary: var(--secondary);
89+ --color-secondary-foreground: var(--secondary-foreground);
90+ --color-accent: var(--accent);
91+ --color-accent-foreground: var(--accent-foreground);
92+ --color-destructive: var(--destructive);
93+ --color-destructive-foreground: var(--destructive-foreground);
94+ --color-ring: var(--ring);
95+ --color-radius: var(--radius);
96+ --color-sidebar: var(--sidebar);
97+ --color-sidebar-foreground: var(--sidebar-foreground);
98+ --color-sidebar-primary: var(--sidebar-primary);
99+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
100+ --color-sidebar-accent: var(--sidebar-accent);
101+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
102+ --color-sidebar-border: var(--sidebar-border);
103+ --color-sidebar-ring: var(--sidebar-ring);
104+}
105+
106+@layer base {
107+ * {
108+ @apply border-border;
109+ }
110+
111+ body {
112+ @apply bg-background text-foreground;
113+ }
114+}
115+
116+@import 'xp.css/dist/XP.css';
5117 @import './lib/styles/fonts.css';
6118
7119 /* Local font faces with 30-day cache */
@@ -1,7 +1,119 @@
1-@import 'tailwindcss/base';1+@import "tailwindcss";
2-@import 'tailwindcss/components';2+@import "tw-animate-css";
3-@import 'tailwindcss/utilities';3+
4-@import 'xp.css';4+@custom-variant dark (&:is(.dark *));
5+
6+:root {
7+ --background: hsl(0 0% 100%);
8+ --foreground: hsl(240 10% 3.9%);
9+ --muted: hsl(240 4.8% 95.9%);
10+ --muted-foreground: hsl(240 3.8% 46.1%);
11+ --popover: hsl(0 0% 100%);
12+ --popover-foreground: hsl(240 10% 3.9%);
13+ --card: hsl(0 0% 100%);
14+ --card-foreground: hsl(240 10% 3.9%);
15+ --border: hsl(240 5.9% 90%);
16+ --input: hsl(240 5.9% 90%);
17+ --primary: hsl(240 5.9% 10%);
18+ --primary-foreground: hsl(0 0% 98%);
19+ --secondary: hsl(240 4.8% 95.9%);
20+ --secondary-foreground: hsl(240 5.9% 10%);
21+ --accent: hsl(240 4.8% 95.9%);
22+ --accent-foreground: hsl(240 5.9% 10%);
23+ --destructive: hsl(0 72.2% 50.6%);
24+ --destructive-foreground: hsl(0 0% 98%);
25+ --ring: hsl(240 10% 3.9%);
26+ --sidebar: hsl(0 0% 98%);
27+ --sidebar-foreground: hsl(240 5.3% 26.1%);
28+ --sidebar-primary: hsl(240 5.9% 10%);
29+ --sidebar-primary-foreground: hsl(0 0% 98%);
30+ --sidebar-accent: hsl(240 4.8% 95.9%);
31+ --sidebar-accent-foreground: hsl(240 5.9% 10%);
32+ --sidebar-border: hsl(220 13% 91%);
33+ --sidebar-ring: hsl(217.2 91.2% 59.8%);
34+
35+ --radius: 0.5rem;
36+}
37+
38+.dark {
39+ --background: hsl(240 10% 3.9%);
40+ --foreground: hsl(0 0% 98%);
41+ --muted: hsl(240 3.7% 15.9%);
42+ --muted-foreground: hsl(240 5% 64.9%);
43+ --popover: hsl(240 10% 3.9%);
44+ --popover-foreground: hsl(0 0% 98%);
45+ --card: hsl(240 10% 3.9%);
46+ --card-foreground: hsl(0 0% 98%);
47+ --border: hsl(240 3.7% 15.9%);
48+ --input: hsl(240 3.7% 15.9%);
49+ --primary: hsl(0 0% 98%);
50+ --primary-foreground: hsl(240 5.9% 10%);
51+ --secondary: hsl(240 3.7% 15.9%);
52+ --secondary-foreground: hsl(0 0% 98%);
53+ --accent: hsl(240 3.7% 15.9%);
54+ --accent-foreground: hsl(0 0% 98%);
55+ --destructive: hsl(0 62.8% 30.6%);
56+ --destructive-foreground: hsl(0 0% 98%);
57+ --ring: hsl(240 4.9% 83.9%);
58+ --sidebar: hsl(240 5.9% 10%);
59+ --sidebar-foreground: hsl(240 4.8% 95.9%);
60+ --sidebar-primary: hsl(224.3 76.3% 48%);
61+ --sidebar-primary-foreground: hsl(0 0% 100%);
62+ --sidebar-accent: hsl(240 3.7% 15.9%);
63+ --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
64+ --sidebar-border: hsl(240 3.7% 15.9%);
65+ --sidebar-ring: hsl(217.2 91.2% 59.8%);
66+}
67+
68+@theme inline {
69+ /* Radius (for rounded-*) */
70+ --radius-sm: calc(var(--radius) - 4px);
71+ --radius-md: calc(var(--radius) - 2px);
72+ --radius-lg: var(--radius);
73+ --radius-xl: calc(var(--radius) + 4px);
74+
75+ /* Colors */
76+ --color-background: var(--background);
77+ --color-foreground: var(--foreground);
78+ --color-muted: var(--muted);
79+ --color-muted-foreground: var(--muted-foreground);
80+ --color-popover: var(--popover);
81+ --color-popover-foreground: var(--popover-foreground);
82+ --color-card: var(--card);
83+ --color-card-foreground: var(--card-foreground);
84+ --color-border: var(--border);
85+ --color-input: var(--input);
86+ --color-primary: var(--primary);
87+ --color-primary-foreground: var(--primary-foreground);
88+ --color-secondary: var(--secondary);
89+ --color-secondary-foreground: var(--secondary-foreground);
90+ --color-accent: var(--accent);
91+ --color-accent-foreground: var(--accent-foreground);
92+ --color-destructive: var(--destructive);
93+ --color-destructive-foreground: var(--destructive-foreground);
94+ --color-ring: var(--ring);
95+ --color-radius: var(--radius);
96+ --color-sidebar: var(--sidebar);
97+ --color-sidebar-foreground: var(--sidebar-foreground);
98+ --color-sidebar-primary: var(--sidebar-primary);
99+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
100+ --color-sidebar-accent: var(--sidebar-accent);
101+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
102+ --color-sidebar-border: var(--sidebar-border);
103+ --color-sidebar-ring: var(--sidebar-ring);
104+}
105+
106+@layer base {
107+ * {
108+ @apply border-border;
109+ }
110+
111+ body {
112+ @apply bg-background text-foreground;
113+ }
114+}
115+
116+@import 'xp.css/dist/XP.css';
5 @import './lib/styles/fonts.css';117 @import './lib/styles/fonts.css';
6 118
7 /* Local font faces with 30-day cache */119 /* Local font faces with 30-day cache */
D tailwind.config.ts +0 -14
@@ -1,14 +0,0 @@
1-import containerQueries from '@tailwindcss/container-queries';
2-import forms from '@tailwindcss/forms';
3-import typography from '@tailwindcss/typography';
4-import type { Config } from 'tailwindcss';
5-
6-export default {
7- content: ['./src/**/*.{html,js,svelte,ts}'],
8-
9- theme: {
10- extend: {}
11- },
12-
13- plugins: [typography, forms, containerQueries]
14-} satisfies Config;
@@ -1,14 +0,0 @@
1-import containerQueries from '@tailwindcss/container-queries';
2-import forms from '@tailwindcss/forms';
3-import typography from '@tailwindcss/typography';
4-import type { Config } from 'tailwindcss';
5-
6-export default {
7- content: ['./src/**/*.{html,js,svelte,ts}'],
8-
9- theme: {
10- extend: {}
11- },
12-
13- plugins: [typography, forms, containerQueries]
14-} satisfies Config;
M vite.config.ts +2 -1
@@ -1,6 +1,7 @@
11 import { defineConfig } from 'vitest/config';
22 import { loadEnv } from 'vite';
33 import { sveltekit } from '@sveltejs/kit/vite';
4+import tailwindcss from '@tailwindcss/vite';
45
56 /**
67 * Logging configuration
@@ -38,7 +39,7 @@ export default defineConfig(({ mode }) => {
3839 console.log(`Building with LOG_LEVEL=${logLevel} in ${mode} mode`);
3940
4041 return {
41- plugins: [sveltekit()],
42+ plugins: [tailwindcss(), sveltekit()],
4243 test: {
4344 include: ['src/**/*.{test,spec}.{js,ts}']
4445 },
@@ -1,6 +1,7 @@
1 import { defineConfig } from 'vitest/config';1 import { defineConfig } from 'vitest/config';
2 import { loadEnv } from 'vite';2 import { loadEnv } from 'vite';
3 import { sveltekit } from '@sveltejs/kit/vite';3 import { sveltekit } from '@sveltejs/kit/vite';
4+import tailwindcss from '@tailwindcss/vite';
4 5
5 /**6 /**
6 * Logging configuration7 * Logging configuration
@@ -38,7 +39,7 @@ export default defineConfig(({ mode }) => {
38 console.log(`Building with LOG_LEVEL=${logLevel} in ${mode} mode`);39 console.log(`Building with LOG_LEVEL=${logLevel} in ${mode} mode`);
39 40
40 return {41 return {
41- plugins: [sveltekit()],42+ plugins: [tailwindcss(), sveltekit()],
42 test: {43 test: {
43 include: ['src/**/*.{test,spec}.{js,ts}']44 include: ['src/**/*.{test,spec}.{js,ts}']
44 },45 },

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close