diff --git a/e2e/specs/mock/scenarios/panel-shape.spec.ts b/e2e/specs/mock/scenarios/panel-shape.spec.ts
new file mode 100644
index 00000000000..ce674c55d94
--- /dev/null
+++ b/e2e/specs/mock/scenarios/panel-shape.spec.ts
@@ -0,0 +1,102 @@
+import { expect, test } from '@playwright/test';
+import type { Locator, Page } from '@playwright/test';
+import { clickHouseTheme } from '../../../../packages/client/src/theme/themes/clickhouse';
+import { NEW_CHAT_PATH } from '../helpers';
+import { probeStyle } from './style.helpers';
+
+/**
+ * The account menu and its help submenu are `.popover-ui` panels, whose rule draws the menu radius
+ * role, so the `rounded-lg` they also carried never painted. The default theme keeps the menu
+ * corner they have always shown, ClickHouse brings them onto its menu corner, and a reference theme
+ * that names only `menuRadius` reshapes them, with no rule written for the account menu itself.
+ * Every expected corner is resolved by the browser from a probe.
+ */
+test.describe.configure({ timeout: 120_000 });
+test.use({ viewport: { width: 1280, height: 800 } });
+
+const REFERENCE_RADIUS = '3px';
+
+const referenceTheme = {
+ version: 1,
+ name: 'menu-radius-reference',
+ modes: {
+ light: { appearance: { menuRadius: REFERENCE_RADIUS } },
+ dark: { appearance: { menuRadius: REFERENCE_RADIUS } },
+ },
+};
+
+type ThemeChoice = 'default' | 'clickhouse' | 'reference';
+
+const definitions: Record, object> = {
+ clickhouse: clickHouseTheme,
+ reference: referenceTheme,
+};
+
+async function openNewChat(page: Page, theme: ThemeChoice) {
+ await page.addInitScript(
+ (definition) => {
+ localStorage.removeItem('theme-colors');
+ localStorage.removeItem('theme-name');
+ if (definition === null) {
+ localStorage.removeItem('theme-definition');
+ localStorage.removeItem('theme-source');
+ return;
+ }
+ localStorage.setItem('theme-definition', JSON.stringify(definition));
+ localStorage.setItem('theme-source', 'definition');
+ },
+ theme === 'default' ? null : definitions[theme],
+ );
+ await page.goto(NEW_CHAT_PATH, { timeout: 15000 });
+ await expect(page.getByTestId('text-input')).toBeVisible({ timeout: 30000 });
+}
+
+const cornerOf = (locator: Locator) =>
+ locator.evaluate((node) => getComputedStyle(node).borderTopLeftRadius);
+
+/** Open the account menu, then its help submenu, and read both panels' corners. */
+async function accountPanelCorners(page: Page) {
+ await page.getByTestId('nav-user').click();
+ const accountMenu = page.getByRole('menu').filter({ has: page.getByTestId('nav-settings') });
+ await expect(accountMenu).toBeVisible();
+ const account = await cornerOf(accountMenu);
+
+ await page.getByRole('menuitem', { name: 'Help' }).click();
+ const helpMenu = page.getByRole('menu').filter({ hasNot: page.getByTestId('nav-settings') });
+ await expect(helpMenu).toBeVisible();
+ const help = await cornerOf(helpMenu);
+ return { account, help };
+}
+
+test.describe('account menu panel shape', () => {
+ test('the default theme keeps the account menu corners @scenario:default-theme-account-panels-unchanged', async ({
+ page,
+ }) => {
+ await openNewChat(page, 'default');
+ const menuCorner = await probeStyle(page, 'popover-ui', 'border-top-left-radius');
+ const corners = await accountPanelCorners(page);
+
+ expect(menuCorner).toBe('11.2px');
+ expect(corners).toEqual({ account: menuCorner, help: menuCorner });
+ });
+
+ test('ClickHouse brings the account menus onto its menu corner @scenario:clickhouse-account-panels-follow-menu-corner', async ({
+ page,
+ }) => {
+ await openNewChat(page, 'clickhouse');
+ const menuCorner = await probeStyle(page, 'popover-ui', 'border-top-left-radius');
+ const corners = await accountPanelCorners(page);
+
+ expect(menuCorner).toBe('4px');
+ expect(corners).toEqual({ account: menuCorner, help: menuCorner });
+ });
+
+ test('a theme naming only the menu radius role reshapes the account menus @scenario:reference-theme-reshapes-account-panels', async ({
+ page,
+ }) => {
+ await openNewChat(page, 'reference');
+ const corners = await accountPanelCorners(page);
+
+ expect(corners).toEqual({ account: REFERENCE_RADIUS, help: REFERENCE_RADIUS });
+ });
+});
diff --git a/packages/client/src/components/ControlCombobox.tsx b/packages/client/src/components/ControlCombobox.tsx
index 87adbc51784..5fe81c5c212 100644
--- a/packages/client/src/components/ControlCombobox.tsx
+++ b/packages/client/src/components/ControlCombobox.tsx
@@ -242,7 +242,7 @@ function ControlCombobox({
portal={portal}
portalElement={portalElement}
className={cn(
- 'border-border-light bg-surface-secondary overflow-hidden rounded-xl border shadow-lg',
+ 'border-border-light bg-surface-secondary rounded-theme-menu-panel overflow-hidden border shadow-lg',
popoverMaxHeight != null && 'flex flex-col',
popoverClassName ?? 'animate-popover',
)}
diff --git a/packages/client/src/components/MultiSelect.tsx b/packages/client/src/components/MultiSelect.tsx
index 9c41f642079..cd485ec608d 100644
--- a/packages/client/src/components/MultiSelect.tsx
+++ b/packages/client/src/components/MultiSelect.tsx
@@ -180,7 +180,7 @@ export default function MultiSelect({
finalFocus={selectRef}
className={cn(
'animate-popover z-40 flex max-h-[18.75rem]',
- 'flex-col overflow-auto overscroll-contain rounded-xl',
+ 'rounded-theme-menu-panel flex-col overflow-auto overscroll-contain',
'bg-surface-secondary text-text-primary px-1.5 py-1 shadow-lg',
'border-border-light border',
'outline-hidden',
diff --git a/packages/client/src/components/TimePicker.tsx b/packages/client/src/components/TimePicker.tsx
index d4377a2c7c3..f4bdac18fda 100644
--- a/packages/client/src/components/TimePicker.tsx
+++ b/packages/client/src/components/TimePicker.tsx
@@ -193,7 +193,7 @@ function PickerShell({
align="start"
sideOffset={6}
className={cn(
- 'border-border-light bg-surface-secondary z-[999] max-w-[min(calc(100vw-1rem),var(--radix-popover-content-available-width))] rounded-xl border p-2 shadow-lg outline-hidden',
+ 'border-border-light bg-surface-secondary rounded-theme-menu-panel z-[999] max-w-[min(calc(100vw-1rem),var(--radix-popover-content-available-width))] border p-2 shadow-lg outline-hidden',
// Same enter/exit motion as the shared Radix primitives (Combobox,
// Select, DropdownMenu): fade + zoom from the trigger edge, with Radix's
// own transform origin so the zoom grows out of wherever it was placed.
diff --git a/packages/client/src/theme/README.md b/packages/client/src/theme/README.md
index 0648e5537d0..717d4c6b1d2 100644
--- a/packages/client/src/theme/README.md
+++ b/packages/client/src/theme/README.md
@@ -65,8 +65,9 @@ Three primitives keep corners of their own outside that scale: the menu panel (`
`tabRadius` (0.185rem, through `rounded-theme-tab`). The defaults are the corners they always drew.
The composer's popovers read `popoverRadius` (1rem, `rounded-theme-popover`), the model selector's
-panel `menuPanelRadius` (0.75rem, `rounded-theme-menu-panel`) and the send and stop buttons
-`composerActionRadius` (a full circle, `rounded-theme-composer-action`), so a theme can bring them
+panel and the form-control pickers `menuPanelRadius` (0.75rem, `rounded-theme-menu-panel`), the
+date range picker `compactPanelRadius` (0.5rem, `rounded-theme-compact-panel`) and the send and
+stop buttons `composerActionRadius` (a full circle, `rounded-theme-composer-action`), so a theme can bring them
onto `menuRadius` or a square corner without moving the control or surface radii. An inline code
chip in Markdown takes its weight from `inlineCodeWeight` (600).
diff --git a/packages/client/src/theme/defaults.css b/packages/client/src/theme/defaults.css
index d056a6759c0..1d48c8e2390 100644
--- a/packages/client/src/theme/defaults.css
+++ b/packages/client/src/theme/defaults.css
@@ -260,6 +260,7 @@ html {
--theme-menu-radius: 0.7rem;
--theme-popover-radius: var(--theme-radius-2xl, 1rem);
--theme-menu-panel-radius: var(--theme-radius-xl, 0.75rem);
+ --theme-compact-panel-radius: var(--theme-radius-lg, 0.5rem);
--theme-composer-action-radius: var(--theme-round-control-radius, 9999px);
--theme-inline-code-weight: 600;
--theme-tooltip-radius: 0.275rem;
diff --git a/packages/client/src/theme/defaults.spec.ts b/packages/client/src/theme/defaults.spec.ts
index 52d60bad0b9..0c870365fbd 100644
--- a/packages/client/src/theme/defaults.spec.ts
+++ b/packages/client/src/theme/defaults.spec.ts
@@ -95,6 +95,7 @@ const stockAliases: Partial<
menuShadow: 'shadowLg',
popoverRadius: 'radius2xl',
menuPanelRadius: 'radiusXl',
+ compactPanelRadius: 'radiusLg',
composerActionRadius: 'roundControlRadius',
authControlRadius: 'radius2xl',
authActionHeight: 'buttonHeight',
diff --git a/packages/client/src/theme/registry.spec.ts b/packages/client/src/theme/registry.spec.ts
index 97b5b449640..f0dd6d11b79 100644
--- a/packages/client/src/theme/registry.spec.ts
+++ b/packages/client/src/theme/registry.spec.ts
@@ -289,7 +289,12 @@ describe('theme registry', () => {
name: 'corner-reference',
modes: {
light: {
- appearance: { radius2xl: '3px', radiusXl: '5px', roundControlRadius: '7px' },
+ appearance: {
+ radius2xl: '3px',
+ radiusXl: '5px',
+ radiusLg: '4px',
+ roundControlRadius: '7px',
+ },
},
},
},
@@ -298,6 +303,7 @@ describe('theme registry', () => {
expect(resolved.appearance.popoverRadius).toBe('3px');
expect(resolved.appearance.menuPanelRadius).toBe('5px');
+ expect(resolved.appearance.compactPanelRadius).toBe('4px');
expect(resolved.appearance.composerActionRadius).toBe('7px');
});
diff --git a/packages/client/src/theme/registry.ts b/packages/client/src/theme/registry.ts
index 199a966de8c..e2cc68e9c44 100644
--- a/packages/client/src/theme/registry.ts
+++ b/packages/client/src/theme/registry.ts
@@ -226,6 +226,7 @@ export const themeAppearanceProperties: Readonly<
menuRadius: '--theme-menu-radius',
popoverRadius: '--theme-popover-radius',
menuPanelRadius: '--theme-menu-panel-radius',
+ compactPanelRadius: '--theme-compact-panel-radius',
composerActionRadius: '--theme-composer-action-radius',
inlineCodeWeight: '--theme-inline-code-weight',
tooltipRadius: '--theme-tooltip-radius',
@@ -339,6 +340,7 @@ export const defaultAppearance: IThemeAppearance = Object.freeze({
menuRadius: '0.7rem',
popoverRadius: '1rem',
menuPanelRadius: '0.75rem',
+ compactPanelRadius: '0.5rem',
composerActionRadius: '9999px',
inlineCodeWeight: '600',
tooltipRadius: '0.275rem',
@@ -624,7 +626,7 @@ function withFloorableLengths(appearance: IThemeAppearance): IThemeAppearance {
/**
* Roles split out of a broader one, each paired with the role it read before. Headings drew the UI
* family before the display role existed, theme-sized controls were padded by the shared spacing,
- * and dialog titles were set in the `text-lg` step and the display family, the composer's popovers, the model selector and the send button drew `rounded-2xl`, `rounded-xl` and the round control corner, and the sign-in controls drew `rounded-2xl` and the other sign-in actions the default Button's height and corner, and the checkbox `rounded-sm`, so a theme that names
+ * and dialog titles were set in the `text-lg` step and the display family, the composer's popovers, the model selector, the date range picker and the send button drew `rounded-2xl`, `rounded-xl`, `rounded-lg` and the round control corner, and the sign-in controls drew `rounded-2xl` and the other sign-in actions the default Button's height and corner, and the checkbox `rounded-sm`, so a theme that names
* the broader role and not the split one keeps what it drew. Pairs resolve in order, so a role can
* follow one that is itself inherited; the other sign-in actions resolve before the sign-in corner
* is derived, so they follow only a corner the theme names and otherwise keep `radiusLg`.
@@ -638,6 +640,7 @@ const inheritedAppearance: ReadonlyArray<[keyof IThemeAppearance, keyof IThemeAp
['dialogTitleFontFamily', 'displayFontFamily'],
['popoverRadius', 'radius2xl'],
['menuPanelRadius', 'radiusXl'],
+ ['compactPanelRadius', 'radiusLg'],
['composerActionRadius', 'roundControlRadius'],
['authActionRadius', 'authControlRadius'],
['authActionRadius', 'radiusLg'],
diff --git a/packages/client/src/theme/tailwind.spec.js b/packages/client/src/theme/tailwind.spec.js
index 0d3f681ac1b..67cf362c900 100644
--- a/packages/client/src/theme/tailwind.spec.js
+++ b/packages/client/src/theme/tailwind.spec.js
@@ -162,6 +162,11 @@ describe('LibreChat Tailwind preset', () => {
['rounded-theme-popover', '--theme-popover-radius', defaultAppearance.popoverRadius],
['rounded-theme-checkbox', '--theme-checkbox-radius', defaultAppearance.checkboxRadius],
['rounded-theme-menu-panel', '--theme-menu-panel-radius', defaultAppearance.menuPanelRadius],
+ [
+ 'rounded-theme-compact-panel',
+ '--theme-compact-panel-radius',
+ defaultAppearance.compactPanelRadius,
+ ],
[
'rounded-theme-composer-action',
'--theme-composer-action-radius',
@@ -330,6 +335,7 @@ describe('LibreChat Tailwind preset', () => {
menuShadow: 'shadowLg',
popoverRadius: 'radius2xl',
menuPanelRadius: 'radiusXl',
+ compactPanelRadius: 'radiusLg',
composerActionRadius: 'roundControlRadius',
authControlRadius: 'radius2xl',
authActionHeight: 'buttonHeight',
diff --git a/packages/client/src/theme/themes/clickhouse.spec.tsx b/packages/client/src/theme/themes/clickhouse.spec.tsx
index cc43050a571..a2f63c83c57 100644
--- a/packages/client/src/theme/themes/clickhouse.spec.tsx
+++ b/packages/client/src/theme/themes/clickhouse.spec.tsx
@@ -594,6 +594,7 @@ describe('clickhouse theme definition', () => {
composerActionRadius: '0.25rem',
popoverRadius: appearance.menuRadius,
menuPanelRadius: appearance.menuRadius,
+ compactPanelRadius: appearance.menuRadius,
inlineCodeWeight: '500',
});
});
diff --git a/packages/client/src/theme/themes/clickhouse.ts b/packages/client/src/theme/themes/clickhouse.ts
index 2946af37480..12ee105bdc1 100644
--- a/packages/client/src/theme/themes/clickhouse.ts
+++ b/packages/client/src/theme/themes/clickhouse.ts
@@ -459,6 +459,7 @@ const clickHouseShape = {
menuRadius: '0.25rem', // genericMenu.panel.radii.all
popoverRadius: '0.25rem', // genericMenu.panel.radii.all, the panel the composer popovers are
menuPanelRadius: '0.25rem', // genericMenu.panel.radii.all
+ compactPanelRadius: '0.25rem', // genericMenu.panel.radii.all
composerActionRadius: '0.25rem', // button.radii.all, Click UI draws its icon buttons square
inlineCodeWeight: '500', // typography.font.weights.2
tooltipRadius: '0.25rem', // tooltip.radii.all
diff --git a/packages/client/src/theme/themes/clickui.spec.ts b/packages/client/src/theme/themes/clickui.spec.ts
index 8a5995b6eab..ed65179edf9 100644
--- a/packages/client/src/theme/themes/clickui.spec.ts
+++ b/packages/client/src/theme/themes/clickui.spec.ts
@@ -610,6 +610,7 @@ const appearanceSources: Partial> = {
menuRadius: 'click.genericMenu.panel.radii.all',
popoverRadius: 'click.genericMenu.panel.radii.all',
menuPanelRadius: 'click.genericMenu.panel.radii.all',
+ compactPanelRadius: 'click.genericMenu.panel.radii.all',
composerActionRadius: 'click.button.radii.all',
authControlRadius: 'click.button.radii.all',
inlineCodeWeight: 'typography.font.weights.2',
diff --git a/packages/client/src/theme/types/index.ts b/packages/client/src/theme/types/index.ts
index af1180cfa83..dfa5b0cb0c0 100644
--- a/packages/client/src/theme/types/index.ts
+++ b/packages/client/src/theme/types/index.ts
@@ -655,6 +655,8 @@ export interface IThemeAppearance {
* control or surface radii. */
popoverRadius: string;
menuPanelRadius: string;
+ /** The corners of the date range picker panel, which drew `rounded-lg`. */
+ compactPanelRadius: string;
composerActionRadius: string;
/** The weight of an inline code chip in rendered Markdown. */
inlineCodeWeight: string;
diff --git a/packages/client/tailwind.preset.cjs b/packages/client/tailwind.preset.cjs
index 6546f032447..246857fc344 100644
--- a/packages/client/tailwind.preset.cjs
+++ b/packages/client/tailwind.preset.cjs
@@ -157,6 +157,7 @@ module.exports = {
'theme-surface': 'var(--theme-surface-radius, 1rem)',
'theme-popover': 'var(--theme-popover-radius, 1rem)',
'theme-menu-panel': 'var(--theme-menu-panel-radius, 0.75rem)',
+ 'theme-compact-panel': 'var(--theme-compact-panel-radius, 0.5rem)',
'theme-composer-action': 'var(--theme-composer-action-radius, 9999px)',
'theme-auth-control': 'var(--theme-auth-control-radius, 1rem)',
'theme-auth-action': 'var(--theme-auth-action-radius, 0.5rem)',
diff --git a/packages/data-provider/src/theme.ts b/packages/data-provider/src/theme.ts
index 1146f0823c2..bdd8f4747e3 100644
--- a/packages/data-provider/src/theme.ts
+++ b/packages/data-provider/src/theme.ts
@@ -427,6 +427,7 @@ const appearanceValidators = {
menuRadius: isLength,
popoverRadius: isLength,
menuPanelRadius: isLength,
+ compactPanelRadius: isLength,
composerActionRadius: isLength,
inlineCodeWeight: isFontWeight,
tooltipRadius: isLength,
From e9c7c09626d2a7f1f3e9d0c2495529d410feb6a3 Mon Sep 17 00:00:00 2001
From: Marco Beretta <81851188+berry-13@users.noreply.github.com>
Date: Fri, 16 Oct 2026 21:38:10 +0200
Subject: [PATCH 2/2] test: Refresh the Theme Cache Pin for the Compact Panel
Role
---
client/src/Providers/__tests__/themeCache.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/Providers/__tests__/themeCache.spec.ts b/client/src/Providers/__tests__/themeCache.spec.ts
index 93e2df5c8b6..962b59ee9e3 100644
--- a/client/src/Providers/__tests__/themeCache.spec.ts
+++ b/client/src/Providers/__tests__/themeCache.spec.ts
@@ -224,7 +224,7 @@ describe('theme cache storage', () => {
* - definitions with one or both mode blocks absent.
* A new role, or a new fallback, joins by construction.
*/
-const PIN = { fingerprint: '1.5.dxwdxg', digest: '1n3ywnj' };
+const PIN = { fingerprint: '1.5.1ln9dy9', digest: '1ohs8yp' };
const digestOf = (text: string): string => {
let hash = 5381;