🎨 Improved dark mode sidebar styling#28895
Conversation
ref https://linear.app/tryghost/issue/DES-1397/ - Added a dedicated sidebar background token so the dark sidebar reads as a distinct surface rather than pure gray-950 - Reworked the sidebar search field in dark mode: gray-900/30 fill with a gray-900/50 border, plus a lighter text and a slightly stronger border on hover so the affordance is clearer against the new background
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @tryghost/admin-x-settings:test:acceptance |
✅ Succeeded | 10m | View ↗ |
nx run-many --target=build --projects=tag:publi... |
✅ Succeeded | 2s | View ↗ |
nx run-many -t test:unit -p @tryghost/admin,@tr... |
✅ Succeeded | 7m 7s | View ↗ |
nx run @tryghost/admin:build |
✅ Succeeded | 4m 9s | View ↗ |
nx run ghost-admin:test |
✅ Succeeded | 2m 41s | View ↗ |
nx run @tryghost/activitypub:test:acceptance |
✅ Succeeded | 37s | View ↗ |
nx run-many -t lint -p @tryghost/admin,@tryghos... |
✅ Succeeded | 20s | View ↗ |
nx run ghost:build:assets |
✅ Succeeded | 2s | View ↗ |
nx run ghost:build:tsc |
✅ Succeeded | 6s | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-06-26 07:47:27 UTC
ref https://linear.app/tryghost/issue/DES-1397/ - Added a dedicated sidebar border token (#25272A) so the right edge of the dark sidebar reads as a distinct surface, matching the new background tone - Tuned active and hover backgrounds: active items now use gray-900/60, non-active hover uses gray-900/30, and active-item hover stays at the active color instead of lightening - Dimmed inactive menu item text (and their icons via currentColor) from gray-100 to gray-500 so the active item reads as the primary signal
ref https://linear.app/tryghost/issue/DES-1397/ - Aligned the "create post" plus icon, sidebar count badges, and the user menu email with the ⌘K kbd colour (gray-800) in dark mode so the secondary affordances all read with the same weight against the new sidebar surface
ref https://linear.app/tryghost/issue/DES-1397/ - Applied the sidebar's active/hover palette to PageMenuItem and the button, button-sm, and pill Tabs variants in dark mode (gray-900/30 hover, gray-900/60 active, active state preserved on hover) so the Analytics navigation and in-card tabs read as part of the same surface system as the sidebar
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/shade/src/components/ui/sidebar.tsx`:
- Around line 533-537: The sidebar button styles in the component are using
hard-coded dark theme color utilities, which should be replaced with semantic
token-based classes. Update the class strings in the Sidebar menu/button styling
logic (including the variant definitions for the button styles) to remove
`dark:` color utilities like `dark:hover:bg-gray-900/30` and any similar
hard-coded dark text/background classes, and move that visual intent into
semantic tokens defined in `theme-variables.css`. Keep the component’s Tailwind
classes token-driven so the `sidebar.tsx` styles follow Shade’s color
guidelines.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d18c7cfe-a7d1-4f5e-8575-9a3c46aa8897
📒 Files selected for processing (4)
apps/admin/src/layout/app-sidebar/nav-content.tsxapps/admin/src/layout/app-sidebar/user-menu.tsxapps/shade/src/components/ui/sidebar.tsxapps/shade/theme-variables.css
✅ Files skipped from review due to trivial changes (1)
- apps/admin/src/layout/app-sidebar/nav-content.tsx
| 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md px-3 py-2 text-left text-control font-medium text-sidebar-foreground ring-sidebar-ring outline-hidden transition-all group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent/70 hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[active=true]:hover:bg-sidebar-accent data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground dark:hover:bg-gray-900/30 dark:data-[active=true]:hover:bg-sidebar-accent [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', | ||
| { | ||
| variants: { | ||
| variant: { | ||
| default: 'hover:bg-sidebar-accent/70 hover:text-sidebar-accent-foreground', | ||
| default: 'hover:bg-sidebar-accent/70 hover:text-sidebar-accent-foreground dark:hover:bg-gray-900/30', |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Replace hard-coded dark color utilities with semantic token classes.
dark:hover:bg-gray-900/30 and dark:text-gray-800 violate Shade component token rules and hardcode theme behavior in the component layer. Move this intent into semantic tokens (via theme-variables.css) and keep component classes token-based.
Suggested direction
- ... dark:hover:bg-gray-900/30 ...
+ ... hover:bg-sidebar-accent/70 ...
- ... text-text-secondary ... dark:text-gray-800
+ ... text-text-secondary ...As per coding guidelines: “Do not add dark: Tailwind variants for colour in Shade components; use semantic tokens instead” and “Use semantic tokens … instead of hard-coded … values.”
Also applies to: 651-651
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/shade/src/components/ui/sidebar.tsx` around lines 533 - 537, The sidebar
button styles in the component are using hard-coded dark theme color utilities,
which should be replaced with semantic token-based classes. Update the class
strings in the Sidebar menu/button styling logic (including the variant
definitions for the button styles) to remove `dark:` color utilities like
`dark:hover:bg-gray-900/30` and any similar hard-coded dark text/background
classes, and move that visual intent into semantic tokens defined in
`theme-variables.css`. Keep the component’s Tailwind classes token-driven so the
`sidebar.tsx` styles follow Shade’s color guidelines.
Source: Coding guidelines
ref https://linear.app/tryghost/issue/DES-1397/ - Introduced --tab-hover and --tab-active design tokens (registered as Tailwind bg-tab-hover and bg-tab-active utilities) so the repeated gray-900/30 and gray-900/60 dark mode values for tabs and the page menu live in one place - Pointed the dark --sidebar-accent at --tab-active so the active surface value is no longer duplicated between the sidebar and tab systems
ref https://linear.app/tryghost/issue/DES-1397/ - Raised the dark mode --border from solid gray-950 to gray-900 at 50% so dividers and outlines read at the right contrast against the new panel surfaces - Pointed --sidebar-border at --border so the sidebar's right edge matches the rest of the system instead of carrying its own one-off value
ref https://linear.app/tryghost/issue/DES-1397/ - Bumped the create post + icon to white on hover in dark mode so it reads as the primary affordance against the new active surface fill
ref https://linear.app/tryghost/issue/DES-1397/ - Dropped the muted top-right gradient on the Analytics overview's "Latest post performance" card so the section sits flat against the page surface in both light and dark mode
ref https://linear.app/tryghost/issue/DES-1397/ - Added a --button-hover token (gray-100 light, gray-900/30 dark) so outline buttons, the dropdown button variant, and the SelectTrigger share one named hover surface - Switched the input-surface recipe to bg-transparent and border-border in dark mode so the Select trigger (plus Input/Textarea/InputGroup) blends into its container the way they already do in light mode and matches the lifted base border colour - Aligned outline / dropdown button borders with the recipe so all control surfaces use the same border-border value
ref https://linear.app/tryghost/issue/DES-1397/ - Pointed dark --surface-elevated and --popover at the existing --color-sidebar-bg so cards, popovers, dropdowns, and the sidebar share one elevated surface value - Bumped the sidebar user menu popover one step lighter so it reads as floating above the sidebar instead of melting into it
ref https://linear.app/tryghost/issue/DES-1397/ - Added a shared --interactive-hover token (aliased to --button-hover) so dropdown options, table rows, and the existing outline button hover all reference one named value - Routed SelectItem and every DropdownMenu item/sub-trigger through --interactive-hover so list options match the outline button hover - Switched the Shade TableCell row hover to --interactive-hover and dropped the tags list's custom row hover + cell-hover suppressor so it picks up the standard treatment - Moved --members-sticky-hover-bg into theme-variables so the members list sticky column hovers with a solid composite of the new hover colour over the page surface in both modes
ref https://linear.app/tryghost/issue/DES-1397/ - Added dark:bg-popover dark:text-popover-foreground to the Shade TooltipContent so every default tooltip lifts above the page using the elevated colour instead of inverting to a light-on-dark pill - Added dark:bg-popover to the TrendBadge's custom tooltip override so the Analytics overview trend tooltip stops melting into the page
ref https://linear.app/tryghost/issue/DES-1397/ - Added a --table-row-hover token (gray-50 light, sidebar-bg dark) and routed the Shade TableCell, the top posts list, and the members list sticky column through it so row hovers settle on the elevated colour instead of the generic interactive hover - Reused --interactive-hover for the analytics top posts overlay, comments list rows, filter dropdown options (CommandItem and the load-more button), the filter add button, and the kpis tab variant - Added a --control-border token (gray-200 light, solid gray-900 dark) for controls that were lacking contrast and applied it to outline / dropdown Button variants, the input-surface recipe, and the filter add button - Bumped --color-sidebar-bg from oklch 0.23 to 0.25 so sidebar, surface-elevated, popover, and table-row-hover lift together - Dropped the members filter add button's border-input override so it picks up the new control border
ref https://linear.app/tryghost/issue/DES-1397/ - Reworked ToggleGroup to sit on the page surface with a border and a 9px radius (--input-group-radius) so it reads as a flat control bar instead of a tinted muted block - Routed the active Toggle state through --tab-active so it visually matches the active Tabs / sidebar surface in both modes - Promoted the user menu's inline oklch(0.27 0.006 260) into a named --surface-elevated-2 token (light = gray-100, dark = oklch 0.27) so "above-elevated" popovers and controls share one named value
ref https://linear.app/tryghost/issue/DES-1397/ - Nudged --color-sidebar-bg and --surface-elevated-2 chroma from 0.006 to 0.009 so the sidebar, elevated panels, popovers, and row hover read with a subtle cool tint instead of feeling flat / dull - Swapped the Analytics top posts feature image placeholder's dark background from the inline #36373a to --surface-elevated-2 so it sits a touch darker and shares the named elevated-2 surface
ref https://linear.app/tryghost/issue/DES-1397/ - Overrode the full gray-* and grey-* scale inside .dark with the same lightness and hue but roughly half the chroma so dark mode surfaces, text, and borders feel less tinted and cleaner without affecting light mode
ref https://linear.app/tryghost/issue/DES-1397/ - Sourced --tab-active from a slightly more tinted oklch (chroma 0.012) instead of the now-desaturated --color-gray-900 so active tabs and sidebar items keep a hint of warmth against the cleaner gray scale - Dropped --control-border from solid gray-900 to gray-900 at 70% so outline buttons, inputs, and the dropdown trigger sit between the base --border and the previous solid value
ref https://linear.app/tryghost/issue/DES-1397/ - Overrode the .gh-posts-list-item-group hover background in app-dark with oklch(0.23 0.009 260) so the legacy posts list matches the Shade --table-row-hover surface used by the React tables - Locked .gh-post-list-feature-image base and hover backgrounds to oklch(0.27 0.009 260) so the legacy feature image placeholder matches the --surface-elevated-2 colour used by the analytics top posts placeholder
ref https://linear.app/tryghost/issue/DES-1397/ - Added dark:data-[state=checked]:bg-green-500 to the Switch track and dark:bg-white to the thumb so the checked Switch reads as a green toggle with a white circle in dark mode instead of an inverted white-track-with-black-thumb
ref https://linear.app/tryghost/issue/DES-1397/ - Added a global html.theme-switching kill-rule that nukes transitions and animations on every element and pseudo-element while the class is set, so the toggle paints the new theme in a single frame instead of crossfading every element with its own duration - Wrapped _setAdminTheme so it adds .theme-switching before flipping the .dark class, then releases it via a double requestAnimationFrame after the dark stylesheet finishes loading (covers both first-time lazy load and cached subsequent toggles, plus the catch path)
ref https://linear.app/tryghost/issue/DES-1397/ - Repointed --background in dark to oklch(0.178 0.006 271) (#101114) so the page sits a touch darker than the previous --color-black - Repointed --color-sidebar-bg in dark to oklch(0.204 0.007 258) (#15171A) which cascades to the sidebar, --surface-elevated, --popover, and --table-row-hover - Routed --card through --background in dark so analytics cards sit flat on the new page surface instead of lifting - Switched the analytics header and Navbar dark:bg-black to dark:bg-background so the sticky header tracks the new page colour
ref https://linear.app/tryghost/issue/DES-1397/ - Repointed --main-bg-color and --dark-main-bg-color in app-dark.css from #15171A to #101114 so the legacy Ember admin page surface matches the new React --background colour
ref https://linear.app/tryghost/issue/DES-1397/ - Repointed .gh-main-white (used by the lexical editor route) to --dark-main-bg-color so the editor canvas sits on the new page bg - Routed .gh-editor-title / .gh-editor-excerpt, .gh-btn-editor, .gh-editor-post-status, and .gh-editor-wordcount-container to the same token so the title field, header controls, post status pill, and word count chip stop reading as lighter islands inside the editor view
ref https://linear.app/tryghost/issue/DES-1397/ - Overrode .fullscreen-modal-total-overlay (publish-flow, preview, and update-flow editor modals) and .gh-publish-setting-form to use --dark-main-bg-color so they sit on the new page surface instead of the lighter --white default
ref https://linear.app/tryghost/issue/DES-1397/ - Routed the comments list row hover through bg-table-row-hover so it matches the Shade Table row hover and the top posts list instead of using the generic interactive hover - Updated the Ember post list row hover override to oklch(0.204 0.007 258) / #15171A so the legacy posts list tracks the current --table-row-hover value - Switched the Post analytics header and Navbar dark:bg-black to dark:bg-background so the sticky header matches the new page surface in dark mode
ref https://linear.app/tryghost/issue/DES-1397/ - Bumped dark --destructive from --color-red-900 to --color-red-500 so destructive menu items, links, and buttons (e.g. the bulk Delete action in the members ... menu) read clearly against the dark surfaces instead of disappearing into them
ref https://linear.app/tryghost/issue/DES-1397/ - Routed the AutomationHeader top bar and the right step sidebar to bg-surface-elevated so they lift above the canvas instead of blending into the page surface - Switched the AutomationCanvas wrapper and the React Flow --xy-background-color (dark) to --background so the canvas itself sits on the new page colour rather than the legacy --surface-page / --color-black values - Also dropped the gap-x-4 hover artifact on the automations list by promoting the row hover bg to TableRow so the bg spans the gap between cells
ref https://linear.app/tryghost/issue/DES-1397/ - Added dark:bg-white/8 to AlertDialogOverlay so it matches the Dialog overlay (which already had the same dark variant). The previous bg-black/30-only treatment disappeared against the very dark page surface, leaving alert dialogs (e.g. the automations discard confirmation) with no visible backdrop in dark mode
ref https://linear.app/tryghost/issue/DES-1397/ - Swapped bg-background for bg-transparent on the outline and dropdown Button variants so they inherit whatever surface sits behind them. The previous bg-background assumed the button always sits on the page bg, which carved a darker rectangle out of elevated surfaces (e.g. the delete step button in the automations step sidebar). Hover feedback is unchanged via hover:bg-button-hover
ref https://linear.app/tryghost/issue/DES-1397/ - Swapped hover:bg-accent/50 for hover:bg-table-row-hover on the help card classNames in the Analytics overview HelpCard, the Automations list HelpCard, and the members empty-state HelpCard so all three share the same hover surface as table rows
ref https://linear.app/tryghost/issue/DES-1397/ - Replaced the green focus border + green box-shadow + white bg flip on .gh-input and .gh-input-x with a neutral midgrey border and a subtle 25% midgrey outer ring so Ember controls match the Shade input recipe in both light and dark mode - Repointed the post settings date/time picker focus-within style to the same neutral pattern instead of the green outline - In dark mode, switched the bare inputs, .gh-input-x, and the settings sidebar select / date-time pickers to transparent backgrounds with visible --lightgrey-l2 borders so the field is carried by the border rather than a darker fill island - Reused the same focus treatment on the ember-power-select trigger active / expanded state so the Labels selector and other dropdowns show a border + ring consistently - Multi-tag selector (.ember-power-select-multiple-trigger) now uses transparent bg + --lightgrey-l2 border in dark - Dropped the .settings-menu-container ember-power-select-trigger [aria-expanded=true] border-transparent rule that was wiping the focused border on the Labels / Tags fields - Removed bare input:focus from the dark focus selector so the nested <input> inside the date/time picker doesn't render an extra ring on top of the wrapper's ring
ref https://linear.app/tryghost/issue/DES-1397/ - Restored ToggleGroup container to bg-muted in light and scoped the bg-background + border treatment to dark only so the toggle reads as a grey rail with a white active option (plus its existing shadow) in light, and as a bordered control in dark - Restored Toggle active state to bg-background in light with dark:data-[state=on]:bg-tab-active so the active option keeps the light-mode white-pill look while still using the new dark surface - Added explicit bg-white to the sidebar search trigger so it stays white in light mode (after the outline Button base went transparent)
ref https://linear.app/tryghost/issue/DES-1397/ - Halved chroma again across the full dark gray-* / grey-* scale (e.g. gray-700 from 0.0143 to 0.0072, gray-900 from 0.0076 to 0.0038) so dark mode surfaces and text read as cleaner neutral greys - Pulled the matching one-off oklch tints down too: sidebar-bg 0.007 to 0.004, --background 0.006 to 0.003, --surface-elevated-2 0.009 to 0.004, and the tab-active base 0.012 to 0.006
ref https://linear.app/tryghost/issue/DES-1397/ - Bumped dark --primary from --color-gray-100 (~97% lightness) to --color-gray-200 (~95%) so the default Button bg sits a touch below pure white and reads less like an eye burn while still keeping plenty of contrast with the black primary-foreground
ref https://linear.app/tryghost/issue/DES-1397/ - Aligned DropdownMenuContent / DropdownMenuSubContent, SelectContent, and PopoverContent on a single visual: bg-surface-elevated-2, a subtler border-border/30, and the standard shadow-md so the three Radix popover surfaces read as the same component - Set the inner Command bg to bg-transparent so the filter dropdown picks up the PopoverContent surface instead of being overlaid by bg-popover (caused the filter dropdown to render at the darker --color-sidebar-bg instead of --surface-elevated-2) - Pulled --surface-elevated-2 in dark from oklch 0.27 down to 0.235 so the elevated-2 surface sits between --surface-elevated (0.204) and the previous brighter value (cascades to the user menu, the active Toggle pill, and the analytics feature image placeholder too) - Added direct .dark .shadow-xs through .dark .shadow-2xl rules with black at 0.35-0.65 alpha so every Shade consumer of the default shadow utilities gets meaningfully darker shadows in dark mode (Tailwind v4 inlines the @theme --shadow-* values at compile time so a token-level override couldn't propagate)
ref https://linear.app/tryghost/issue/DES-1397/ - Dialed avatar fill saturation from 75 to 45 across the Shade Avatar component, the Posts newsletter feedback avatar, and the legacy gh-member-avatar Ember component so members list, comments list, member details, and member activity avatars stop reading as oversaturated in both modes - Reverted light --surface-elevated-2 from --color-gray-100 back to --color-white so the dropdown surfaces (DropdownMenu, Select, Popover, plus the user menu / placeholder consumers' light variant) stop showing as a light grey block in light mode - Bumped the light mode dropdown border opacity from border-border/30 to border-border/60 while keeping dark:border-border/30 so the three dropdown surfaces have a clearer visible edge against light backgrounds
ref https://linear.app/tryghost/issue/DES-1397/ - Repointed .ember-power-select-multiple-option background from var(--white) (= #15171A, now barely distinguishable from the new page bg) to var(--lightgrey) so label and tag chips in Ember selectors lift above the surface in dark mode

Summary
Polish pass on Shade's dark mode sidebar — scoped strictly to dark mode (light mode untouched).
--color-sidebar-bgtoken so the dark sidebar reads as a distinct surface instead of puregray-950.gray-900/30gray-900/50gray-400, border togray-900/80ref https://linear.app/tryghost/issue/DES-1397/
Test plan
🤖 Generated with Claude Code