Skip to main content
LibreChat is joining ClickHouse to power the open-source Agentic Data Stack 🎉 Learn more
LibreChat

Theme

Apply a bundled or custom theme to every user with interface.theme in librechat.yaml

Overview

interface.theme sets the deployment theme: the colors, shape, typography, shadows and motion that every user sees, in both light and dark mode. It takes either the name of a theme bundled with LibreChat or an inline theme definition.

Pending: not yet released

interface.theme and the appearance scales on this page are merged to LibreChat's dev branch and are not in a tagged release yet; v0.8.8 and earlier do not read them.

interface:
  theme: clickhouse

When interface.theme is unset, LibreChat behaves as before: users see the default LibreChat theme, or whatever build-time colors or stored theme apply to them.

This page covers what an operator sets in librechat.yaml. The theme engine itself, including the full token list and how each token maps to Tailwind utilities, is documented in the theme README in the LibreChat repository.

How the theme is chosen

The client picks one theme, highest priority first:

  1. High-contrast modes. A user who picks high-contrast-light or high-contrast-dark, or whose system setting resolves to high contrast, always gets the built-in accessible palette.
  2. interface.theme from librechat.yaml.
  3. REACT_APP_THEME_* build-time colors. See Theme Colors.
  4. The user's stored theme in their browser.

The deployment theme sets colors, shape, fonts, shadows and motion, but not the mode: each user still chooses light, dark or system themselves.

The deployment theme is never written to the user's browser storage. If you remove interface.theme, users get their own stored theme back (or the REACT_APP_THEME_* colors, when the build sets them).

The theme is part of the pre-login configuration, so the login and registration pages already render with it.

A shared link paints the theme of the tenant that owns the link, not the theme of the person viewing it. If the owning tenant sets no interface.theme, or its shared-link configuration fails to load, the shared page shows no deployment theme rather than falling back to the viewer's. Leaving the shared page restores the viewer's theme.

Bundled themes

NameTheme
librechatThe default LibreChat palette and shape. Setting it explicitly also overrides REACT_APP_THEME_* colors and users' stored themes.
clickhouseA reference theme built from ClickHouse's Click UI design tokens. See ClickHouse theme.

A name that is not one of these is ignored: the server logs it and drops interface.theme (see Validation), and the app falls back to the next source in the list above.

ClickHouse theme

interface.theme: clickhouse changes both color and shape:

  • Palette. Every color token is defined in both modes from Click UI's light and dark tokens, so nothing falls back to the LibreChat palette. Values are Click UI's own, even where a pairing falls below WCAG AA contrast. Users who need more contrast can pick a high-contrast mode, which always uses the built-in accessible palette (see How the theme is chosen).
  • Accent. Near-black #151515 in light mode and the ClickHouse yellow #faff69 in dark mode, used for the accent and submit button. The focus ring is Click UI's outline color: blue #437eef in light mode, #faff69 in dark.
  • Buttons. The primary button is #302e32 in light mode and #faff69 in dark, with Click UI's hover steps.
  • Radii. A tighter scale taken from Click UI's border.radii: controls, menus, popovers, tooltips, tabs, the composer's action buttons and sm through lg at 0.25rem, surfaces and xl/2xl at 0.5rem, large surfaces and 3xl at 0.75rem.
  • Borders. Decorative outlines on controls and chips and the hairlines inside stroked surfaces are hidden (chromeBorderAlpha and insetBorderAlpha at 0), so surfaces are separated by fill; field edges and the neutral button's border stay. Destructive actions use the soft style.
  • Shadows. Click UI's single elevation shadow on every raised surface, menus and dragged items, at 0.15 opacity in light mode and 0.6 in dark, with a hairline shadow for the 2xs through sm steps. Tooltips have no shadow.
  • Controls. Buttons and fields are 2rem tall, including the sign-in field and submit button (this theme sets authFieldHeight and authButtonHeight to 2rem, where the defaults are 2.75rem and 3rem), controls are padded 1rem with a 0.5rem icon gap and a regular (400) label weight. Fields are filled (fieldFillStyle: fill) and swap their edge color on focus (fieldFocusStyle: border); labels are 0.75rem at weight 500. Disabled controls use the fill style with their own disabled colors, the switch is a compact 2rem by 1rem, and table rows get a 1px rule.
  • Dialogs. A 1px edge, 2rem side padding, and titles at 1.25rem, weight 700, in the Inter stack. Every dialog scrim is at 0.75 opacity.
  • Fonts. The UI font stays Inter. Code uses Click UI's mono stack, "Inconsolata", Consolas, "SFMono Regular", monospace; LibreChat bundles Inconsolata. Headings ask for Basier Square first, which is not bundled, so they render in Inter unless the viewer has it installed.

