LibreChat/custom/theme.example.css
Linus Schlumberger 110098a555 feat: custom theme CSS support via mounted file
Implements operator-level CSS theming without rebuilding the image.
Closes danny-avila/LibreChat#5389.
2026-03-11 17:49:59 +01:00

267 lines
7.5 KiB
CSS

/*
* LibreChat Custom Theme
*
* This file documents all theme variables with their default values.
* To use it, copy this file to `theme.css` in the same directory and
* change whatever you want.
*
* How it works:
* - The server checks for `custom/theme.css` on startup
* - If found, a <link> tag is injected at the end of <head>, after all built-in styles
* - This gives your overrides the highest precedence in the cascade by load order
* - The file is served with no-cache headers, so CSS edits take effect on page reload
* (no container or server restart needed)
*
* Docker setup:
* Mount this directory via docker-compose.override.yml:
*
* services:
* api:
* volumes:
* - ./custom:/app/custom
*
* Or mount a single file:
*
* services:
* api:
* volumes:
* - ./my-theme.css:/app/custom/theme.css
*
* Tips:
* - Use your browser's DevTools to inspect elements and find which variables
* or classes to override.
* - The `html` selector targets light mode; `.dark` targets dark mode.
* - To load custom fonts, add @font-face rules below and place the font
* files in this directory — they'll be served under /custom/.
*/
/* ==========================================================================
* Base color palette
*
* Redefine these to shift the entire palette at once, or override the
* individual semantic variables below for more surgical control.
* ========================================================================== */
:root {
--white: #fff;
--black: #000;
--gray-20: #ececf1;
--gray-50: #f7f7f8;
--gray-100: #ececec;
--gray-200: #e3e3e3;
--gray-300: #cdcdcd;
--gray-400: #999696;
--gray-500: #595959;
--gray-600: #424242;
--gray-700: #2f2f2f;
--gray-800: #212121;
--gray-850: #171717;
--gray-900: #0d0d0d;
--green-50: #ecfdf5;
--green-100: #d1fae5;
--green-200: #a7f3d0;
--green-300: #6ee7b7;
--green-400: #34d399;
--green-500: #10b981;
--green-600: #059669;
--green-700: #047857;
--green-800: #065f46;
--green-900: #064e3b;
--green-950: #022c22;
--red-50: #fef2f2;
--red-100: #fee2e2;
--red-200: #fecaca;
--red-300: #fca5a5;
--red-400: #f87171;
--red-500: #ef4444;
--red-600: #dc2626;
--red-700: #b91c1c;
--red-800: #991b1b;
--red-900: #7f1d1d;
--red-950: #450a0a;
--amber-50: #fffbeb;
--amber-100: #fef3c7;
--amber-200: #fde68a;
--amber-300: #fcd34d;
--amber-400: #fbbf24;
--amber-500: #f59e0b;
--amber-600: #d97706;
--amber-700: #b45309;
--amber-800: #92400e;
--amber-900: #78350f;
--amber-950: #451a03;
/* Typography */
--font-size-xs: 0.75rem;
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--font-size-xl: 1.25rem;
--markdown-font-size: 1rem;
}
/* ==========================================================================
* Light mode semantic variables (selector: html)
* ========================================================================== */
html {
--brand-purple: #ab68ff;
/* Text */
--presentation: var(--white);
--text-primary: var(--gray-800);
--text-secondary: var(--gray-600);
--text-secondary-alt: var(--gray-500);
--text-tertiary: var(--gray-500);
--text-warning: var(--amber-500);
--text-destructive: var(--red-600);
/* Focus ring */
--ring-primary: var(--gray-500);
/* Header */
--header-primary: var(--white);
--header-hover: var(--gray-50);
--header-button-hover: var(--gray-50);
/* Surfaces */
--surface-active: var(--gray-100);
--surface-active-alt: var(--gray-200);
--surface-hover: var(--gray-200);
--surface-hover-alt: var(--gray-300);
--surface-primary: var(--white);
--surface-primary-alt: var(--gray-50);
--surface-primary-contrast: var(--gray-100);
--surface-secondary: var(--gray-50);
--surface-secondary-alt: var(--gray-200);
--surface-tertiary: var(--gray-100);
--surface-tertiary-alt: var(--white);
--surface-dialog: var(--white);
--surface-chat: var(--white);
--surface-submit: var(--green-700);
--surface-submit-hover: var(--green-800);
--surface-destructive: var(--red-700);
--surface-destructive-hover: var(--red-800);
/* Borders */
--border-light: var(--gray-200);
--border-medium: var(--gray-300);
--border-medium-alt: var(--gray-300);
--border-heavy: var(--gray-400);
--border-xheavy: var(--gray-500);
--border-destructive: var(--red-600);
/* HSL-based variables used by shadcn/ui components */
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--radius: 0.5rem;
--switch-unchecked: 0 0% 58%;
}
/* ==========================================================================
* Dark mode semantic variables (selector: .dark)
* ========================================================================== */
.dark {
--brand-purple: #ab68ff;
/* Text */
--presentation: var(--gray-800);
--text-primary: var(--gray-100);
--text-secondary: var(--gray-300);
--text-secondary-alt: var(--gray-400);
--text-tertiary: var(--gray-500);
--text-warning: var(--amber-500);
--text-destructive: var(--red-600);
/* Header */
--header-primary: var(--gray-700);
--header-hover: var(--gray-600);
--header-button-hover: var(--gray-700);
/* Surfaces */
--surface-active: var(--gray-500);
--surface-active-alt: var(--gray-700);
--surface-hover: var(--gray-600);
--surface-hover-alt: var(--gray-600);
--surface-primary: var(--gray-900);
--surface-primary-alt: var(--gray-850);
--surface-primary-contrast: var(--gray-850);
--surface-secondary: var(--gray-800);
--surface-secondary-alt: var(--gray-800);
--surface-tertiary: var(--gray-700);
--surface-tertiary-alt: var(--gray-700);
--surface-dialog: var(--gray-850);
--surface-chat: var(--gray-700);
--surface-submit: var(--green-700);
--surface-submit-hover: var(--green-800);
--surface-destructive: var(--red-800);
--surface-destructive-hover: var(--red-900);
/* Borders */
--border-light: var(--gray-700);
--border-medium: var(--gray-600);
--border-medium-alt: var(--gray-600);
--border-heavy: var(--gray-500);
--border-xheavy: var(--gray-400);
--border-destructive: var(--red-500);
/* HSL-based variables used by shadcn/ui components */
--background: 0 0% 7%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 40.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--switch-unchecked: 0 0% 40%;
}
/* ==========================================================================
* Custom font example
*
* Place font files in this directory; they are served under /custom/.
* ========================================================================== */
/*
@font-face {
font-family: 'MyFont';
src: url('/custom/MyFont.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
html {
font-family: 'MyFont', sans-serif;
}
*/