refactor(ui): clean dark theme — global-first, variable-backed approach

Phase 1: Replace all @apply dark: in @layer components with explicit :is(.dark)
rules for .app-input, .app-select, .app-label, .app-page-subtitle,
.app-page-title, .app-data-table th. This fixes unreliable PostCSS variant
handling in Tailwind v4 @layer components.

Phase 2: Add missing global dark overrides for interactive text colors:
text-blue-600/500, text-red-500/400, text-indigo-600/700, text-amber-600,
plus hover states. Add :is(.dark) option for native <select> dropdowns.

Phase 3: Add semantic component classes .app-action-edit, .app-action-delete,
.app-action-danger-btn — variable-backed, no hardcoded hex values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 07:01:59 +02:00
parent ddd711f93f
commit e575462b01
+92 -6
View File
@@ -376,11 +376,32 @@
/* Missing text color overrides */
.dark .text-amber-700 { color: rgb(251 191 36) !important; }
.dark .text-amber-600 { color: rgb(251 191 36) !important; }
.dark .text-orange-600 { color: rgb(251 146 60) !important; }
.dark .text-green-600 { color: rgb(52 211 153) !important; }
.dark .text-emerald-700 { color: rgb(52 211 153) !important; }
.dark .text-brand-700 { color: rgb(var(--accent-400)) !important; }
/* Interactive text — action links, errors, close buttons */
.dark .text-blue-600 { color: rgb(96 165 250) !important; }
.dark .text-blue-500 { color: rgb(96 165 250) !important; }
.dark .text-red-500 { color: rgb(248 113 113) !important; }
.dark .text-red-400 { color: rgb(251 113 133) !important; }
.dark .text-indigo-600 { color: rgb(129 140 248) !important; }
.dark .text-indigo-700 { color: rgb(129 140 248) !important; }
/* Hover states for action links */
.dark .hover\:text-blue-800:hover { color: rgb(147 197 253) !important; }
.dark .hover\:text-red-700:hover { color: rgb(252 165 165) !important; }
.dark .hover\:text-red-600:hover { color: rgb(248 113 113) !important; }
.dark .hover\:text-indigo-800:hover { color: rgb(165 180 252) !important; }
/* Native <option> elements — best-effort across browsers */
:is(.dark) option {
background-color: rgb(var(--surface-card));
color: rgb(var(--text-primary));
}
/* Divide override for gray-50 (ChargeabilityWidget sticky headers) */
.dark .divide-gray-50 > * + * { border-color: rgb(var(--border-subtle)) !important; }
@@ -465,7 +486,6 @@
.app-input {
@apply w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition;
@apply dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100;
}
.app-input:focus {
@@ -474,6 +494,12 @@
outline: none;
}
:is(.dark) .app-input {
background-color: rgb(var(--surface-input));
border-color: rgb(var(--border-input));
color: rgb(var(--text-primary));
}
:is(.dark) .app-input:focus {
border-color: rgb(var(--accent-400));
box-shadow: 0 0 0 3px rgba(var(--accent-400), 0.20), inset 0 0 8px rgba(var(--accent-400), 0.08);
@@ -482,11 +508,25 @@
.app-select {
@apply rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition;
@apply focus:border-brand-500 focus:ring-4 focus:ring-brand-100/80;
@apply dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100 dark:focus:ring-brand-900/50;
}
:is(.dark) .app-select {
background-color: rgb(var(--surface-input));
border-color: rgb(var(--border-input));
color: rgb(var(--text-primary));
}
:is(.dark) .app-select:focus {
border-color: rgb(var(--accent-400));
box-shadow: 0 0 0 3px rgb(var(--accent-400) / 0.20);
}
.app-label {
@apply mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.18em] text-gray-500 dark:text-gray-400;
@apply mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.18em] text-gray-500;
}
:is(.dark) .app-label {
color: rgb(var(--text-very-muted));
}
.app-page {
@@ -498,7 +538,7 @@
}
.app-page-title {
@apply font-display text-3xl font-semibold text-gray-900 dark:text-gray-100;
@apply font-display text-3xl font-semibold text-gray-900;
}
:is(.dark) .app-page-title {
@@ -509,7 +549,11 @@
}
.app-page-subtitle {
@apply text-sm text-gray-500 dark:text-gray-400;
@apply text-sm text-gray-500;
}
:is(.dark) .app-page-subtitle {
color: rgb(var(--text-muted));
}
.app-data-table {
@@ -536,7 +580,49 @@
}
.app-data-table th {
@apply text-[11px] font-semibold uppercase tracking-[0.16em] text-gray-500 dark:text-gray-400;
@apply text-[11px] font-semibold uppercase tracking-[0.16em] text-gray-500;
}
:is(.dark) .app-data-table th {
color: rgb(var(--text-very-muted));
}
/* ─── Semantic action classes ─────────────────────────────────────────── */
.app-action-edit {
@apply text-xs font-medium cursor-pointer transition-colors;
color: rgb(59 130 246);
}
.app-action-edit:hover {
color: rgb(37 99 235);
text-decoration: underline;
}
:is(.dark) .app-action-edit { color: rgb(96 165 250); }
:is(.dark) .app-action-edit:hover { color: rgb(147 197 253); }
.app-action-delete {
@apply text-xs font-medium cursor-pointer transition-colors;
color: rgb(239 68 68);
}
.app-action-delete:hover {
color: rgb(185 28 28);
text-decoration: underline;
}
:is(.dark) .app-action-delete { color: rgb(248 113 113); }
:is(.dark) .app-action-delete:hover { color: rgb(252 165 165); }
.app-action-danger-btn {
@apply rounded px-2 py-1 text-sm font-medium cursor-pointer transition-colors;
color: rgb(239 68 68);
}
.app-action-danger-btn:hover {
color: rgb(185 28 28);
background-color: rgb(254 242 242);
}
:is(.dark) .app-action-danger-btn { color: rgb(248 113 113); }
:is(.dark) .app-action-danger-btn:hover {
color: rgb(252 165 165);
background-color: rgb(127 29 29 / 0.2);
}
}