Inline theme definition

Instead of a name, interface.theme can hold a theme definition. Anything you leave out falls back to LibreChat's defaults for that mode, so a definition only needs the values you want to change.

interface:
  theme:
    version: 1
    name: acme
    modes:
      light:
        colors:
          rgb-accent-primary: '29 78 216'
          rgb-accent-primary-hover: '30 64 175'
          rgb-ring-primary: '29 78 216'
          rgb-surface-submit: '29 78 216'
          rgb-surface-submit-hover: '30 64 175'
          rgb-link: '29 78 216'
        appearance:
          controlRadius: '0.375rem'
          radiusLg: '0.375rem'
          radiusXl: '0.5rem'
          shadowLg: '0 8px 16px -4px rgb(0 0 0 / 0.2)'
      dark:
        colors:
          rgb-accent-primary: '96 165 250'
          rgb-accent-primary-hover: '147 197 253'
          rgb-ring-primary: '96 165 250'
          rgb-surface-submit: '37 99 235'
          rgb-surface-submit-hover: '29 78 216'
          rgb-link: '96 165 250'
        appearance:
          controlRadius: '0.375rem'
          radiusLg: '0.375rem'
          radiusXl: '0.5rem'
          shadowLg: '0 8px 16px -4px rgb(0 0 0 / 0.5)'
    brands:
      provider-openai: '#10a37f'
KeyTypeDescription
versionNumberRequired. Must be 1.
nameStringRequired. A non-empty name for the theme.
modesObjectRequired. Holds light and/or dark. A mode you leave out uses LibreChat's defaults for that mode.
modes.<mode>.colorsObjectColor tokens for that mode. Keys are rgb-* token names, values are R G B triplets. See Colors.
modes.<mode>.appearanceObjectShape, control, typography, scrim, shadow and motion values for that mode. See Appearance.
modes.<mode>.brandsObjectProvider brand colors for that mode. Overrides the theme-wide brands.
brandsObjectProvider brand colors for both modes. See Brands.

Validation

The server checks interface.theme with the same rules the browser applies before painting it, when librechat.yaml is loaded and on every config reload.

An invalid theme is dropped as if interface.theme were unset. A wrong version, a missing name, a mode other than light or dark, a field the definition format does not have (at the top level, such as css, or inside a mode, anything other than colors, appearance and brands), an unknown brand token, or a value of the wrong kind for any token makes the theme unusable. LibreChat removes interface.theme, logs a warning with one line per problem, and loads the rest of the configuration as usual, so a theme mistake never stops the server. Users then get the next source in How the theme is chosen: the REACT_APP_THEME_* colors or their stored theme, or LibreChat's default theme when neither applies. The log calls this the default theme:

Ignoring interface.theme in /app/librechat.yaml; the default theme applies instead:
- interface.theme.modes.dark.colors.rgb-surface-primary: Invalid RGB value for rgb-surface-primary: 300 16 32

A bundled theme name that does not exist is handled the same way (Unknown bundled theme "<name>", expected one of: librechat, clickhouse).

A theme set through a config override (for a role, group or user in the Admin Panel) follows the same rules, with one difference: an invalid override theme falls back to the base interface.theme from librechat.yaml, not to the default theme. Those log lines start with [getAppConfig], for example [getAppConfig] Ignoring interface.theme from a config override; the base theme applies instead:.

The colors and appearance maps work differently from the fields around them.

An unknown token is ignored, and the rest of the theme applies. A key inside colors or appearance that this version of LibreChat does not know, whether a typo or a token added in a newer version, costs only itself. The server logs it and leaves unknown colors out of the theme it sends to the browser:

interface.theme in /app/librechat.yaml names tokens this version ignores:
- interface.theme.modes.light.colors.rgb-surfce-secondary: Unknown light color token ignored: rgb-surfce-secondary

An unknown token is still checked for shape: a color name must be a plain lowercase token (letters, digits and -) holding an R G B value, and an appearance name must be camelCase holding a plain value without ;, {, }, <, > or url(). Anything else is an error and the whole theme falls back. Check the server log after changing a theme, because a misspelled token is only reported there.

