Skip to content

feat(frontend): accent colors and an optional glassy style - #6949

Open
Plus98ir wants to merge 8 commits into
MHSanaei:mainfrom
Plus98ir:feat/accent-colors-glassy
Open

Plus98ir wants to merge 8 commits into
MHSanaei:mainfrom
Plus98ir:feat/accent-colors-glassy

Conversation

@Plus98ir

@Plus98ir Plus98ir commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds an accent color picker (7 colors) and an optional "Glassy style" to the panel. Both work with the light, dark and ultra-dark themes and are saved per browser, the same way the theme is. With the defaults the panel looks exactly as it does today.

Why

Admins who run several panels want to tell them apart at a glance, and some want a softer look. Today the only appearance choice is light/dark/ultra-dark with a fixed blue.

What changes

  • Accent color: a palette button next to the theme switch, in the sidebar and in the mobile drawer. It offers seven swatches: the panel's own blue (default), red, pink, orange, yellow, green and purple. The choice becomes Ant Design's colorPrimary, so buttons, links, the active menu item, tabs and switches follow it.
    • Each color has its own shade per mode. On light, white text on it keeps a contrast of at least 4.5:1. On dark and ultra-dark it stays readable on cards, and orange, yellow and green buttons get dark text where white would fail contrast.
    • The command palette, the Jalali date picker and the overview sparklines now read the accent instead of a hard-coded blue.
  • Glassy style: a switch under the swatches.
    • Cards, the sidebar, the drawer and dialogs become frosted glass (translucent surface and backdrop blur) over a soft glow in the accent color. The main button gets an accent gradient.
    • Popups stay nearly opaque, so menus and pickers stay readable over busy pages.
    • Fixed table columns and sticky headers stay solid, so scrolled rows never show through them.
  • Mobile menu: the drawer is sized to its content (about 265px instead of a fixed width). The accent popover stays on screen inside it.
  • The new labels are translated into all 14 panel languages.

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Documentation
  • Tests only
  • Build / CI / tooling
  • Other

Areas affected

  • Frontend (UI / panel pages)
  • Backend (API endpoints, login, settings)
  • Xray config generation
  • Subscription (share links / Clash / JSON)
  • Statistics / traffic counters
  • Database / migrations
  • Install / upgrade script
  • Docker image
  • Multi-node (sub-nodes)
  • Telegram bot

How was this tested?

  • Frontend only, with no backend or API change. npm run typecheck, npm run lint, npm run format:check, npm run build and the unit tests pass (1422 tests). make gen leaves src/generated unchanged.
  • Manual checks in Chrome (desktop and phone width): every color on light, dark and ultra-dark, with Glassy style on and off. Checked the sidebar, the mobile drawer, the command palette, the Jalali date picker, the overview tiles, tables with fixed columns, modals and dropdowns.
  • Has been running on two production panels (v3.10.2, then v3.11.0) since 2026-10-25.

Screenshots / recordings

Accent picker (sidebar, light theme):

accent picker

Glassy style, dark + green:

glassy dark green

Glassy style, light + pink:

glassy light pink

Glassy style, ultra-dark + orange (Inbounds):

glassy ultra-dark orange

Breaking changes

None. The defaults (blue accent, Glassy style off) keep today's look. Both choices live in localStorage only.

Checklist

  • I tested the change locally and confirmed the described behavior.
  • I added or updated tests for the new behavior (when applicable). (Visual-only change: no unit test can catch it.)
  • go build ./... and the test suite pass locally.
  • For frontend changes: npm run lint, npm run typecheck, and npm run build pass.
  • I updated the Wiki / README / API docs if user-facing behavior changed.
  • My commits follow the project's existing message style.
  • I have no unrelated changes mixed into this PR.

…rk themes

A palette button next to the theme switch (sidebar and mobile drawer) opens
seven swatches: the panel's own blue (default), red, pink, orange, yellow,
green and purple. The color becomes Ant Design's colorPrimary, so buttons,
links, the active menu item, tabs and switches follow it; the choice is kept
per browser like the theme.

Each color has a shade per mode: on light, white text on it stays at 4.5:1 or
more; on dark and ultra-dark it is readable on the cards, and orange, yellow
and green buttons get dark text where white would not be. The command palette
and the Jalali date picker read the color instead of a fixed blue. Default
keeps today's look.
A switch under the accent colors turns it on, per browser. The page gets a soft
glow in the accent color, and cards, the sidebar, the drawer and dialogs become
frosted glass (translucent surface + backdrop blur). Popups stay nearly opaque
so menus and pickers keep reading over busy pages, and fixed table columns and
sticky headers stay solid so scrolled rows never show through them. The main
button gets an accent gradient. Works on light, dark and ultra-dark; off keeps
today's look.
…lassy glow

