Median

Theming

The CSS variables the components read, dark mode, corners, fonts and motion.

Updated Oct 1, 20263 minute read
globals.css
:root {
  --primary: oklch(0.55 0.2 260);
  --primary-foreground: oklch(0.985 0 0);
}

The components read the same CSS variables as shadcn/ui. A site that uses shadcn/ui already defines them. A variable you leave out falls back to Median's light theme.

Variables

VariablePaintsFallback
--cardThe panel, the modal, the unread card, the feedback panel, the contact form, the composeroklch(1 0 0)
--card-foregroundText on those surfacesoklch(0.145 0 0)
--mutedThe agent's bubbles, the AI label, hover fills, initials on teammate avatars. Mixed into the visitor's bubbleoklch(0.955 0 0)
--muted-foregroundTimestamps, the status line, placeholders, icon buttons, the Powered by Median line, scrollbars, the letters on teammate avatarsoklch(0.556 0 0)
--foregroundThe 12% tint in the visitor's bubble, typing dots, the AI agent's default avatar tile, inline code, hover textoklch(0.145 0 0)
--backgroundThe icon on the AI agent's default avatar, the digits of the unread count, the starter rows, the scroll to latest buttonoklch(0.976 0 0)
--primaryThe launcher, send buttons, the highlight ring around an element on your pageoklch(0.205 0 0)
--primary-foregroundIcons and text on --primaryoklch(0.985 0 0)
--destructiveThe unread count on the launcher, error text, invalid fieldsoklch(0.577 0.245 27.325)
--borderEdges of the panel and cards, dividersoklch(0.916 0 0)
--inputBorders of the composer and form fieldsoklch(0.916 0 0)
--ringFocus rings, the border of a focused composer or fieldoklch(0.708 0 0)

--popover, --accent and --secondary have no visible effect on the components. For --radius, see Corners.

:::warning The visitor's bubble is color-mix(in oklch, var(--muted), var(--foreground) 12%) with no fallback. Define both --muted and --foreground, or the visitor's bubble has no background. The glow around the highlight ring reads --primary the same way. :::

Where to set them

Set the variables on :root or html. The modal, the highlight ring, the feedback panel, and the widgets from mountMedianSupport, mountMedianSupportModal and mountMedianFeedback render into <body>, outside any wrapper element in your app. The contact form and a MedianSupport you render yourself inherit from wherever you place them.

Dark mode

The components have no dark palette of their own. Define your dark values and they follow, whether you switch themes with a class or a media query.

globals.css
.dark {
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --foreground: oklch(0.985 0 0);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
}

A few small fills and borders, and the logo in the Powered by Median line, switch with your theme: a dark class or data-theme="dark" on the page. The operating system's setting does not change them, so a light site stays light. A site that switches themes with only a media query gets its own dark colours, with those few details left light.

Corners

Your --radius does not change the components' corners. Only the highlight ring reads it.

ElementCorner
Panel, modal, unread card, feedback panel, contact form0.875rem
Buttons and fields inside themDerived from a --radius pinned to 0.625rem
LauncherA circle
Highlight ring around an element on your pageYour --radius, or 0.625rem without one

Fonts and sizes

  • The components use your page's font-family. Code uses the system monospace stack.
  • Sizes are in rem. A root font size other than 16px scales every component.
  • On screens narrower than 40rem, text fields use 16px text so iOS does not zoom in.

Motion

Two variables set the easing of most transitions.

VariableDefaultUsed by
--ease-springcubic-bezier(0.34, 1.45, 0.64, 1)The corner panel opening and closing, the modal and feedback panel opening, the launcher and unread card appearing
--ease-out-quintcubic-bezier(0.22, 1, 0.36, 1)The phone sheet, the modal closing, a panel opening with no launcher, the launcher hiding

The stylesheet declares both on the components' own root, so a value on :root does not reach them. Set them on [data-median-widget], outside any @layer:

globals.css
[data-median-widget] {
  --ease-spring: cubic-bezier(0.22, 1, 0.36, 1);
}

Durations are fixed. The launcher's flight to the edge after a drag, the contact form's sent mark and bubbles changing size run in JavaScript and ignore both variables.

When the visitor's system asks for reduced motion, the components turn their animations and transitions off.

Scoping

Every rule in @mediansh/widget/styles.css is limited to elements under [data-median-widget], so your page keeps its own styles. The stylesheet carries a reset scoped the same way, and the components look the same on pages without Tailwind.

These roots carry the attribute:

RootRendered
The launcher and corner panelWhere you render MedianSupport, or in <body> from mountMedianSupport
The modalIn <body>
The highlight ringIn <body>
The feedback panelIn <body>
The contact formWhere you mount it

Still need help?

    Esc