The browser applies the same rules to the theme it receives and logs [DeploymentTheme] Ignoring invalid interface.theme: ... if it still has to reject one, for example when an older cached client meets a newer server.

Colors

Colors use the same token names as the theme engine: rgb- followed by the token, such as rgb-surface-primary, rgb-text-primary, rgb-border-medium, rgb-accent-primary or rgb-status-error-subtle. The full list of 169 tokens is themeColorTokens in packages/data-provider/src/theme.ts, which the server and the browser both read, and each token is described in the IThemeRGB interface in packages/client/src/theme/types/index.ts.

Beyond the surface, text, border, status and syntax palettes, these roles let a theme restyle specific interaction states and components:

RoleTokensPaints
Focusrgb-focus-outline, rgb-focus-controlThe keyboard focus outline across the app, and the focus ring of shared controls
Pressedrgb-surface-pressed, rgb-surface-inverted-pressedNeutral and inverted controls while pressed
Inverted and fixedrgb-surface-inverted, rgb-surface-inverted-hover, rgb-text-inverted, rgb-surface-fixed, rgb-surface-fixed-hover, rgb-text-fixedControls drawn in the opposite mode's colors, and controls that keep one color in both modes
Disabledrgb-surface-disabled, rgb-text-disabled, rgb-border-disabledDisabled controls, when disabledStyle is fill (see Appearance)
Control borderrgb-border-controlThe edge of inputs, select triggers and one-time code slots, kept separate from quiet separators because it needs 3:1 contrast
Fieldsrgb-border-field-focus, rgb-field-fill, rgb-field-textA focused field's edge when fieldFocusStyle is border (follows rgb-focus-control), a field's fill when fieldFillStyle is fill (follows rgb-surface-primary), and the typed value (follows rgb-text-primary)
Checkboxrgb-checkbox-fillAn unchecked checkbox's fill when checkboxFillStyle is fill (follows rgb-surface-primary)
Primary buttonrgb-button-primary, rgb-button-primary-hoverThe primary Button's fill and hover; follow rgb-surface-inverted and rgb-surface-inverted-hover, which checkboxes and switches keep using
Inksrgb-dialog-title, rgb-badge-labelDialog titles and badge labels; both follow rgb-text-primary
Scrimrgb-surface-overlayThe color behind dialogs; its strength is set by the scrim opacities in Appearance
Media overlayrgb-surface-media-overlay, rgb-text-on-mediaScrims, chips and progress drawn over the user's own images (lightbox, image preview, uploads in progress), and the text on them. Bundled themes keep them black and white in both modes
Avatarrgb-avatar-fill, rgb-avatar-text, rgb-avatar-placeholder, rgb-avatar-edgeThe default user avatar's fill and glyph (the glyph follows rgb-text-primary), the backdrop behind a loading or transparent agent or assistant avatar (follows rgb-surface-secondary in light mode and rgb-surface-tertiary in dark), and the hairline around the default avatar
File tilesrgb-file-document, rgb-file-sheet, rgb-file-code, rgb-file-artifact, rgb-file-audio, rgb-file-video, rgb-file-generic, rgb-file-inkOne fill per kind of file, and the glyph drawn on every tile
Illustrationrgb-illustration-subtle, rgb-illustration, rgb-illustration-strongThe three tones of in-app artwork, such as the file drop zone's illustration
Switchrgb-switch-unchecked, rgb-switch-thumbThe unchecked switch track, and the knob in both states
Tablergb-table-header-text, rgb-table-header-fillColumn names, and the opaque fill of a sticky table header
Chart seriesrgb-series-1 through rgb-series-8Categorical chart colors, in order

Each value is a bare R G B triplet with every channel from 0 to 255, for example '255 255 255'. Hex values and rgb(...) are rejected. Quote the value so YAML reads it as a string.

A few tokens follow a related token you did set when you leave them out, so a partial palette stays coherent. For example, rgb-text-muted follows rgb-text-tertiary, rgb-surface-composer-hover and rgb-surface-pressed follow rgb-surface-hover, rgb-focus-outline follows rgb-ring-primary, rgb-focus-control follows rgb-text-primary, rgb-border-control follows rgb-border-light, rgb-switch-thumb follows rgb-surface-primary, rgb-table-header-text follows rgb-text-secondary, and rgb-table-header-fill follows rgb-surface-dialog.

Appearance