A healthy CPU/RAM/swap/disk tile drew its sparkline in a fixed blue; it now uses the accent color (warning and critical keep yellow and red). The glassy glow is sized to the viewport, so it covers most of the page on big screens too.
…dark accents

On a short phone screen the drawer's flex column squeezed the search box to a sliver once the page list outgrew the screen. The page list now scrolls on its own and the header, search box and log-out row keep their size. Dark themes use Smart Caddy's brighter accent tones (more readable on the dark cards), with dark button text where white would not read, and a softer glassy glow.
In the drawer the palette button sits near the middle, so a popover aligned to its right edge ran past the left edge of a phone screen and hid the first swatch. The drawer's popover opens centred under the button, and the swatches wrap on screens too narrow for seven in a row.
The drawer always opened at min(82vw, 320px), wider than its items need on a phone. It now takes the width of its widest row (the header with the brand and its actions, about 265px), capped at the old size so a long translation still wraps inside it.
@github-actions

Copy link
Copy Markdown
Contributor

Verdict: Fits with gaps. This adds a per-browser accent colour (seven swatches) and an optional frosted-glass skin to the panel. Both are reached from a new palette button beside the theme switch, and with the defaults (blue, glass off) the panel's Ant Design theme is the same as today. There is one regression, and it hits every operator, whether or not they ever open the picker: the command palette (Ctrl + K) appears to lose the highlight on the row that the arrow keys select, and its search icon is no longer blue. That happens because the palette's new colour rules point at theme variables that never reach the palette. This rests on reading the code and Ant Design's source; nothing was rendered. Two days before this PR opened, the maintainer closed #6827, a broader theme-customisation PR (presets, accent colour, appearance drawer), saying they would rather keep one consistent panel theme. This PR is a narrower take on the same idea, so whether it fits is the maintainer's call. I found no other open or merged work that overlaps it.

No blocking issues — 0 HIGH / 1 MEDIUM / 1 LOW open (2 new), 0 pre-existing

Reviewed head: 7ad962c

Evidence. Feature questions:

  • What it adds. An accentColor value in localStorage becomes the antd colorPrimary (frontend/src/hooks/useTheme.tsx:290-318). A glassTheme flag swaps in translucent surface tokens (useTheme.tsx:246-281) and sets html[data-glass], which frontend/src/styles/glass.css keys off.
  • Already offered? No. Base offers only the light/dark/ultra-dark cycle.
  • Defaults on upgrade. They hold for the antd config. With the accent on default, the light branch still applies LIGHT_BUTTON_TOKENS (useTheme.tsx:300), the dark branch adds no Button key (useTheme.tsx:316), and the date picker returns its base theme (frontend/src/components/form/DateTimePicker.tsx:87). They do not hold for the command palette (MEDIUM below).
  • Deployment shapes. It is frontend-only and stores nothing in the DB. Multi-node, migrations and the link generators are untouched. The login page and the subscription page also mount ThemeProvider (frontend/src/entries/login.tsx:26, frontend/src/entries/subpage.tsx:21), so they pick up a stored accent or glass flag whenever they share the panel's origin.

Design.

  • Maintainer's earlier stance. The owner turned down feat(theme): add token-based theme customisation with presets and appearance drawer #6827 to keep "one consistent, maintainable panel theme". A seven-colour picker plus a second surface skin is less than that PR offered, but it still adds two dimensions every future style change has to be checked across.
  • The glow ignores the accent. The PR says the glow is in the accent colour, but one of its three gradients is a fixed blue (glass.css:36), so a red or green accent still gets a blue patch in the top-right corner.
  • Dark text only on buttons. The dark-text fix covers only Button (useTheme.tsx:316). Other antd parts that put white on a solid colorPrimary fill, such as a checked checkbox tick, a solid radio button or a step number, keep white on the bright yellow, green, orange and pink dark shades. This is inferred from antd's token model, not rendered.
  • Same-origin panels share the choice. Saving per browser tells panels apart only when they sit on different origins. Several panels behind one hostname under different paths share one localStorage, so they share one accent.
  • Unmeasured cost of the glass rule. The glass override is a universal body * rule that sets two !important custom properties on every element (glass.css:46-49). Its cost on the large client tables has not been measured.

Findings

