Files
Abhinav Raut c865748ca5 switch to Geist and cache help center pages
Geist replaces Instrument Sans and is served from static/ instead of Google
Fonts, so it works on installs with no internet access.

Public help center pages are cached in Redis via fastcache with ETags, and any
admin write clears the group so edits show up on the next load.
2026-08-20 18:01:42 +05:30

9.3 KiB

Libredesk Design System

Reference for colors, typography, spacing, radius, elevation, and component conventions across both apps (agent dashboard + livechat widget).

Source of truth:

  • Tokens (CSS variables): shared-ui/assets/styles/main.scss - :root, .light and .dark blocks
  • Tailwind mapping: tailwind.config.cjs - theme.extend.colors and borderRadius
  • Primitives: shared-ui/components/ui/ (shadcn-vue)

Golden rule: never hardcode a value that a token exists for. Use bg-success, not bg-green-600. Use rounded-lg, not rounded. Every token has a light and a dark value; if you add one, define both.


1. Color tokens

HSL channel triples in CSS variables, consumed via hsl(var(--x)) (Tailwind classes like bg-primary do it for you). Opacity modifiers work: bg-success/10, text-primary/80.

Token Light Dark Use for
background 0 0% 99.2% 120 2.6% 7.6% app/content surface
foreground 0 0% 1% 150 6% 93% primary text
foreground-lighter 0 0% 41% 150 1% 60% idle sidebar nav items
card / card-foreground 0 0% 100% / 0 0% 1% 120 2% 10% / 150 6% 93% raised card surface
popover / popover-foreground 0 0% 100% / 0 0% 1% 120 2% 12% / 150 6% 93% menus, popovers, dropdowns
primary / primary-foreground 152 39% 30% / 0 0% 100% 152 58% 54% / 120 2.6% 7.6% brand, active state, unread badges, primary buttons
secondary / secondary-foreground 0 0% 96% / 0 0% 1% 150 3% 12% / 150 6% 93% secondary buttons, outgoing message bubbles
muted / muted-foreground 0 0% 96% / 0 0% 27% 150 3% 13% / 120 1% 74% muted backgrounds, captions/meta text, secondary labels
accent / accent-foreground 0 0% 95% / 0 0% 1% 150 3% 15% / 150 6% 93% hover and selected states
destructive / destructive-foreground 2 47% 46% / 9 100% 99% 4 92% 74% / 12 38% 3% errors, delete, SLA breached, overdue, offline
success / success-foreground 142 72% 37% / 0 0% 98% 142 55% 55% / 142 40% 10% positive/met, verified, online, connected, delivered
warning / warning-foreground 39 85% 43% / 24 45% 2% 36 87% 62% / 24 45% 2% away, pending, SLA approaching, connecting (as background)
warning-600 35 92% 33% 36 87% 62% warning as TEXT/icon color. Plain warning fails 4.5:1 on light
link 210 100% 40% 210 90% 66% links inside rendered email/message content only. UI links use .link-style
border 0 0% 91% 150 2% 16% all borders/dividers
input 0 0% 85% 150 2% 19% form field borders
ring 151 41% 45% 152 45% 33% focus rings
private 35 90% 94% 30 35% 18% private-note background tint
canvas 0 0% 82% 120 3% 4% app gutter behind floating panels (deepest surface)

Sidebar tokens (left nav chrome): sidebar-background matches background in both themes, plus sidebar-foreground, sidebar-primary, sidebar-accent, sidebar-accent-foreground, sidebar-border, sidebar-ring.

Chart tokens (unovis): --vis-primary-color: var(--primary), --vis-secondary-color: var(--success), --vis-text-color: var(--muted-foreground).

Semantic status mapping

Color carries meaning; use the semantic token, never a raw palette color.

  • success (green): SLA met, identity verified, agent online, widget connected, message delivered/read
  • warning (amber): agent away, SLA approaching/remaining, widget connecting, no-internet banner
  • destructive (red): error, delete, SLA breached, SLA overdue
  • primary (green): brand identity, active nav, unread count badges, primary actions
  • foreground / muted: neutral data (counts, totals, timestamps). Do not color a number unless the color means something. On the reports dashboard numbers are neutral; only met=success and breached=destructive are colored.