Appearance values are set per mode, and a mode without them uses the defaults below. The defaults are the same in both modes except menuShadow and tooltipShadow, which are heavier in dark mode, and buttonNeutralBorderOpacity, which is opaque in dark mode. To change shape in both modes, repeat the values under light and dark, as in the example above.

KeyControlsDefault
controlRadiusCorner radius of controls such as buttons and inputs0.75rem
roundControlRadiusRadius of fully rounded controls9999px
surfaceRadiusRadius of surfaces such as cards and menus1rem
largeSurfaceRadiusRadius of large surfaces such as dialogs1.5rem
menuRadiusCorner radius of menu panels0.7rem
menuPanelRadiusCorner radius of the model selector menu, hover cards and the composer's menus; follows radiusXl when unset0.75rem
popoverRadiusCorner radius of the composer's popovers, such as the prompts, skills and mention lists; follows radius2xl when unset1rem
composerActionRadiusCorner radius of the composer's send and stop buttons and its other round action buttons; follows roundControlRadius when unset9999px
compactPanelRadiusCorner radius of the date range picker panel; follows radiusLg when unset0.5rem
tooltipRadiusCorner radius of tooltips0.275rem
tooltipPaddingXInline padding of tooltips0.5rem
tooltipPaddingYVertical padding of tooltips0.25rem
tooltipTextSizeFont size of tooltip text1rem
tabRadiusCorner radius of tab triggers0.185rem
tabMinWidthMinimum width of a tab trigger100px
listMinWidthMinimum width of a field Dropdown list; 0 sizes it by its trigger8rem
listMaxHeightHeight after which a field Dropdown list scrolls, bounded by available space24rem
radiusSmThe rounded-sm step used across the appcalc(0.5rem - 4px)
radiusMdThe rounded-md stepcalc(0.5rem - 2px)
radiusLgThe rounded-lg step0.5rem
radiusXlThe rounded-xl step0.75rem
radius2xlThe rounded-2xl step1rem
radius3xlThe rounded-3xl step1.5rem
controlHeightHeight of standard controls2.25rem
controlPaddingXInline padding of theme-sized controls; follows spaceNormal when unset0.75rem
buttonPaddingXInline padding of the default Button size1rem
controlGapGap between a control's icon and label; follows spaceCompact when unset0.375rem
iconSizeSize of standard icons (size-theme-icon)1rem
iconSizeMdSize of medium icons (size-theme-icon-md)1.25rem
iconSizeLgSize of large icons, such as a dialog's close button (size-theme-icon-lg)1.5rem
controlFontWeightLabel weight of theme-sized controls500
buttonHeightHeight of the default Button2.5rem
buttonHeightSmHeight of the sm Button2.25rem
buttonHeightXsHeight of the xs Button1.75rem
buttonHeightLgHeight of the lg Button2.75rem
buttonHeightCompactHeight of compact Buttons2rem
iconButtonSizeSmWidth and height of the icon-sm Button2rem
fieldHeightHeight of form fields2.5rem
fieldHeightLgHeight of large form fields, such as title inputs3rem
authFieldHeightHeight of the floating-label fields on the sign-in pages2.75rem
authButtonHeightHeight of the sign-in pages' submit buttons3rem
authControlRadiusCorner radius of the sign-in fields and submit buttons; follows radius2xl when unset1rem
authActionHeightHeight of the other sign-in actions, the two-factor verify and password-reset continue buttons; when unset, follows authButtonHeight if the theme sets it and buttonHeight otherwise, so the default matches buttonHeight2.5rem
authActionRadiusCorner radius of those other sign-in actions; when unset, follows authControlRadius if the theme sets it and radiusLg otherwise, so the default matches radiusLg0.5rem
fieldPaddingYVertical padding of form fields0.5rem
fieldFocusStyleHow a focused field shows focus: ring draws the focus ring, border swaps the field's edge to rgb-border-field-focusring
fieldFillStyleWhether fields stay transparent or paint rgb-field-fill (fill)transparent
labelSizeFont size of field labels; follows textSm when unset0.875rem
labelLeadingLine height of field labels1
labelFontWeightWeight of field labels; inherit keeps the weight of the surrounding textinherit
focusRingWidthWidth of the keyboard focus outline2px
focusRingOffsetDistance of the focus outline from the element's edge2px
switchWidthWidth of the switch2.75rem
switchHeightHeight of the switch; the knob is this minus the track's 4px border1.5rem
checkboxSizeSize of the checkbox and its check mark1rem
checkboxRadiusCorner radius of the checkbox; follows radiusSm when unsetcalc(0.5rem - 4px)
checkboxFillStyleWhether an unchecked checkbox stays transparent or paints rgb-checkbox-fill (fill), which follows rgb-surface-primary; a checked box keeps rgb-surface-invertedtransparent
tableCellSpaceYVertical padding of table cells1rem
tableRowStrokeThickness of the rule between table rows0px
spaceCompactCompact spacing step0.375rem
spaceNormalNormal spacing step0.75rem
disabledStyleHow disabled controls look: dim fades them to 50% opacity, fill paints them with the disabled color rolesdim
fontFamilyUI font family (font-sans)Inter, sans-serif
monoFontFamilyCode font family (font-mono)'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, 'Cascadia Mono', 'Liberation Mono', Consolas, monospace
displayFontFamilyHeadings and dialog titles (font-display); follows fontFamily when unsetInter, sans-serif
textXsThe text-xs size0.75rem
textSmThe text-sm size0.875rem
textBaseThe text-base size1rem
textLgThe text-lg size1.125rem
textXlThe text-xl size1.25rem
text2xlThe text-2xl size1.5rem
text3xlThe text-3xl size1.875rem
text3xsThe text-3xs size (10px by default)0.625rem
text2xsThe text-2xs size (11px by default)0.6875rem
text1xsThe text-1xs size (13px by default)0.8125rem
text1smThe text-1sm size (15px by default)0.9375rem
leadingXsLine height paired with text-xscalc(1 / 0.75)
leadingSmLine height paired with text-smcalc(1.25 / 0.875)
leadingBaseLine height paired with text-basecalc(1.5 / 1)
leadingLgLine height paired with text-lgcalc(1.75 / 1.125)
leadingXlLine height paired with text-xlcalc(1.75 / 1.25)
leading2xlLine height paired with text-2xlcalc(2 / 1.5)
leading3xlLine height paired with text-3xlcalc(2.25 / 1.875)
inlineCodeWeightWeight of inline code in messages600
dialogStrokeWidth of the dialog's edge stroke0px
dialogPaddingXInline padding of dialogs1.5rem
dialogHeaderGapGap between a dialog's title and description0.375rem
dialogTitleSizeFont size of dialog titles; follows textLg when unset1.125rem
dialogTitleLeadingLine height of dialog titles1
dialogTitleFontWeightWeight of dialog titles600
dialogTitleFontFamilyFont family of dialog titles; follows displayFontFamily when unsetInter, sans-serif
scrimOpacityStrength of the scrim behind standard dialogs0.8
alertScrimOpacityStrength of the scrim behind confirmation dialogs0.9
modalScrimOpacityOpacity for custom modal scrims using bg-scrim-modal; no bundled dialog currently uses it0.65
buttonNeutralBorderOpacityOpacity of the neutral button's borderLight: 0.1; dark: 1
elevationSurfaceShadow of raised theme surfaces0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)
elevationDragShadow of a badge while it is dragged0 10px 25px rgb(0 0 0 / 0.1)
shadow2xsThe shadow-2xs step0 1px rgb(0 0 0 / 0.05)
shadowXsThe shadow-xs step0 1px 2px 0 rgb(0 0 0 / 0.05)
shadowSmThe shadow-sm step and bare shadow0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)
shadowMdThe shadow-md step0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)
shadowLgThe shadow-lg step0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)
shadowXlThe shadow-xl step0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)
shadow2xlThe shadow-2xl step0 25px 50px -12px rgb(0 0 0 / 0.25)
menuShadowShadow of menu panels; in light mode, follows shadowLg when unsetLight: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); dark: 0 10px 15px -3px rgb(0 0 0 / 0.25), 0 4px 6px -4px rgb(0 0 0 / 0.1)
tooltipShadowShadow of tooltipsLight: 0 2px 4px 0 rgb(0 0 0 / 0.25); dark: 0 1px 2px 0 rgb(0 0 0 / 0.35)
motionFastDuration of fast transitions150ms
motionNormalDuration of normal transitions200ms
chromeBorderAlphaHow much of the border color the outlines of controls and chips keep; 0 hides them without moving anything1
insetBorderAlphaHow much of the border color the hairlines inside a stroked surface keep; 0 hides them1
destructiveStyleHow destructive actions are painted: fill uses the solid destructive surface, soft a tint of itfill

