Theming
The CSS variables the components read, dark mode, corners, fonts and motion.
: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
| Variable | Paints | Fallback |
|---|---|---|
--card |
The panel, the modal, the unread card, the feedback panel, the contact form, the composer | oklch(1 0 0) |
--card-foreground |
Text on those surfaces | oklch(0.145 0 0) |
--muted |
The agent’s bubbles, the AI label, hover fills, initials on teammate avatars. Mixed into the visitor’s bubble | oklch(0.955 0 0) |
--muted-foreground |
Timestamps, the status line, placeholders, icon buttons, the Powered by Median line, scrollbars, the letters on teammate avatars | oklch(0.556 0 0) |
--foreground |
The 12% tint in the visitor’s bubble, typing dots, the AI agent’s default avatar tile, inline code, hover text | oklch(0.145 0 0) |
--background |
The icon on the AI agent’s default avatar, the digits of the unread count, the starter rows, the scroll to latest button | oklch(0.976 0 0) |
--primary |
The launcher, send buttons, the highlight ring around an element on your page | oklch(0.205 0 0) |
--primary-foreground |
Icons and text on --primary |
oklch(0.985 0 0) |
--destructive |
The unread count on the launcher, error text, invalid fields | oklch(0.577 0.245 27.325) |
--border |
Edges of the panel and cards, dividers | oklch(0.916 0 0) |
--input |
Borders of the composer and form fields | oklch(0.916 0 0) |
--ring |
Focus rings, the border of a focused composer or field | oklch(0.708 0 0) |
--popover, --accent and --secondary have no visible effect on the
components. For --radius, see Corners.
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.
.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.
| Element | Corner |
|---|---|
| Panel, modal, unread card, feedback panel, contact form | 0.875rem |
| Buttons and fields inside them | Derived from a --radius pinned to 0.625rem |
| Launcher | A circle |
| Highlight ring around an element on your page | Your --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.
| Variable | Default | Used by |
|---|---|---|
--ease-spring |
cubic-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-quint |
cubic-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:
[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:
| Root | Rendered |
|---|---|
| The launcher and corner panel | Where you render MedianSupport, or in <body> from mountMedianSupport |
| The modal | In <body> |
| The highlight ring | In <body> |
| The feedback panel | In <body> |
| The contact form | Where you mount it |