MEDIUM — the command palette's active-row highlight and icon colours resolve to nothing, on every theme and every accent, including the default.

  • What the PR changed. It replaced the palette's hard-coded colours with antd CSS variables at frontend/src/components/command-palette/CommandPalette.css:120, :129, :264-277 and :355-375, and inline at CommandPalette.tsx:358.
  • Where antd's variables are defined. antd declares those variables only under the theme's CSS-variable class. This repo pins that class as xui (useTheme.tsx:170), and the comment above it says the token block is injected "under a new class". Upstream, serializeCSSVar in @ant-design/cssinjs (lib/util/css-variables.js) builds a class selector from the key, not :root. genStyleHooks in @ant-design/cssinjs-utils (lib/util/genStyleUtils.js) returns cssVar.key as the class that each antd component puts on its own root element.
  • Why the palette doesn't get them. The palette is plain divs (CommandPalette.tsx:638-650), rendered beside the page outlet (frontend/src/layouts/PanelLayout.tsx:11-14). Above it are only ConfigProviders (frontend/src/main.tsx:53-59, frontend/src/i18n/AntdRootConfig.tsx:37-42), and those add no DOM element. So no ancestor of .command-palette-item carries the xui class.
  • What breaks. A var() with no fallback, or a color-mix() built on one, is then invalid at computed-value time. The .active background becomes transparent, and the icons inherit the surrounding text colour. The .active background is the only thing that marks the selected row, so Arrow-key navigation shows no cursor and Enter runs an item the operator cannot see. Base used literal hex values here, so it worked.
  • How sure this is. This comes from reading the code. The upstream source was fetched from unpkg's latest antd 6 / cssinjs packages, not the lockfile-pinned build, and nothing was rendered. The PR body says the palette was checked by hand, so a render would settle it.

LOW — the feature ships with no test, against the CLAUDE.md rule that a feature starts from a test of its first behaviour.

  • What the PR added. It added stored-value parsing (readAccent, useTheme.tsx:51-56) and theme-config branches (useTheme.tsx:283-332) with no test.
  • Why "no unit test can catch it" doesn't hold. That is the PR body's reason, but the repo already renders ThemeProvider and asserts the DOM it applies (frontend/src/test/storybook-theme.test.tsx:30-40).
  • What is unpinned. The fallback to default for an unknown stored accentColor, the data-glass attribute applied before mount, and the dark-ink button text: a regression in any of them passes CI.

Coverage

  • Read in full: frontend/src/hooks/useTheme.tsx, frontend/src/styles/glass.css, frontend/src/layouts/AppSidebar.tsx and .css, and the command palette's .tsx and .css. Also read: DateTimePicker.tsx, VitalTile.tsx, models/status.ts, and the callers of ThemeProvider and buildAntdThemeConfig.
  • antd CSS-variable scoping was checked against the upstream cssinjs / cssinjs-utils / antd 6 source on unpkg (latest, not the lockfile version) and against the palette's real DOM ancestry.
  • The backdrop-filter containing-block risk was checked: no position: fixed element sits inside a glass card, sider, drawer or modal (drawer handle, palette backdrop and login toolbar are all outside), so it is fine.
  • Sidebar selected-item colours are already tinted by the existing AppSidebar.css:420-425 overrides, so the bright dark accents do not get white menu text there.
  • i18n: all 14 locales carry the same 11 new keys (counted with grep). The dynamic menu.accentColors.${color} key is covered by the dead-key test's template-prefix harvest.
  • Not executed: nothing was built, rendered or run. The visual claims in the PR (contrast ratios, drawer fit-content width, header fit with a fifth button) are unverified.
  • CI for this head: CI is action_required, so it awaits maintainer approval, and nothing it covers (typecheck, lint, the Vitest/Storybook suite, make verify) is verified until a maintainer approves the run. prepare succeeded; review and Claude PR Review are in progress.
  • Discussion: the PR has no comments or inline threads yet. Related work: feat(theme): add token-based theme customisation with presets and appearance drawer #6827 (closed by the owner, same area), feat: 3X-UI(3.0.0) Multi-Node Architecture, Client Management, HWID Tracking, Host Management, Glass Morphism UI & Redis Cache #3635 (closed, glass UI). No duplicate open PR.
  • Diff: 23 files, about 586 additions and 42 deletions, all frontend plus translations. Every changed file was read.

The palette is plain divs under a ConfigProvider, which adds no DOM, so the
--ant-* variables it was switched to never reached it: the arrow-key row lost
its highlight and the search icon its colour, on every theme. --xui-accent is
now always set on :root (not only with the glass style on) and the palette
reads it; the glass glow's third gradient drew a fixed blue and now uses it too.
@Plus98ir

Copy link
Copy Markdown
Contributor Author

Fixed in 50848b3: the palette is plain divs under a DOM-less ConfigProvider, so --ant-* variables never reached it. --xui-accent is now always set on :root and the palette reads it; the glass glow's third gradient follows the accent too. frontend/src/test/accent-root-var.test.tsx covers both, plus the unknown-stored-accent fallback.

This branch has not been deployed

No deployments
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