The defaults reproduce LibreChat's look, so a theme that sets none of these keys changes no shape.

Accepted values:

  • Radii, controlHeight, controlPaddingX, buttonPaddingX, controlGap, buttonHeight, buttonHeightSm, fieldHeight, fieldPaddingY, spacing, tooltip padding and text size, text and label sizes, and the dialog lengths (dialogStroke, dialogPaddingX, dialogHeaderGap, dialogTitleSize): 0, or a number in px, rem or em (such as 0.25rem), or a single calc() of two such lengths (such as calc(0.5rem - 2px)).
  • buttonHeightXs, buttonHeightLg, buttonHeightCompact, iconButtonSizeSm, fieldHeightLg, authFieldHeight, authButtonHeight and authActionHeight: a length in px or rem of at least 24px (1.5rem), the minimum pointer target size.
  • iconSize: 12px to 20px (0.75rem to 1.25rem). iconSizeMd: 20px to 24px. iconSizeLg: 16px to 32px. checkboxSize: 16px to 24px. listMaxHeight: 128px to 640px (8rem to 40rem). Each is a length in px or rem.
  • tabMinWidth and listMinWidth: 0, or a length in px or rem.
  • switchWidth and switchHeight: a positive length in px or rem. Both must use the same unit (a side you leave out uses its rem default), the width must exceed the height so the knob can travel, and the height must clear the 4px track border (more than 4px, or at least 0.5rem).
  • tableCellSpaceY and tableRowStroke: 0, or a length in px or rem.
  • focusRingWidth: a positive length in px or rem, so the focus indicator never disappears.
  • focusRingOffset: 0, or a length in px or rem that may be negative (drawing the outline inside the element's edge).
  • disabledStyle: dim or fill.
  • fieldFocusStyle: ring or border.
  • fieldFillStyle: transparent or fill.
  • checkboxFillStyle: transparent or fill.
  • Font weights (controlFontWeight, dialogTitleFontWeight, inlineCodeWeight): a whole number from 1 to 1000. labelFontWeight also accepts inherit.
  • Line heights (the leading* steps, labelLeading and dialogTitleLeading): a unitless number (such as 1.5), a single calc() dividing two numbers (such as calc(1.25 / 0.875)), or a length.
  • Scrim opacities, buttonNeutralBorderOpacity, chromeBorderAlpha and insetBorderAlpha: a number from 0 to 1.
  • destructiveStyle: fill or soft.
  • Font families: any non-empty font-family list without ;, { or }. The font must be available to the browser: LibreChat bundles Inter, Roboto Mono and Inconsolata, so any other family has to be installed on the viewer's machine or served by your deployment, or the next family in the list is used.
  • Shadow steps (shadow2xs through shadow2xl), menuShadow, tooltipShadow and elevationDrag: a concrete box-shadow list, or none. var(), env(), attr() and url() are rejected.
  • elevationSurface: any non-empty box-shadow value without ;, {, } or url().
  • Motion: a duration in ms or s, such as 120ms.

Every appearance value must be a string. Quote numbers such as font weights, line heights and opacities ('500', '1.5', '0.8'); an unquoted YAML number is rejected.

Brands

brands recolors the provider icons shown next to models. It can be set once at the top level for both modes, and overridden per mode under modes.<mode>.brands.

KeyDefault
provider-openai#19C37D
provider-openai-gpt4#AB68FF
provider-openai-reasoning#000000
provider-anthropic#d09a74
provider-azurelinear-gradient(0.375turn, #61bde2, #4389d0)
provider-bedrock#268672
provider-foreground#ffffff

Values are hex colors (#rgb, #rrggbb or #rrggbbaa). The fills may also be a linear-gradient(...); provider-foreground, the icon glyph color, must be a hex color.

Using the theme outside LibreChat

The @librechat/client package publishes the theme as a stylesheet, so another app built on its components can paint the same tokens. Import @librechat/client/theme.css in the stylesheet that imports Tailwind:

@import 'tailwindcss';
@import '@librechat/client/theme.css';

It carries the semantic color tokens and their Tailwind mappings, the default value of every color and appearance token, and the @font-face rules for Inter, Roboto Mono and Inconsolata. The font files ship in the package as @librechat/client/fonts/*, so the app's bundler (Vite, webpack or esbuild) has to resolve and emit them; a setup that serves compiled CSS without a bundler has to serve those paths itself. A font is only downloaded when text renders in it.

How is this guide?