Deliberate exceptions

  • features/conversation/message/attachment/BubbleAttachmentItem.vue colors attachment icons by file type (pdf=red, spreadsheet=green, doc=blue, archive=amber, audio=purple) with raw palette classes. The color is file-type identity, not status, and no token means "blue = document". Leave it.
  • components/editor/TextEditor.vue - styles for rendered email HTML. Emails are standalone documents, not themed.
  • features/admin/inbox/LivechatInboxForm.vue / LivechatWidgetPreview.vue - the customer-configurable widget brand color and its defaults.
  • features/conversation/ReplyBox.vue - a linear-gradient(#000 0 0) CSS mask trick, not a color choice.

2. Surfaces and depth

Both themes use three tiers so panels read as floating, not flat:

canvas (deepest gutter behind the panels)
  └─ background (app content + sidebar chrome, same value)
        └─ card / popover (lifted by border + shadow-sm)

In light mode card is pure white against a near-white background. In dark mode card and popover are lighter than the background, so lift comes from the surface, not the shadow.


3. Typography

Font: Geist. Sizes: text-xs 12 · text-sm 14 · text-base 16 · text-lg 18 · text-xl 20 · text-2xl 24 · text-3xl 30. Weights: 400 body · 500 labels · 600 headings · 700 rare emphasis.

Role Style
Page / panel title text-xl font-semibold
KPI / stat value text-2xl (or text-3xl) font-bold tabular-nums
Section label .sidebar-section-label utility, or the classes it applies
Body text-sm
Caption / meta / helper text-xs text-muted-foreground

Use tabular-nums for any numeric column, timer, or metric to stop width jitter.


4. Radius

--radius = 0.5rem (8px). The Tailwind scale derives from it:

Class Value Use for
rounded-xl radius + 4 (12px) large surfaces, widget window preview, dashboard message bubbles
rounded-lg radius (8px) cards, containers, dialogs
rounded-md radius - 2 (6px) buttons, inputs, chips, badges, small interactive
rounded-sm radius - 4 (4px) tiny insets
rounded-full - avatars, status dots, count badges, pills

Never use bare rounded. It is Tailwind's fixed 4px and ignores the token.


5. Elevation

Class Use for
shadow-sm cards, .box, default buttons
shadow-md popovers, dropdown menus, hover/floating elements
shadow-lg dialogs, modals, the widget window

Never use bare shadow. Depth comes from the surface tiers and borders, not heavy shadows.


6. Spacing

Follow a 4 / 8px rhythm for padding and gaps. Vertical section spacing tiers: 16 / 24 / 32 / 48. Keep the dense-desk feel; this is a high-volume support tool, not a marketing page.


7. Utilities

Defined in main.scss. Prefer them over repeating the class list.

Utility Expands to Use for
.box border shadow-sm rounded-lg the standard card surface
.sidebar-section-label text-xs font-medium uppercase tracking-wider text-muted-foreground sidebar group headers (Views, Team Inboxes)
.link-style text-muted-foreground underline underline-offset-4 hover:text-foreground UI links. Not brand-colored, so links in chrome stay quiet

8. Components

Reuse shared-ui/components/ui/ primitives; do not hand-roll a styled <button>/<input> when one exists.

Button - use the size variant, never an ad-hoc h-*:

size height notes
default h-9 standard
sm h-8 dense (text-xs)
xs h-7 very dense
lg h-10 prominent
icon h-9 w-9 icon-only

Variants: default (primary), destructive, outline, secondary, ghost, link. h-8 w-8 ghost triggers for dense table row actions are an intentional pattern.

Every button inside a <form> that is not the submit button needs an explicit type="button". HTML defaults to type="submit", so a typeless button submits the form on click and gets activated when the user presses Enter in any input. This includes buttons in child components rendered inside a parent's form.

Badge variants: default, secondary, destructive, success, outline.

AlertDialogAction takes a variant prop, so destructive confirms use variant="destructive" instead of a hand-written class.

Sidebar nav items are font-medium text-foreground-lighter; hover and active states use bg-sidebar-accent (hover at /50). Group headers use .sidebar-section-label.

Tooltip is bg-foreground text-background, not brand-colored.

Table row actions stay hidden until hover: [@media(hover:hover)]:opacity-0 with group-hover/row:opacity-100, plus focus-within:!opacity-100 and [&:has([data-state=open])]:!opacity-100. The media query keeps them visible on touch and focus-within keeps them reachable by keyboard.


9. Checklist

Before merging UI work:

  • No hardcoded palette colors (bg-green-600, text-blue-500, ...). Only exception: file-type icons.
  • No bare rounded or shadow.
  • Every non-submit button in a form has type="button".
  • Color on a number or element means something, and is not decoration.
  • Both light and dark verified.
  • New shared words go through i18n (i18n/en-US.json); reused nouns in globals.terms.
  • Reused an existing ui/ primitive rather than building a variant.