Skip to content

fix(colors): Replace hardcoded colors with theme tokens#6040

Open
dan-rukas wants to merge 7 commits into
OHIF:masterfrom
dan-rukas:fix/hardcoded-colors
Open

fix(colors): Replace hardcoded colors with theme tokens#6040
dan-rukas wants to merge 7 commits into
OHIF:masterfrom
dan-rukas:fix/hardcoded-colors

Conversation

@dan-rukas
Copy link
Copy Markdown
Member

@dan-rukas dan-rukas commented May 26, 2026

Context

Replaces hardcoded hex color values across CSS files, icon SVGs, and usage sites so the UI responds to theme changes. Previously, ~40 hex values were frozen to the default OHIF dark palette regardless of any theme override.

32 files changed — 126 insertions, 129 deletions.

  • 6 CSS/component files: hardcoded hex → CSS variable tokens
  • 19 icon SVG files: hardcoded hex → currentColor, hsl(var(--accent)), or transparent
  • 7 usage-site files: explicit color classes added where parent inheritance wasn't sufficient

Changes & Results

CSS / Component Changes (6 files)

LayoutSelector.tsx — Grid cell unhovered state

  • bg-[#04225b]bg-accent

Onboarding.css — Shepherd.js onboarding buttons

  • !bg-[#348cfd]!bg-primary
  • !text-[#348cfd]!text-primary

styles.css — Custom scrollbar

  • scrollbar-color: #173239hsl(var(--neutral) / 0.5) (matches ScrollArea and ImageScrollbar convention)
  • Removed 2× background-color: #041c4a that overrode existing @apply bg-popover on webkit scrollbar thumb

ProgressLoadingBar.css — Loading bar track

  • background-color: #091731hsl(var(--muted))

DicomTagBrowser.css — DICOM tag table

  • color: #ffffffhsl(var(--foreground))
  • Removed border-top: 1px solid #ddd (cleaner without row separators)
  • Removed color: '#20A5D6' (quoted hex was invalid CSS — never applied)

colorPickerDialog.css — Segmentation color picker

  • background: #090c29transparent (inherits from parent dialog)
  • Added box-shadow: none to remove react-color's default drop shadow

Icon SVG Changes (19 files)

A. Single-color → currentColor (9 files, 10 icons)

One hex color per icon replaced with currentColor. Icons now inherit parent text color.

File Old New
AlertOutline.tsx stroke="#5ACCE6" currentColor
ContentNext.tsx fill="#FFFFFF" currentColor
ContentPrev.tsx fill="#FFFFFF" currentColor
IconTransferring.tsx stroke="#5ACCE6" currentColor
IllustrationNotFound.tsx stroke="#358CFD" ×11 currentColor
Plus.tsx stroke="#348CFD" currentColor
Search.tsx stroke="#348CFD" currentColor
Show.tsx stroke="#348CFD" ×5 currentColor
SocialGithub.tsx fill="#FFFFFF" currentColor
Upload.tsx stroke="#348CFD" currentColor
B. Two-tone → currentColor + contrast marks stay (5 files)

Theme-colored shape becomes currentColor. Internal contrast marks (#FFF, #000) stay hardcoded — they provide the visual detail against the colored background.

File Theme color replaced Contrast kept
Alert.tsx fill="#B70D11"currentColor stroke="#FFF" (exclamation mark)
NotificationInfo.tsx fill="#0944B3"currentColor stroke="#FFF" (info symbol)
NotificationWarning.tsx fill="#F3DC43" + stroke="#F3DC43"currentColor stroke="#000" (exclamation)
StatusTracking.tsx stroke="#5ACCE6" + fill="#5ACCE6"currentColor stroke="#000" (checkmark)
LoadingSpinner.tsx fill="#348CFD" (ring, 25% opacity) + fill="#5ACCE6" (arc) → both currentColor

LoadingSpinner also fixes a minor issue: destructures className from props to avoid undefined in the className template string.

C. Layout thumbnails → hsl(var(--accent)) (1 file, 4 icons)

Active pane fill in layout selector thumbnails. Uses --accent CSS variable directly as an SVG fill attribute.

Icon in Layout.tsx Change
LayoutAdvanced3DFourUp fill="#263A71"fill="hsl(var(--accent))"
LayoutAdvanced3DMain fill="#263A71"fill="hsl(var(--accent))"
LayoutAdvanced3DOnly Moved fill from <g> to child <rect>, → fill="hsl(var(--accent))"
LayoutAdvanced3DPrimary fill="#263A71"fill="hsl(var(--accent))"

Why hsl(var(--accent)) instead of currentColor: these thumbnails have both outlined panes (stroke) and one filled pane (the active/highlighted one). currentColor would make the filled pane match the stroke color. The accent token gives it a distinct highlight that tracks the theme.

D. Status badge dark fills → transparent (3 files)

Status badges had fill="#0D0E24" (near-black) as a background fill inside their ring — the old dark theme color baked into the icon. Replaced with transparent so the badge becomes a ring-only indicator that works on any background.

File Changes
StatusAlert.tsx fill="#0D0E24"transparent, stroke="#7BB2CE"currentColor
StatusLocked.tsx fill="#0D0E24"transparent, stroke="#7BB2CE"currentColor
StatusUntracked.tsx fill="#0D0E24"transparent (stroke was already currentColor)
E. Tools gear → hsl(var(--primary)) (1 file, 2 icons)

The gear icon on ToolLayout and ToolLayoutDefault uses --primary to stay visually distinct from the grid lines in the same icon.

Icon in Tools.tsx Change
ToolLayout gear (line 79) stroke="#348CFD"stroke="hsl(var(--primary))"
ToolLayoutDefault gear (line 1828) stroke="#348CFD"stroke="hsl(var(--primary))"
F. Helpers → hsl(var(--accent)) + currentColor (1 file, 3 icons)

Segmentation helper icons (HelperCombineSubtract, HelperCombineIntersect, HelperCombineAdd) had three types of hardcoded color:

Old New Count
fill="#1A3F7E" (overlap region) fill="hsl(var(--accent))" ×3
stroke="white" (dashed borders) stroke="currentColor" all strokes
fill="white" (labels) fill="currentColor" all label fills

Usage-Site Updates (7 files)

Most icons inherit appropriate color from their parent context. These sites needed explicit color classes because the parent doesn't provide the right color.

File Icon Class added Reason
TrackingStatus.tsx StatusTracking text-highlight Tracked status indicator (cyan)
DataSourceConfigurationModalComponent.tsx status-tracked (ByName) text-highlight Data source tracked indicator
DicomUploadProgressItem.tsx icon-transferring (ByName) text-highlight Upload in-progress state
DicomUploadProgressItem.tsx icon-alert-small (ByName) text-destructive Upload failed state
DicomUploadProgressItem.tsx icon-alert-outline (ByName) text-highlight Upload cancelled state
NotFound.tsx IllustrationNotFound text-primary on parent div 404 page illustration
Sonner.tsx LoadingSpinner text-highlight Toast loading state
Sonner.tsx StatusError text-destructive Toast error state

Not Changed (intentional)

Legacy-only icons — reverted to hardcoded (components being replaced):

  • CheckBoxChecked, CheckBoxUnChecked — only used in platform/ui/Select.tsx
  • LaunchArrow, LaunchInfo — only used in WorkList (being replaced with new Study List)
  • Magnifier — only used in platform/ui/EmptyStudies.tsx

These were converted then explicitly reverted because their only consumers are legacy platform/ui components that are being phased out. Converting the icons without updating those legacy consumers would break their appearance.

Icons already correct (no changes needed):

  • Cancel.tsx, Clear.tsx — already use currentColor

Intentionally hardcoded (brand/regulatory marks):

  • OHIFLogo, OHIFLogoColorDarkBackground, LoadingOHIFMark — brand logos
  • InvestigationalUse — regulatory badge
  • StatusWarning — semantic warning yellow
  • IconColorLUT — LUT visualization colors

Deferred (separate scope):

  • LineChart / D3 components — requires design review of chart color interaction with D3.js
  • WindowLevel / WindowLevelHistogram — histogram fill/line default props
  • BackgroundColorSelect — hex values are functional data (viewport background presets), not styling
  • DicomMicroscopyViewport — OpenLayers CSS custom properties and legacy Less variables (third-party theming)

Testing

CSS / Component Tests

  • LayoutSelector — Toolbar → Layout → verify grid cell unhovered/hovered contrast
  • Onboarding — Clear localStorage.removeItem('shownTours'), refresh, open study → verify button colors
  • Scrollbar — DICOM Tag Browser → scroll tag list → verify thumb color
  • ProgressLoadingBar — Navigate to study or /local import → verify track color
  • DicomTagBrowser — Open tag browser → verify row text color, no row borders, headers unchanged
  • colorPickerDialog — Segmentation → segment menu → "Change Color" → verify transparent background, no shadow

Icon Tests

  • Single-color icons — Plus, Search, Upload, Show render in parent text color
  • Content navigation — ContentNext/ContentPrev chevrons visible in AllInOneMenu
  • Tools gear — Toolbar layout button gear is primary-colored, distinct from grid lines
  • Layout selector — Layout popover → 3D presets → active pane uses accent color
  • Status badges — StatusTracking (cyan via text-highlight), StatusAlert/Locked/Untracked rings visible, interiors transparent
  • Loading spinner — Toast loading state → monochrome spinner visible and animating, no className error
  • Notifications — NotificationInfo/Warning → circle + internal mark contrast in toasts
  • Alert icon — DicomUpload failed state → red via text-destructive
  • Helpers — Segmentation combine operations → overlap region accent-colored, labels/borders in text color
  • IllustrationNotFound — Navigate to /nonexistent → 404 illustration in primary blue
  • Upload progress — DicomUpload states: transferring (cyan), failed (red), cancelled (cyan)

Regression

  • Default dark theme — No visual regression with default OHIF colors
  • Theme presets — If appearance dialog is available, all converted icons/components follow the active theme
  • WorkList — LaunchArrow/LaunchInfo unchanged (still hardcoded, still work with text-black parent)
  • Reverted icons — CheckBoxChecked/Unchecked in legacy Select, Magnifier in EmptyStudies render correctly

Checklist

PR

  • My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • [] The documentation page has been updated as necessary for any public API
    additions or removals.

Tested Environment

  • OS: macOS Sequoia 15.7.4 (24G517), Apple M4 Pro
  • Node version: 20.19.1
  • Browser: Chrome (latest) 148.0.7778.179 (Official Build) (arm64)

Greptile Summary

This PR replaces ~40 hardcoded hex color values across 32 files with CSS theme tokens (currentColor, hsl(var(--accent)), hsl(var(--primary)), hsl(var(--muted)), etc.) so that icons, scrollbars, and components respond to theme changes instead of being frozen to the OHIF default dark palette.

  • Icon SVGs (19 files): Single-color icons move to currentColor; two-tone icons keep contrast marks hardcoded (#FFF/#000) while the theme-colored shape uses currentColor; layout thumbnails and gear icons use explicit hsl(var(--)) tokens where currentColor would conflict with adjacent strokes; status badge backgrounds switch from near-black fills to transparent.
  • CSS / Component files (6 files): Scrollbar thumb, progress bar track, DICOM tag browser text, color picker background, onboarding buttons, and layout grid cells all converted to design-token equivalents. The LoadingSpinner also fixes a pre-existing bug where a missing className prop would produce \"… animate-spin undefined\" as the class string.
  • Usage sites (7 files): Explicit text-highlight / text-destructive / text-primary classes added at render sites where parent context does not supply the correct inherited color for the newly currentColor-aware icons.

Confidence Score: 4/5

Safe to merge; all changes are purely visual/presentational and carry no runtime logic risk.

The code changes are consistently correct across all 32 files — hardcoded hex values are replaced with appropriate tokens, the LoadingSpinner className bug is properly fixed, and the decision to use hsl(var(--accent)) rather than currentColor for layout thumbnails and gear strokes is well-justified. The breadth of visual surface touched (19 icon components, 6 CSS files, and 7 usage sites) warrants visual regression confirmation in the default dark theme and any custom theme, but the code itself has no logic errors.

Visual spot-checking of the status badge icons (StatusAlert, StatusLocked, StatusUntracked) and the LoadingSpinner in Sonner toasts is recommended since their appearance changed most significantly.

Important Files Changed

Filename Overview
platform/ui-next/src/components/Icons/Sources/LoadingSpinner.tsx Destructures className from props to avoid injecting the string "undefined" into the className template literal when no className is passed; also converts both fill colors to currentColor.
platform/ui-next/src/components/Icons/Sources/Layout.tsx Replaces hardcoded #263A71 fills with hsl(var(--accent)) in 4 layout thumbnails; LayoutAdvanced3DOnly correctly moves fill from to its single child with no other dependants.
platform/ui-next/src/components/Icons/Sources/Tools.tsx Gear stroke on ToolLayout and ToolLayoutDefault changed to hsl(var(--primary)); works correctly for inline SVGs where CSS custom properties are resolved by the browser's CSS engine.
platform/ui-next/src/components/Icons/Sources/Helpers.tsx HelperCombineSubtract, HelperCombineIntersect, and HelperCombineMerge updated: overlap fills → hsl(var(--accent)), white strokes/fills → currentColor. PR description says "HelperCombineAdd" but diff shows HelperCombineMerge — documentation discrepancy only.
platform/ui-next/src/components/Sonner/Sonner.tsx Adds text-highlight to LoadingSpinner and text-destructive to StatusError icons so they inherit the right color via currentColor after the icon changes.
platform/ui-next/src/assets/styles.css Scrollbar thumb color changed to hsl(var(--neutral)/0.5) and two hardcoded background-color overrides removed from webkit thumb rules that were fighting the existing @apply bg-popover.
extensions/default/src/DicomTagBrowser/DicomTagBrowser.css Row text color changed to hsl(var(--foreground)); border-top separator removed; invalid quoted hex color '#20A5D6' removed (was never applied).
platform/ui-next/src/components/LayoutSelector/LayoutSelector.tsx Unhovered grid cell background changed from bg-[#04225b] to bg-accent, keeping hovered state as bg-primary.
extensions/cornerstone/src/components/DicomUpload/DicomUploadProgressItem.tsx Explicit text-highlight and text-destructive classes added to upload status icons so currentColor resolves correctly after the icon changes.
platform/app/src/routes/NotFound/NotFound.tsx Added text-primary to the illustration container div so IllustrationNotFound inherits the primary color via currentColor.

Reviews (1): Last reviewed commit: "Merge branch 'OHIF:master' into fix/hard..." | Re-trigger Greptile

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 26, 2026

Deploy Preview for ohif-dev failed. Why did it fail? →

Name Link
🔨 Latest commit e8b0a9e
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6a15d26bcae7b30008734731

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant