feat: dark mode#3246
Conversation
…or exact ink-gray semantics
…theme drives readability
There was a problem hiding this comment.
Pull request overview
Adds dark mode support across the Helpdesk desk UI by migrating hardcoded Tailwind color utilities to semantic design tokens, wiring a theme toggle into the user menus, and ensuring inbound email HTML renders with theme-controlled colors.
Changes:
- Replaced many
text-gray-*/bg-white/border-gray-*utilities with semantic token classes (text-ink-*,bg-surface-*,border-outline-*) to make dark mode viable. - Added theme initialization + theme toggle entries in sidebar user dropdowns (desktop + mobile).
- Added email-color stripping + a reactive
data-thememirror to keep iframe-based email content and theme-aware charts in sync.
Reviewed changes
Copilot reviewed 141 out of 141 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| desk/src/utils.ts | Adds stripEmailColors() and a reactive dataTheme mirror for theme-aware JS components. |
| desk/src/stores/ticketStatus.ts | Migrates status color map entries to semantic ink tokens. |
| desk/src/pages/ticket/TicketTextEditor.vue | Migrates placeholder/background colors to semantic tokens. |
| desk/src/pages/ticket/TicketNew.vue | Migrates form label + required indicator colors to semantic tokens. |
| desk/src/pages/ticket/TicketFeedback.vue | Migrates text + required indicator colors to semantic tokens. |
| desk/src/pages/ticket/TicketCustomerTemplateFields.vue | Migrates field label/value colors to semantic tokens. |
| desk/src/pages/ticket/TicketConversation.vue | Migrates heading + timeline border colors to semantic tokens. |
| desk/src/pages/ticket/TicketCommunication.vue | Migrates meta text/icon colors to semantic tokens. |
| desk/src/pages/ticket/MobileTicketAgent.vue | Migrates button/background/text colors to semantic tokens. |
| desk/src/pages/knowledge-base/NewArticle.vue | Migrates editor border colors to semantic tokens. |
| desk/src/pages/knowledge-base/KnowledgeBaseCustomer.vue | Migrates heading text colors to semantic tokens. |
| desk/src/pages/knowledge-base/KnowledgeBaseAgent.vue | Migrates heading text colors to semantic tokens. |
| desk/src/pages/knowledge-base/Article.vue | Migrates editor background + meta colors to semantic tokens. |
| desk/src/pages/home/components/RecentFeedback.vue | Makes ECharts options theme-aware via CSS variables + dataTheme. |
| desk/src/pages/home/components/PendingTickets.vue | Migrates table borders/hover states + SLA warning text color to semantic tokens. |
| desk/src/pages/home/components/AvgTimeMetrics.vue | Makes chart colors/theme tooltips driven by CSS vars + dataTheme. |
| desk/src/pages/home/components/AvgTimeCard.vue | Migrates percentage change colors to semantic tokens. |
| desk/src/pages/home/components/AgentTicketsCard.vue | Migrates percentage change colors to semantic tokens. |
| desk/src/pages/home/Home.vue | Migrates header title color to semantic tokens. |
| desk/src/pages/desk/customer/Customers.vue | Migrates header title color to semantic tokens. |
| desk/src/pages/desk/customer/CustomerDialog.vue | Migrates dialog title color to semantic tokens. |
| desk/src/pages/desk/contact/Contacts.vue | Migrates header title color to semantic tokens. |
| desk/src/pages/desk/contact/ContactDialog.vue | Migrates dialog title + body text colors to semantic tokens. |
| desk/src/pages/call-logs/CallLogs.vue | Migrates header title color to semantic tokens. |
| desk/src/pages/MobileNotifications.vue | Migrates list hover + text/icon colors to semantic tokens. |
| desk/src/pages/InvalidPage.vue | Migrates empty/error page text color to semantic tokens. |
| desk/src/main.js | Migrates toast icon color to semantic token. |
| desk/src/index.css | Sets base background and color-scheme for light/dark. |
| desk/src/components/view-controls/SortBy.vue | Migrates popover/background/border/icon colors to semantic tokens. |
| desk/src/components/view-controls/Filter.vue | Migrates popover/background/border/text colors to semantic tokens. |
| desk/src/components/ticket/TicketSplitModal.vue | Migrates banner ring/text colors to semantic tokens. |
| desk/src/components/ticket/TicketMergeModal.vue | Migrates modal backgrounds/rings/code backgrounds to semantic tokens. |
| desk/src/components/ticket/TicketFeedback.vue | Migrates feedback text colors to semantic tokens. |
| desk/src/components/ticket/TicketCustomerSidebar.vue | Migrates label/value colors to semantic tokens. |
| desk/src/components/ticket/TicketAgentSidebar.vue | Migrates action button + feedback section colors to semantic tokens. |
| desk/src/components/ticket/TicketAgentDetails.vue | Migrates label text color to semantic tokens. |
| desk/src/components/ticket/TicketAgentContact.vue | Migrates hover text color to semantic tokens. |
| desk/src/components/ticket/TicketAgentActivities.vue | Migrates timeline border + icon/empty-state colors to semantic tokens. |
| desk/src/components/ticket/ActivityHeader.vue | Migrates header title color to semantic tokens. |
| desk/src/components/ticket-agent/FeedbackBox.vue | Migrates optional text color to semantic tokens. |
| desk/src/components/ticket-agent/AssignTo.vue | Adds transparent input styling for dark mode + migrates label color. |
| desk/src/components/notifications/Notifications.vue | Migrates notification drawer backgrounds/hover/text colors to semantic tokens. |
| desk/src/components/layouts/Sidebar.vue | Adds theme toggle menu item; migrates sidebar colors to semantic tokens. |
| desk/src/components/layouts/SettingsLayoutBase.vue | Migrates description text color to semantic tokens. |
| desk/src/components/layouts/MobileSidebar.vue | Adds theme toggle menu item; migrates overlay/sidebar colors to semantic tokens. |
| desk/src/components/knowledge-base/CategoryFolder.vue | Migrates borders/text colors to semantic tokens. |
| desk/src/components/knowledge-base/ArticleFeedback.vue | Migrates container + text colors to semantic tokens. |
| desk/src/components/knowledge-base/ArticleCard.vue | Migrates borders/text colors to semantic tokens. |
| desk/src/components/icons/ThumbsUpIcon.vue | Switches SVG fill to currentColor for theming. |
| desk/src/components/icons/ThumbsUpFilledIcon.vue | Switches SVG fill to currentColor for theming. |
| desk/src/components/icons/ThumbsDownIcon.vue | Removes hardcoded background rect; switches fill to currentColor. |
| desk/src/components/icons/ThumbsDownFilledIcon.vue | Switches SVG fill to currentColor for theming. |
| desk/src/components/icons/GlobeIcon.vue | Switches SVG fill to currentColor for theming. |
| desk/src/components/icons/FieldDependencyIcon.vue | Switches SVG fill to currentColor for theming. |
| desk/src/components/icons/ExternalLinkIcon.vue | Switches SVG fill to currentColor for theming. |
| desk/src/components/icons/CopyIcon.vue | Switches SVG fill to currentColor for theming. |
| desk/src/components/icons/AgentIcon.vue | Switches SVG fill to currentColor for theming. |
| desk/src/components/frappe-ui/MultiSelectCombobox.vue | Migrates input/popover/active-state colors to semantic tokens. |
| desk/src/components/frappe-ui/Link.vue | Migrates label + required asterisk colors to semantic tokens. |
| desk/src/components/frappe-ui/DurationPicker.vue | Migrates borders/hover/text colors to semantic tokens. |
| desk/src/components/frappe-ui/DurationField.vue | Migrates popover/background/border/hover/text colors to semantic tokens. |
| desk/src/components/frappe-ui/Autocomplete.vue | Migrates input/popover/active-state tokens + transparent input bg. |
| desk/src/components/desk/global/NewContactDialog.vue | Migrates label + required indicator colors to semantic tokens. |
| desk/src/components/desk/global/AddNewAgentsDialog.vue | Migrates container/item/hover colors to semantic tokens. |
| desk/src/components/conditions-filter/CFConditions.vue | Migrates container border color to semantic tokens. |
| desk/src/components/conditions-filter/CFCondition.vue | Migrates connective label text color to semantic tokens. |
| desk/src/components/command-palette/CPGroupResult.vue | Migrates active/background/icon/text colors to semantic tokens. |
| desk/src/components/command-palette/CPGroup.vue | Migrates active/background/icon/text colors to semantic tokens. |
| desk/src/components/command-palette/CP.vue | Migrates input placeholder + border colors to semantic tokens. |
| desk/src/components/UserMenu.vue | Migrates text/icon colors to semantic tokens. |
| desk/src/components/UniInput.vue | Migrates label + required indicator colors to semantic tokens. |
| desk/src/components/TypingIndicator.vue | Migrates indicator text color to semantic tokens. |
| desk/src/components/TicketField.vue | Migrates label + required indicator colors to semantic tokens. |
| desk/src/components/SidebarLink.vue | Migrates base text/icon colors + selected/hover classes to semantic tokens. |
| desk/src/components/Settings/Telephony/Telephony.vue | Migrates hover backgrounds to semantic tokens. |
| desk/src/components/Settings/Teams/TeamsList.vue | Migrates list header + hover backgrounds to semantic tokens. |
| desk/src/components/Settings/Teams/TeamEdit.vue | Migrates list header + hover/icon colors to semantic tokens. |
| desk/src/components/Settings/Sla/SlaWorkDaysList.vue | Migrates borders/header text/required indicator colors to semantic tokens. |
| desk/src/components/Settings/Sla/SlaPriorityListItem.vue | Migrates hover bg + popover bg to semantic tokens. |
| desk/src/components/Settings/Sla/SlaPriorityList.vue | Migrates borders/header text/required indicator colors to semantic tokens. |
| desk/src/components/Settings/Sla/SlaPolicyView.vue | Migrates code block bg + borders to semantic tokens. |
| desk/src/components/Settings/Sla/SlaPolicyListItem.vue | Migrates hover background to semantic tokens. |
| desk/src/components/Settings/Sla/SlaPolicyList.vue | Migrates list header text to semantic tokens. |
| desk/src/components/Settings/Sla/SlaPolicies.vue | Migrates search input styling to token-friendly classes. |
| desk/src/components/Settings/Sla/SlaAssignmentConditions.vue | Migrates empty-state border/text colors to semantic tokens. |
| desk/src/components/Settings/Sla/Modals/WorkDayModal.vue | Migrates error border class to semantic token. |
| desk/src/components/Settings/Sla/Modals/EditResponseResolutionModal.vue | Migrates picker bg/text/placeholder colors to semantic tokens. |
| desk/src/components/Settings/SettingsModal.vue | Migrates sidebar tab colors to semantic tokens. |
| desk/src/components/Settings/SettingsLayoutHeader.vue | Migrates description text color to semantic tokens. |
| desk/src/components/Settings/SavedReplies/components/PreviewDialog.vue | Migrates loading overlay color to semantic token. |
| desk/src/components/Settings/SavedReplies/SavedRepliesList.vue | Migrates list header + hover backgrounds to semantic tokens. |
| desk/src/components/Settings/Profile/Profile.vue | Migrates loading overlay color to semantic token. |
| desk/src/components/Settings/Holiday/RecurringHolidaysList.vue | Migrates borders/header/empty text colors to semantic tokens. |
| desk/src/components/Settings/Holiday/HolidaysTableView.vue | Migrates borders/header/empty text + row input bg to semantic tokens. |
| desk/src/components/Settings/Holiday/HolidaysCalendarView.vue | Migrates border + indicator dot colors to semantic tokens. |
| desk/src/components/Settings/Holiday/Holidays.vue | Migrates search input styling to token-friendly classes. |
| desk/src/components/Settings/Holiday/HolidayView.vue | Migrates legend swatch background to semantic token. |
| desk/src/components/Settings/Holiday/HolidayListItem.vue | Migrates hover background to semantic tokens. |
| desk/src/components/Settings/Holiday/HolidayList.vue | Migrates header text color to semantic tokens. |
| desk/src/components/Settings/Holiday/HLCalender.vue | Migrates text/background/border colors to semantic tokens. |
| desk/src/components/Settings/General/components/WorkflowKnowledgebaseSettings.vue | Migrates section header color to semantic tokens. |
| desk/src/components/Settings/General/components/TicketSettings.vue | Migrates header + helper text color to semantic tokens. |
| desk/src/components/Settings/General/components/LogoUpload.vue | Migrates border color to semantic tokens. |
| desk/src/components/Settings/General/components/Branding.vue | Migrates section header color to semantic tokens. |
| desk/src/components/Settings/General/General.vue | Migrates section header color to semantic tokens. |
| desk/src/components/Settings/FieldDependency/FieldDependencyValueSelection.vue | Migrates search icon colors to semantic tokens. |
| desk/src/components/Settings/FieldDependency/FieldDependencyList.vue | Migrates list header + hover backgrounds to semantic tokens. |
| desk/src/components/Settings/EmailProviderIcon.vue | Migrates provider tile colors to semantic tokens. |
| desk/src/components/Settings/EmailNotifications/NotificationList.vue | Migrates hover background to semantic tokens. |
| desk/src/components/Settings/EmailNotifications/Notification.vue | Migrates helper text color to semantic tokens. |
| desk/src/components/Settings/EmailEdit.vue | Migrates banner ring/text + helper text + toast icon colors to semantic tokens. |
| desk/src/components/Settings/EmailAdd.vue | Migrates banner ring/text + link color to semantic tokens. |
| desk/src/components/Settings/EmailAccountList.vue | Migrates list header text to semantic tokens. |
| desk/src/components/Settings/EmailAccountCard.vue | Migrates border/hover background to semantic tokens. |
| desk/src/components/Settings/Assignment Rules/AssignmentSchedule.vue | Migrates borders/header text to semantic tokens. |
| desk/src/components/Settings/Assignment Rules/AssignmentRulesSection.vue | Migrates empty-state border/text colors to semantic tokens. |
| desk/src/components/Settings/Assignment Rules/AssignmentRulesListView.vue | Migrates header text colors to semantic tokens. |
| desk/src/components/Settings/Assignment Rules/AssignmentRulesList.vue | Migrates search input styling to token-friendly classes. |
| desk/src/components/Settings/Assignment Rules/AssignmentRuleView.vue | Migrates popover backgrounds + hover/bg + borders to semantic tokens. |
| desk/src/components/Settings/Assignment Rules/AssignmentRuleListItem.vue | Migrates hover background to semantic tokens. |
| desk/src/components/Settings/Assignment Rules/AssigneeSearch.vue | Migrates popover/input bg + active/empty colors to semantic tokens. |
| desk/src/components/Settings/Assignment Rules/AssigneeRules.vue | Migrates popover bg + badge colors to semantic tokens. |
| desk/src/components/Settings/Agents.vue | Migrates search input styling + list header colors to semantic tokens. |
| desk/src/components/SelectDropdown.vue | Migrates dropdown background to semantic tokens. |
| desk/src/components/SearchMultiSelect.vue | Migrates avatar ring color to token variable. |
| desk/src/components/SearchArticles.vue | Migrates hover/text colors to semantic tokens. |
| desk/src/components/SavedRepliesSelectorModal.vue | Migrates hover/overlay/empty text/editor colors to semantic tokens. |
| desk/src/components/PageTitle.vue | Migrates title color to semantic tokens. |
| desk/src/components/MultipleAvatar.vue | Migrates ring color to token variable. |
| desk/src/components/MultiSelectInput.vue | Migrates input/popover/active/text colors to semantic tokens. |
| desk/src/components/MultiSelect.vue | Migrates container background to semantic tokens. |
| desk/src/components/HistoryBox.vue | Migrates text colors to semantic tokens. |
| desk/src/components/EmailEditor.vue | Migrates label + quote border colors to semantic tokens. |
| desk/src/components/EmailContent.vue | Strips inbound email colors + keeps iframe theme in sync with app theme. |
| desk/src/components/EmailArea.vue | Migrates container background to semantic tokens. |
| desk/src/components/ConfirmDialog.vue | Migrates message text color to semantic tokens. |
| desk/src/components/CommunicationArea.vue | Migrates active-state button backgrounds to semantic tokens. |
| desk/src/components/CommentBox.vue | Migrates header/action colors and partial reaction state to tokens. |
| desk/src/components/Autocomplete.vue | Migrates input/popover/active-state colors to semantic tokens. |
| desk/src/components/Apps.vue | Migrates popover/list item colors to semantic tokens. |
| desk/src/App.vue | Initializes theme and migrates toast icon colors to semantic tokens. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| div.querySelectorAll("[bgcolor]").forEach((el) => | ||
| el.removeAttribute("bgcolor") | ||
| ); | ||
| div.querySelectorAll("font[color]").forEach((el) => | ||
| el.removeAttribute("color") | ||
| ); |
There was a problem hiding this comment.
The comment says this removes bgcolor/color attributes, but the implementation only removes color on font[color]. If the goal is to strip color attributes generally, use a broader selector (e.g., [color]) or update the comment to match the narrower behavior.
| if (!localStorage.getItem("theme")) { | ||
| localStorage.setItem("theme", "light"); | ||
| } | ||
| useTheme(); | ||
|
|
||
| onMounted(() => { |
There was a problem hiding this comment.
localStorage is accessed at module initialization time. In environments where window/localStorage isn’t available (SSR, certain test setups, pre-render tooling), this will throw before the app mounts. Wrap this in a typeof window !== 'undefined' guard (and/or move it into onMounted).
| if (!localStorage.getItem("theme")) { | |
| localStorage.setItem("theme", "light"); | |
| } | |
| useTheme(); | |
| onMounted(() => { | |
| onMounted(() => { | |
| if (typeof window !== "undefined") { | |
| if (!window.localStorage.getItem("theme")) { | |
| window.localStorage.setItem("theme", "light"); | |
| } | |
| useTheme(); | |
| } |
| <p class="text-p-sm text-ink-gray-7 max-w-md text-ink-gray-6"> | ||
| {{ description }} |
There was a problem hiding this comment.
The description text has two conflicting text color utility classes (text-ink-gray-7 and text-ink-gray-6) on the same element. Only the latter will effectively apply, and it makes the intended color unclear—remove one of them (likely keep a single semantic token).
| <p class="text-p-sm text-ink-gray-7 max-w-md text-ink-gray-6"> | ||
| {{ description }} | ||
| </p> |
There was a problem hiding this comment.
This paragraph includes two different text-ink-gray-* classes (text-ink-gray-7 and text-ink-gray-6) which conflict; only one should be present to avoid accidental overrides and to make the intended description color clear.
| class="flex-col rounded-l-lg w-56 shrink-0 bg-surface-menu-bar m-1 bg-surface-menu-bar overflow-y-auto hide-scrollbar" | ||
| > |
There was a problem hiding this comment.
This class list repeats bg-surface-menu-bar twice, which is redundant and makes future theme tweaks harder to reason about. Remove the duplicate class.
| :class=" | ||
| reaction.current_user_reacted | ||
| ? 'bg-blue-100 text-blue-700 hover:bg-blue-200' | ||
| ? 'bg-surface-blue-2 text-blue-700 hover:bg-blue-200' |
There was a problem hiding this comment.
The “current user reacted” classes still use hardcoded Tailwind colors (text-blue-700, hover:bg-blue-200). These won’t adapt to dark mode the way semantic tokens do, and can end up with poor contrast. Replace them with the appropriate semantic ink/surface tokens for the reacted state.
| ? 'bg-surface-blue-2 text-blue-700 hover:bg-blue-200' | |
| ? 'bg-surface-blue-2 text-ink-blue-3 hover:bg-surface-blue-3' |
| class="flex size-8 cursor-pointer items-center justify-center rounded-full bg-surface-gray-2 hover:bg-surface-gray-3" | ||
| :class="{ 'ring-2 ring-blue-500': selected }" | ||
| > |
There was a problem hiding this comment.
The selection ring color is still using a hardcoded Tailwind color (ring-blue-500). For consistent theming/dark-mode support, this should be switched to a semantic token-based ring/outline class that matches the design system.
|
please resolve conflicts & the changes suggested by Co pilot, some of them are unrelated, you can ignore those. |
Summary
Adds dark mode support to Helpdesk, building on the approach from this PR.
Screen.Recording.2026-04-24.at.7.41.32.PM.mov
Technical Decisions
Color token migration
update-tailwind-classes.jsscript (ref) acrossdesk/srcto move from literal Tailwind shades (bg-gray-100,text-white, etc.) to semantic tokens.bg-gray-400,bg-gray-600,border-gray-100,bg-black,text-black). Chose closest exact token per the frappe-ui colors table (surface-gray-4/surface-gray-5/surface-gray-7/ink-gray-9).Theme toggle
useTheme()wired inApp.vuefor initialization.lightEmail HTML sanitization
stripEmailColors()util removescolor/background/background-color/border-colorinline styles andbgcolor/colorattributes from inbound email HTML so the iframe's themed CSS controls foreground/background. Keeps font, spacing, layout, alignment intact.cc: @RitvikSardana @niraj2477