Skip to content

Custom Theming ​

react-dockable-desktop ships with 7 built-in skins and a clean CSS custom-property architecture that lets you add your own without modifying any library code. To put your company's colour and font on a built-in skin, you don't need a skin of your own at all — see Brand your app.

How it works ​

The skin prop on <RddDesktop /> sets a data-rdd-skin attribute on the workspace root element (and on <html>, so the sidebar and toolbar outside it pick it up too). The library stylesheet uses attribute selectors to map skin names to CSS custom properties:

css
[data-rdd-skin="nord"] {
  --rdd-bg-workspace: #2e3440;
  --rdd-accent-color: #88c0d0;
  /* ... */
}

All child components inherit those variables. Because skin is typed as string (not a restricted union), any name you define in CSS becomes valid — <RddDesktop skin="my-brand" /> works immediately.

Brand your app ​

Every built-in skin, in dark and light, takes your company's colour and font from three variables set on :root — and, if you want them, your surfaces and corner shape from three more:

css
:root {
  --rdd-brand-accent: #e4002b;                 /* your brand colour */
  --rdd-brand-on-accent: #ffffff;              /* text on a brand-coloured fill (see below) */
  --rdd-font-family: 'Acme Sans', sans-serif;  /* your brand font */
}

That's all. Leave a variable unset and the skin keeps its own value.

VariableWhat follows itDefault
--rdd-brand-accentEverything a skin draws in its accent: tab indicators, the active sidebar tab and toolbar button, hover and active tints, glows, the focused window's glow, the taskbar, focus rings, the primary button.Each skin's own accent
--rdd-brand-on-accentText drawn on a solid accent fill: the confirmation dialog's primary button and the highlighted dock target while you drag.#090b11 (the primary button in light mode: #ffffff)
--rdd-font-familyEvery piece of chrome — tabs, title bars, toolbar, sidebar, menus, flyouts, toasts, drawers, modals — and panel content, which inherits the workspace font.Each skin's own font

Set them on :root (or <body>), not on a wrapper around the workspace: context menus, the toolbar flyout and toasts render straight into <body>, so a value set on a wrapper doesn't reach them.

Light brand colours

The library can't tell whether your colour is light or dark. With a light brand colour — yellow, lime, a pale cyan — set --rdd-brand-on-accent to a dark colour, so text on a brand-coloured button stays readable:

css
:root {
  --rdd-brand-accent: #facc15;
  --rdd-brand-on-accent: #1a1a1a;
}

A different brand colour for light mode ​

One brand colour applies to both schemes. If yours needs a darker shade on a light background, scope a second value to the light scheme:

css
:root                            { --rdd-brand-accent: #ff5a5f; }
:root[data-color-scheme="light"] { --rdd-brand-accent: #d93b40; }

Your brand font ​

The library never loads a font. Load your company font the way you already do — an @font-face rule, a <link> to your font provider, your design system's font package — and name it in --rdd-font-family, followed by fallbacks:

css
@font-face {
  font-family: 'Acme Sans';
  src: url('/fonts/acme-sans.woff2') format('woff2');
  font-display: swap;
}
:root {
  --rdd-font-family: 'Acme Sans', system-ui, sans-serif;
}

To use your page's own font instead, set --rdd-font-family: inherit on :root. See Fonts for the details.

Use your UI framework's theme ​

The library doesn't depend on any UI framework, so it can't read your theme by itself — but because your framework's theme is already CSS variables on the page, pointing the brand variables at them is one line each, and the workspace then follows your theme, including when it changes at runtime:

FrameworkBrand colourText on itFont
Bootstrap 5.3var(--bs-primary)—var(--bs-body-font-family)
MUI (with cssVariables: true in createTheme)var(--mui-palette-primary-main)var(--mui-palette-primary-contrastText)your theme.typography.fontFamily
Angular Material 3var(--mat-sys-primary)var(--mat-sys-on-primary)your typography's font family
Tailwind CSS v4a theme colour, e.g. var(--color-indigo-600)—var(--font-sans)
shadcn/uivar(--primary) — or hsl(var(--primary)) in versions that store it as HSL numbersvar(--primary-foreground) (same rule)your font

For example, with Bootstrap:

css
:root {
  --rdd-brand-accent: var(--bs-primary);
  --rdd-font-family: var(--bs-body-font-family);
}

The variable you point at must be defined on :root (or <html>), where the brand variables are read.

Your surfaces ​

Two more variables replace a skin's backgrounds and text with your own, per scheme: --rdd-brand-surface (the app background) and --rdd-brand-text (the main text colour). The library derives every other surface from those two — panels and the workspace a few percent towards the text, the tab bar and rail a little darker, borders and muted text as mixes of the two — so layers stay distinct and text stays readable:

css
/* dark is the default: rdd reads a missing data-color-scheme as dark */
:root:not([data-color-scheme="light"]) {
  --rdd-brand-surface: #0b1f3a;
  --rdd-brand-text: #e8eef7;
}
:root[data-color-scheme="light"] {
  --rdd-brand-surface: #f4f1ec;
  --rdd-brand-text: #2b2620;
}
  • Set both, or neither. With only one of them set, every skin keeps its own surfaces — half a palette is the case most likely to be unreadable.
  • A scheme you leave out keeps the skin's surfaces. Brand dark only, and light mode looks as it always did.
  • A skin keeps its shape and effects — macOS's glass and window buttons, Chrome's tabs, the VS Code accent bar. Only the colours come from you, so with a brand surface set the skins differ by shape, not by colour. Each translucent surface (macOS panels, floating windows, modals) keeps the skin's own transparency.
  • Not affected: the accent (that's --rdd-brand-accent), status colours (errors, warnings, the toast types), and shadows.

Pick a surface and a text colour with enough contrast between them — the text is used as-is on the panels. The library's own check requires 4.5:1 for the main text on panels with the two colours above.

Corners ​

--rdd-radius-scale multiplies every corner the library draws:

css
:root { --rdd-radius-scale: 0; }    /* square corners, everywhere */

1 (the default) is each skin's own shape, 0 is square, 1.5 is rounder; a skin keeps its own proportions at every scale, so macOS stays rounder than VS Code. Circles and pills stay round: macOS's window buttons and the taskbar's peek handle. It also scales the three radius tokens you can set yourself (--rdd-panel-float-radius, --rdd-panel-toolbar-btn-radius, --rdd-tab-btn-active-radius).

The library draws no logo of its own — where one goes is your app's decision. Two natural places:

  • Your own header or toolbar content, outside the workspace — it's your markup, so anything goes.
  • The top of the RddSidebar rail, where VS Code, Slack and Teams put theirs. headerAction accepts a custom render:
tsx
<RddSidebar
  tabs={tabs}
  headerAction={{ render: () => <img src={logo} alt="Acme" className="acme-logo" /> }}
>
  <RddDesktop />
</RddSidebar>

The rail is narrow, so a square mark fits better than a wide wordmark. Adjust its spacing with --rdd-sidebar-header-area-padding-top and --rdd-sidebar-header-area-padding-bottom (both 8px).

Browser support ​

Branding relies on CSS color-mix(), available since Chrome 111, Edge 111, Safari 16.2 and Firefox 113 (all 2023). In an older browser the tinted hover and active highlights lose their colour; layout and behaviour are unaffected.

Frosted glass and your own overlays ​

Several skins draw floating windows, drawers, overlay widgets, frosted panel toolbars and (in macos) docked panels as frosted glass. Since 7.4.0 the frost is drawn on each container's ::before, never on the container itself. A backdrop-filter on an element makes it the containing block for position: fixed content, so a dropdown or popover of yours inside a frosted window used to be positioned against the window rather than the viewport — in some skins and not others. Now position: fixed inside any library container means the viewport, in every skin.

If you write a skin of your own that frosts a container hosting content, do the same: put the backdrop-filter (and the background it tints) on ::before.

Motion ​

The library's own transitions and animations stop when the user has asked the system for reduced motion (prefers-reduced-motion: reduce, 7.4.0), and whenever animations={false} is set on RddDesktop. Neither touches your own transitions.

Built-in skins ​

tsx
<RddDesktop skin="vscode" />   {/* default */}
SkinCharacterActive state
vscodeVS Code dark — neutral dark blue-gray, cyan accent. The default.Transparent fill, 2 px accent bar — identical to VS Code's own activity bar
macosGlass Chip — accent-tinted fill, rounded corners.36 px floating chip, 10 px radius, white inner ring — macOS icon strip convention
chromeGoogle Chrome — angled tab geometry, Google blue accent.Sidebar: colour-matched half-pill bridge to drawer. Toolbar: 2 px accent bar
slateFluent Slate — deep navy/slate palette, sky-blue accent.Floating 36 px accent-tinted pill, 8 px radius — Fluent Design language
nordArctic Frost — muted blue-gray from the Nord color palette.Short horizontal line below the icon (no fill, no bar)
obsidianVercel Midnight — pure black/white, high-contrast minimal.Near-black fill, inset glow shadow, icon drop-shadow filter
tokyoTokyo Night — purple accent on dark blue-gray, inspired by the popular editor theme.Accent-tinted fill, neon glow, vivid icon drop-shadow filter

All built-in skins include both dark and light variants — see Dark and light variants below.

Each skin also brings its own font, the platform's UI font where it has a known one. They are system font stacks — the library loads none of them, so each machine uses the first one it has — and your --rdd-font-family replaces them all:

SkinFont (--rdd-skin-font-family)
vscodeVS Code's workbench font: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif
macosSan Francisco: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', Helvetica, Arial, sans-serif
chromeGoogle's UI fonts: 'Google Sans Text', 'Google Sans', Roboto, system-ui, -apple-system, 'Segoe UI', sans-serif
slateFluent's stack: 'Segoe UI Variable Text', 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serif
nordA softer humanist sans: 'Avenir Next', 'Nunito', 'Segoe UI', system-ui, sans-serif
obsidianThe library's fallback stack: 'Outfit', 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif
tokyoA terminal/editor feel: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace

Panel content inherits the workspace font, so in tokyo your own panels turn monospace too unless they set a font of their own.

Per-skin active state design language ​

Every built-in skin applies a distinct visual pattern to active Sidebar tabs and active Toolbar buttons. Both components share the same CSS design tokens, so they always read as a matched pair — if a skin uses a floating chip in the Sidebar, it uses the same chip shape in the Toolbar.

SkinPatternVisual idea
vscodeAccent BarTransparent fill, 2 px bar at the inner edge — the same language VS Code's own activity bar uses
macosGlass Chip36 px contained chip, 10 px radius all corners, white inner ring — macOS icon strip selection
chromeTab BridgeSidebar: colour-matched half-pill connecting icon to drawer. Toolbar: 2 px accent bar
slateFluent Pill36 px floating accent-tinted pill, 8 px radius — Microsoft Fluent Design selection pattern
nordLine IndicatorTransparent fill, no bar — a short horizontal line drawn below the icon via ::after
obsidianDeep GlowNear-black fill, inset ambient glow shadow, icon drop-shadow — subtle without color
tokyoNeon PulseAccent-tinted fill, vivid neon inner glow, icon drop-shadow — high-energy Tokyo Night feel

Design tokens ​

These CSS custom properties drive the active state shape and effects. They are declared in :root with neutral defaults and overridden per skin.

Sidebar strip

TokenDefaultControls
--rdd-tab-btn-active-bg#1e2024Fill color of the active tab button.
--rdd-tab-btn-active-width100%Button width. Chip skins (macos, slate) set 36px for a contained floating shape.
--rdd-tab-btn-active-radius0pxBorder-radius. Chip skins set 10px or 8px for rounded corners on all sides.
--rdd-tab-btn-active-shadownonebox-shadow on the button. Obsidian/Tokyo add an inset ambient glow.
--rdd-tab-btn-active-glownonefilter on the button. Obsidian/Tokyo add drop-shadow() for icon glow.
--rdd-tab-accent-bar-width3pxWidth of the edge accent bar. Set to 0px to suppress it entirely.

Toolbar strip

TokenDefaultControls
--rdd-toolbar-btn-radio-active-bgthe accent at 14%Fill color of the active radio/group button.
--rdd-toolbar-btn-active-shadownonebox-shadow on active toolbar buttons.
--rdd-toolbar-btn-active-glownonefilter on active toolbar buttons.
--rdd-toolbar-accent-bar-width3pxWidth of the toolbar edge accent bar.

Both strips share --rdd-tab-icon-active for the accent color — set it once and both update. By default it is --rdd-accent-color itself, so it follows a brand colour too.

Customising the active state in your own skin ​

Only override the tokens you want to change; all others inherit their :root defaults.

Minimal: narrower accent bar

css
[data-rdd-skin="my-skin"] {
  --rdd-tab-accent-bar-width: 1px;
  --rdd-toolbar-accent-bar-width: 1px;
}

Floating pill (like slate)

css
[data-rdd-skin="my-skin"] {
  --rdd-tab-btn-active-bg:           color-mix(in srgb, var(--rdd-accent-color) 18%, transparent);
  --rdd-tab-btn-active-width:        36px;
  --rdd-tab-btn-active-radius:       8px;
  --rdd-tab-accent-bar-width:        0px;
  --rdd-toolbar-btn-radio-active-bg: color-mix(in srgb, var(--rdd-accent-color) 18%, transparent);
  --rdd-toolbar-accent-bar-width:    0px;
}
[data-rdd-skin="my-skin"] .rdd-sidebar-tab-btn.rdd-active {
  border-top: 1px solid transparent !important;
  border-bottom: 1px solid transparent !important;
  margin: 0 !important;
}
[data-rdd-skin="my-skin"] .rdd-toolbar-strip .rdd-toolbar-btn-radio.rdd-active,
[data-rdd-skin="my-skin"] .rdd-toolbar-strip .rdd-toolbar-btn-group.rdd-active {
  border-radius: 8px !important;
}

Icon glow (dark skin)

css
[data-rdd-skin="my-skin"] {
  --rdd-tab-btn-active-glow:     drop-shadow(0 0 5px color-mix(in srgb, var(--rdd-accent-color) 60%, transparent));
  --rdd-toolbar-btn-active-glow: drop-shadow(0 0 5px color-mix(in srgb, var(--rdd-accent-color) 60%, transparent));
}

Writing tints as color-mix() of --rdd-accent-color, as these examples do, rather than as a fixed rgba(), keeps them in step with the accent — and with a brand colour, if one is set.

Keep Sidebar and Toolbar in sync

Both components share --rdd-tab-icon-active for the accent color — set it once and both update. Match --rdd-tab-accent-bar-width to --rdd-toolbar-accent-bar-width (and the fill/glow tokens) so Sidebar and Toolbar always read as a consistent pair.

Creating a custom skin ​

Step 1 — Import order in main.tsx

Your skin CSS must be imported after the library stylesheet so your variables win:

ts
// main.tsx
import 'react-dockable-desktop/styles.css';  // library first
import './my-skin.css';                        // your skin second

Step 2 — Define the CSS block

Create a file (e.g. my-skin.css) and define a block using [data-rdd-skin]:

css
/* my-skin.css */
[data-rdd-skin="my-brand"] {
  --rdd-bg-workspace:  #0d1117;
  --rdd-bg-panel:      #161b22;
  --rdd-bg-tab-bar:    #0d1117;
  --rdd-accent-color:  var(--rdd-brand-accent, #f78166);
  --rdd-accent-glow:   color-mix(in srgb, var(--rdd-accent-color) 15%, transparent);
  --rdd-skin-font-family: 'IBM Plex Sans', system-ui, sans-serif;
  --rdd-border-panel:  #30363d;
  --rdd-text-tab-inactive: #8b949e;
  --rdd-text-tab-active:   #f0f6fc;
  --rdd-window-bg:     rgba(22, 27, 34, 1.0);
  --rdd-window-border: #30363d;
  --rdd-window-shadow: 0 16px 40px rgba(1, 4, 9, 0.8);
}

You only need to override the variables you want to change. Any variable you omit inherits from the library defaults (:root).

Let your skin take a brand, as the built-in ones do

Four habits keep a skin brandable, as the built-in ones are:

  • Declare the accent as var(--rdd-brand-accent, <your colour>), never as a bare colour.
  • Write every tint of it as color-mix(in srgb, var(--rdd-accent-color) N%, transparent) instead of an rgba() of the same colour — then one accent drives them all.
  • Give your skin a font with --rdd-skin-font-family, never --rdd-font-family: declared in a skin, --rdd-font-family would override the one a consumer sets on :root. Likewise, never declare a --rdd-brand-* variable in a skin — those belong to the app.
  • Write a corner radius your element rules add as calc(6px * var(--rdd-radius-scale, 1)), so --rdd-radius-scale reaches it too.

A skin of your own keeps its own surfaces: brand surfaces recolour the built-in skins, and a custom skin is where you choose every colour yourself.

Step 3 — Pass the name to RddDesktop

tsx
<RddDesktop skin="my-brand" />

That's it. No TypeScript changes, no library recompilation, no config registration.

Dark and light variants ​

The colour scheme is your app's to set: put data-color-scheme="light" on <html> for light, and remove it (or set "dark") for dark, which is also the default when the attribute is absent. The library never writes it on <html> and doesn't read the system preference — follow prefers-color-scheme yourself if you want to:

ts
document.documentElement.setAttribute('data-color-scheme', 'light'); // light
document.documentElement.removeAttribute('data-color-scheme');       // dark (the default)

The workspace root copies the value, so skins can target it with a compound selector:

css
/* Dark mode — usually your primary skin definition */
[data-rdd-skin="my-brand"] {
  --rdd-bg-workspace: #0d1117;
  --rdd-accent-color: var(--rdd-brand-accent, #f78166);
  /* ... */
}

/* Light mode override */
[data-rdd-skin="my-brand"][data-color-scheme="light"] {
  --rdd-bg-workspace: #ffffff;
  --rdd-bg-panel:     #f6f8fa;
  --rdd-bg-tab-bar:   #f6f8fa;
  --rdd-accent-color: var(--rdd-brand-accent, #cf222e);
  --rdd-text-tab-inactive: #57606a;
  --rdd-text-tab-active:   #1f2328;
  --rdd-window-bg:    rgba(246, 248, 250, 1.0);
  --rdd-window-border: #d0d7de;
  --rdd-window-shadow: 0 8px 24px rgba(140, 149, 159, 0.12);
  --rdd-panel-text:   #1f2328;
  --rdd-panel-title-color: var(--rdd-accent-color);
  --rdd-close-btn-color: #57606a;
  --rdd-close-btn-active-color: #1f2328;
}

You are not required to provide a light variant. If omitted, the dark definition applies in both modes.

Reading the current color scheme in your own code ​

If your panel content needs to react in JavaScript (not just CSS) to the same scheme the workspace is using — swapping a map's tile layer or an embedded editor's theme, for example — use useColorScheme():

tsx
import { useColorScheme } from 'react-dockable-desktop';

function MyMapPanel() {
  const colorScheme = useColorScheme(); // 'dark' | 'light', updates live

  useEffect(() => {
    tileLayer.setUrl(colorScheme === 'light' ? LIGHT_TILES : DARK_TILES);
  }, [colorScheme]);

  return <div ref={containerRef} />;
}

It reads and reactively tracks the data-color-scheme attribute on <html> described above, so it matches the library's own [data-color-scheme] styles as long as you set the attribute there, which is the supported place. (A data-color-scheme on some wrapper element isn't read, and inside the workspace the <html> value wins.)

Runtime skin switching ​

Because skin is a regular React prop, switching skins at runtime is just state:

tsx
import { useState } from 'react';

const SKINS = ['vscode', 'nord', 'tokyo', 'macos', 'my-brand'];

function App() {
  const [skin, setSkin] = useState('vscode');

  return (
    <DockableDesktopProvider workspace={workspace}>
      <div className="rdd-fill-viewport" style={{ position: 'relative' }}>
        <RddDesktop skin={skin} />
        <RddSidePanels />
      </div>
      <RddModals />

      {/* Skin picker anywhere outside the workspace */}
      <select value={skin} onChange={e => setSkin(e.target.value)}>
        {SKINS.map(s => <option key={s} value={s}>{s}</option>)}
      </select>
    </DockableDesktopProvider>
  );
}

The browser applies the new CSS variable set instantly — no remounting.

Starter template ​

Copy this into your CSS file and fill in the color values. All variable names are included; delete any you want to inherit from the default:

css
/* ============================================================
   My custom skin starter template
   Usage: <RddDesktop skin="my-skin" />
   ============================================================ */

[data-rdd-skin="my-skin"] {

  /* --- Workspace backgrounds --- */
  --rdd-bg-primary:    #090b11;      /* workspace background */
  --rdd-bg-workspace:  #0f111a;      /* workspace canvas */
  --rdd-bg-panel:      #141722;      /* docked panel background */
  --rdd-bg-tab-bar:    #0d0f16;      /* tab bar strip */

  /* --- Text --- */
  --rdd-text-primary:   #f1f5f9;
  --rdd-text-secondary: #94a3b8;

  /* --- Borders --- */
  --rdd-border-color: rgba(255, 255, 255, 0.08);  /* generic border */
  --rdd-border-panel: rgba(255, 255, 255, 0.08);  /* panel border */

  /* --- Accent (brandable: an app's --rdd-brand-accent replaces the colour) --- */
  --rdd-accent-color: var(--rdd-brand-accent, #38bdf8);                            /* primary interactive color */
  --rdd-accent-glow:  color-mix(in srgb, var(--rdd-accent-color) 15%, transparent); /* focus rings, halos */

  /* --- Font (an app's --rdd-font-family replaces it) --- */
  --rdd-skin-font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;

  /* --- Tabs --- */
  --rdd-bg-tab-inactive:  #0c0d12;
  --rdd-bg-tab-hover:     #171a22;
  --rdd-text-tab-inactive: #858b99;
  --rdd-text-tab-active:   #ffffff;
  --rdd-text-tab-hover:    #e2e8f0;

  /* Tab active-state indicators */
  --rdd-tab-indicator-focused:    var(--rdd-accent-color);
  --rdd-tab-indicator-unfocused:  rgba(255, 255, 255, 0.3);
  --rdd-tab-bg-active-focused:    var(--rdd-bg-panel);
  --rdd-tab-bg-active-unfocused:  rgba(20, 23, 34, 0.55);
  --rdd-tab-text-active-focused:  var(--rdd-text-tab-active);
  --rdd-tab-text-active-unfocused: rgba(255, 255, 255, 0.65);

  /* --- Close / control buttons --- */
  --rdd-close-btn-color:        #858b99;
  --rdd-close-btn-active-color: #e2e8f0;
  --rdd-close-btn-hover-bg:     rgba(255, 255, 255, 0.12);
  --rdd-close-btn-hover-color:  #ffffff;

  /* Custom (anchor/minimize/maximize) buttons */
  --rdd-custom-btn-bg:         rgba(255, 255, 255, 0.03);
  --rdd-custom-btn-border:     rgba(255, 255, 255, 0.05);
  --rdd-custom-btn-hover-bg:   rgba(255, 255, 255, 0.12);
  --rdd-custom-btn-hover-color: #ffffff;

  /* --- Floating windows --- */
  --rdd-window-bg:              rgba(20, 22, 28, 0.85);   /* supports var(--rdd-window-opacity) */
  --rdd-window-border:          rgba(255, 255, 255, 0.08);
  --rdd-window-border-focused:  rgba(255, 255, 255, 0.28);
  --rdd-window-header-bg:       rgba(0, 0, 0, 0.25);
  --rdd-window-text:            #f8f9fa;
  --rdd-window-shadow:          0 16px 40px rgba(0, 0, 0, 0.4);
  --rdd-window-shadow-focused:  0 24px 50px rgba(0, 0, 0, 0.55);

  /* --- Resizer --- */
  --rdd-resizer-bg: rgba(255, 255, 255, 0.08);

  /* --- Taskbar --- */
  --rdd-taskbar-bg:            rgba(0, 0, 0, 0.75);
  --rdd-taskbar-border:        rgba(255, 255, 255, 0.1);
  --rdd-taskbar-nav-color:     rgba(255, 255, 255, 0.5);
  --rdd-taskbar-item-bg:       rgba(15, 23, 42, 0.6);
  --rdd-taskbar-item-hover-bg: rgba(15, 23, 42, 0.8);
  --rdd-taskbar-item-border:   rgba(255, 255, 255, 0.08);
  --rdd-taskbar-item-text:     var(--rdd-accent-color);

  /* --- Scrollbars --- */
  --rdd-scrollbar-thumb:       rgba(255, 255, 255, 0.1);
  --rdd-scrollbar-thumb-hover: rgba(255, 255, 255, 0.2);
  --rdd-scrollbar-track:       rgba(255, 255, 255, 0.01);

  /* --- Panel cards (mockup / inner panel chrome) --- */
  --rdd-panel-card-bg:     rgba(0, 0, 0, 0.2);
  --rdd-panel-card-border: rgba(255, 255, 255, 0.1);
  --rdd-panel-text:        var(--rdd-text-primary);
  --rdd-panel-title-color: var(--rdd-accent-color);

  /* --- Header button spacing --- */
  --rdd-header-button-gap: 4px;
}

/* Optional: light mode variant */
[data-rdd-skin="my-skin"][data-color-scheme="light"] {
  --rdd-bg-primary:    #f8f9fa;
  --rdd-bg-workspace:  #f1f5f9;
  --rdd-bg-panel:      #ffffff;
  --rdd-bg-tab-bar:    #e9ecef;
  --rdd-text-primary:  #212529;
  --rdd-text-secondary: #6c757d;
  --rdd-border-color:  rgba(0, 0, 0, 0.08);
  --rdd-border-panel:  rgba(0, 0, 0, 0.08);
  --rdd-accent-color:  var(--rdd-brand-accent, #0066cc);
  --rdd-accent-glow:   color-mix(in srgb, var(--rdd-accent-color) 15%, transparent);
  --rdd-bg-tab-inactive:   #e9ecef;
  --rdd-text-tab-inactive: #495057;
  --rdd-text-tab-active:   #212529;
  --rdd-window-bg:          rgba(243, 244, 246, 0.9);
  --rdd-window-border:      rgba(0, 0, 0, 0.08);
  --rdd-window-border-focused: rgba(0, 0, 0, 0.28);
  --rdd-window-header-bg:   rgba(0, 0, 0, 0.04);
  --rdd-window-text:        #212529;
  --rdd-window-shadow:      0 10px 30px rgba(0, 0, 0, 0.06);
  --rdd-window-shadow-focused: 0 16px 36px rgba(0, 0, 0, 0.12);
  --rdd-panel-text:         #212529;
  --rdd-panel-title-color:  var(--rdd-accent-color);
  --rdd-close-btn-color:    #495057;
  --rdd-close-btn-active-color: #212529;
}

The RddSidebar component uses a separate variable set. Override these if your skin includes an RddSidebar. The sidebar sits outside the workspace, so these are read from <html>, which carries data-rdd-skin (set by RddDesktop) and your data-color-scheme. Target dark with the skin selector alone — nothing sets data-color-scheme="dark" in the default dark mode — and light with the compound selector:

css
[data-rdd-skin="my-skin"] {  /* dark (the default) */
  --rdd-sidebar-bg:                  #1e2024;
  --rdd-sidebar-tabs-bg:             #141619;
  --rdd-sidebar-border:              rgba(255, 255, 255, 0.08);
  --rdd-sidebar-card-bg:             rgba(255, 255, 255, 0.03);
  --rdd-sidebar-card-border:         rgba(255, 255, 255, 0.08);
  --rdd-sidebar-card-active-bg:      color-mix(in srgb, var(--rdd-accent-color) 6%, transparent);
  --rdd-sidebar-card-active-border:  color-mix(in srgb, var(--rdd-accent-color) 30%, transparent);
  --rdd-sidebar-text-title:          #f8f9fa;
  --rdd-sidebar-text-muted:          #8a90a0;
  --rdd-sidebar-badge-bg:            #2d3139;
  --rdd-sidebar-badge-text:          #b0b5c0;
  --rdd-sidebar-btn-front-border:    var(--rdd-accent-color);
  --rdd-sidebar-btn-front-text:      var(--rdd-accent-color);
  --rdd-sidebar-btn-front-bg:        transparent;
  --rdd-sidebar-btn-front-hover-bg:  color-mix(in srgb, var(--rdd-accent-color) 10%, transparent);
  --rdd-tab-icon-active:             var(--rdd-accent-color);
  --rdd-tab-icon-inactive:           #9ea4b0;
  --rdd-tab-btn-active-bg:           #1e2024;  /* active tab fill */

  /* Active tab shape and effects — see Per-skin active state design language */
  --rdd-tab-btn-active-width:        100%;     /* set 36px for a floating chip */
  --rdd-tab-btn-active-radius:       0px;      /* set 8px–10px for rounded chip */
  --rdd-tab-btn-active-shadow:       none;     /* inset glow: inset 0 0 12px rgba(...) */
  --rdd-tab-btn-active-glow:         none;     /* icon glow: drop-shadow(0 0 5px rgba(...)) */
  --rdd-tab-accent-bar-width:        3px;      /* set 0px to use a shape-only indicator */
}

[data-rdd-skin="my-skin"][data-color-scheme="light"] {  /* light */
  --rdd-sidebar-bg:                  #f6f8fa;
  --rdd-sidebar-tabs-bg:             #eaeef2;
  /* … the same variables, light values … */
}

Minimal overrides

You don't need to define all variables. A skin that only sets --rdd-accent-color, --rdd-bg-workspace, and --rdd-bg-panel is perfectly valid — the tints, the active tab icon and the sidebar's accent tokens all follow --rdd-accent-color by default, and everything else inherits from the library default.

CSS variable reference ​

Workspace & panels ​

VariableDark defaultDescription
--rdd-bg-primary#090b11Page background behind the workspace.
--rdd-bg-workspace#0f111aWorkspace canvas background.
--rdd-bg-panel#141722Background of docked panel areas.
--rdd-bg-tab-bar#0d0f16Tab strip background.
--rdd-border-colorrgba(255,255,255,0.08)Generic border color.
--rdd-border-panelrgba(255,255,255,0.08)Border between panels in the grid.

Text ​

VariableDark defaultDescription
--rdd-text-primary#f1f5f9Main body text.
--rdd-text-secondary#94a3b8Muted / secondary text.

Status and placeholders ​

VariableDark defaultDescription
--rdd-danger-color#dc3545 (light: #b91c1c)Errors the library shows itself: the border and text of the placeholder for a panel whose component isn't registered.
--rdd-placeholder-bgrgba(108,117,125,0.15)Fill of a minimized panel's taskbar thumbnail when it has no live preview.

Accent ​

VariableDark defaultDescription
--rdd-brand-accent(unset)Set by your app, never by a skin: replaces every skin's accent. See Brand your app.
--rdd-brand-on-accent(unset — #090b11; primary button in light mode #ffffff)Set by your app: text on a solid accent fill (the primary button, the active dock target).
--rdd-accent-colorvar(--rdd-brand-accent, #38bdf8) (light: #0066cc)Primary interactive color — tab indicators, active borders. Every tint of it in the library is a color-mix() of this variable.
--rdd-accent-glowthe accent at 15%Translucent version used for focus halos.
--rdd-focus-ring2px solid var(--rdd-accent-color)Outline drawn on a library control reached from the keyboard (:focus-visible). Read with that fallback rather than declared, so it follows the skin's (or your brand's) accent wherever it is drawn.
--rdd-context-menu-focus-ringvar(--rdd-focus-ring)Outline on a context-menu item that has keyboard focus.

Fonts ​

VariableDefaultDescription
--rdd-font-familythe skin's font (--rdd-skin-font-family), else 'Outfit', 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serifEvery piece of chrome: tabs, title bars, toolbar, sidebar, menus, flyouts, toasts, drawers, modals. Set it to brand the app.
--rdd-skin-font-familyper skin — see Built-in skinsA skin's own font. Set it in a skin, never --rdd-font-family.
--rdd-font-family-monoui-monospace, SFMono-Regular, Menlo, Consolas, monospacePlaceholders, the drag ghost and the other monospaced labels.

The library loads none of these fonts: each is a stack of system fonts, and a machine uses the first one it has. Load your own font yourself (see Your brand font), or change the stack. Set it on :root or <body> to reach everything: context menus, the toolbar flyout and toasts render straight into <body>, so a stack set on a wrapper around the workspace reaches only the chrome inside that wrapper.

To use your page's own font everywhere, set --rdd-font-family: inherit on :root, or --rdd-font-family: initial on <body>. (inherit works only on :root: a custom property set to inherit copies its parent's value, so below :root it just copies the skin's stack down. initial leaves the property without a value, and the chrome then inherits the page's font — on a wrapper, again, only inside it.) Panel content inherits the workspace font; your own form controls inside a panel keep the browser's default font, as they would anywhere else.

Tabs ​

VariableDark defaultDescription
--rdd-bg-tab-inactive#0c0d12Background of unfocused tabs.
--rdd-bg-tab-hover#171a22Tab background on hover.
--rdd-text-tab-inactive#858b99Label color of unfocused tabs.
--rdd-text-tab-active#ffffffLabel color of the active tab.
--rdd-text-tab-hover#e2e8f0Label color of hovered tabs.
--rdd-tab-indicator-focusedvar(--rdd-accent-color)Color of the active-tab indicator bar when the leaf has focus.
--rdd-tab-indicator-unfocusedrgba(255,255,255,0.3)Indicator bar color when the leaf is not focused.
--rdd-tab-bg-active-focusedvar(--rdd-bg-panel)Active tab background when the leaf has focus.
--rdd-tab-bg-active-unfocusedrgba(20,23,34,0.55)Active tab background when the leaf is unfocused.
--rdd-tab-text-active-focusedvar(--rdd-text-tab-active)Active tab text color when focused.
--rdd-tab-text-active-unfocusedrgba(255,255,255,0.65)Active tab text color when unfocused.

Control buttons ​

VariableDark defaultDescription
--rdd-close-btn-color#858b99Default color of close/minimize/maximize icons.
--rdd-close-btn-active-color#e2e8f0Icon color when the button is active.
--rdd-close-btn-hover-bgrgba(255,255,255,0.12)Button background on hover.
--rdd-close-btn-hover-color#ffffffIcon color on hover.
--rdd-custom-btn-bgrgba(255,255,255,0.03)Background of custom action buttons (anchor, float, etc.).
--rdd-custom-btn-borderrgba(255,255,255,0.05)Border of custom action buttons.
--rdd-custom-btn-hover-bgrgba(255,255,255,0.12)Custom button background on hover.
--rdd-custom-btn-hover-color#ffffffCustom button icon color on hover.
--rdd-header-button-gap4pxSpacing between titlebar action buttons.

Floating windows ​

VariableDark defaultDescription
--rdd-window-bgrgba(20,22,28,0.85)Floating window background. Supports var(--rdd-window-opacity) for glassmorphic effects.
--rdd-window-borderrgba(255,255,255,0.08)Unfocused window border.
--rdd-window-border-focusedrgba(255,255,255,0.28)Focused window border.
--rdd-window-header-bgrgba(0,0,0,0.25)Titlebar background.
--rdd-window-text#f8f9faTitlebar text color.
--rdd-window-shadow0 16px 40px rgba(0,0,0,0.4)Unfocused window drop-shadow.
--rdd-window-shadow-focused0 24px 50px rgba(0,0,0,0.55)Focused window drop-shadow.

Resizer ​

VariableDark defaultDescription
--rdd-resizer-bgrgba(255,255,255,0.08)Split-pane drag handle color.

Taskbar ​

VariableDark defaultDescription
--rdd-taskbar-bgrgba(0,0,0,0.75)Taskbar strip background.
--rdd-taskbar-borderrgba(255,255,255,0.1)Taskbar top border.
--rdd-taskbar-nav-colorrgba(255,255,255,0.5)Navigation icon color in the taskbar.
--rdd-taskbar-item-bgrgba(15,23,42,0.6)Minimized-panel chip background.
--rdd-taskbar-item-hover-bgrgba(15,23,42,0.8)Chip background on hover.
--rdd-taskbar-item-borderrgba(255,255,255,0.08)Chip border.
--rdd-taskbar-item-textvar(--rdd-accent-color)Chip text / icon color.

Scrollbars ​

VariableDark defaultDescription
--rdd-scrollbar-thumbrgba(255,255,255,0.1)Scrollbar thumb color.
--rdd-scrollbar-thumb-hoverrgba(255,255,255,0.2)Scrollbar thumb on hover.
--rdd-scrollbar-trackrgba(255,255,255,0.01)Scrollbar track background.

Panel cards ​

VariableDark defaultDescription
--rdd-panel-card-bgrgba(0,0,0,0.2)Background of inner card elements inside panels.
--rdd-panel-card-borderrgba(255,255,255,0.1)Border of inner card elements.
--rdd-panel-textvar(--rdd-text-primary)Default text color inside panels.
--rdd-panel-title-colorvar(--rdd-accent-color)Title / heading accent inside panels.

RddSidebar component ​

The library defines these on :root (dark, the default) and on [data-color-scheme="light"]; some built-in skins override a few of them (the active-tab and accent ones). Override them in your skin with [data-rdd-skin="my-skin"] for dark and [data-rdd-skin="my-skin"][data-color-scheme="light"] for light.

VariableDark defaultDescription
--rdd-sidebar-bg#1e2024Sidebar drawer background.
--rdd-sidebar-tabs-bg#141619Sidebar icon strip background.
--rdd-sidebar-borderrgba(255,255,255,0.08)Drawer edge border.
--rdd-sidebar-card-bgrgba(255,255,255,0.03)Content card background in the drawer.
--rdd-sidebar-card-borderrgba(255,255,255,0.08)Content card border.
--rdd-sidebar-card-active-bgthe accent at 6%Selected/active card background.
--rdd-sidebar-card-active-borderthe accent at 30%Selected/active card border.
--rdd-sidebar-text-title#f8f9faPrimary text inside the drawer.
--rdd-sidebar-text-muted#8a90a0Secondary / muted text.
--rdd-sidebar-badge-bg#2d3139Badge pill background.
--rdd-sidebar-badge-text#b0b5c0Badge pill text.
--rdd-sidebar-btn-front-bordervar(--rdd-accent-color)Primary action button border.
--rdd-sidebar-btn-front-textvar(--rdd-accent-color)Primary action button text.
--rdd-sidebar-btn-front-bgtransparentPrimary action button background.
--rdd-sidebar-btn-front-hover-bgthe accent at 10%Primary action button hover background.
--rdd-tab-icon-activevar(--rdd-accent-color)Active tab icon color in the strip.
--rdd-tab-icon-inactive#9ea4b0Inactive tab icon color.
--rdd-tab-btn-active-bg#1e2024Active tab button background (merges with drawer).
--rdd-tab-btn-active-width100%Width of the active tab button. Chip skins (macos, slate) set 36px for a contained floating shape.
--rdd-tab-btn-active-radius0pxBorder-radius of the active tab button.
--rdd-tab-btn-active-shadownonebox-shadow on the active tab. Obsidian/Tokyo add an inset ambient glow.
--rdd-tab-btn-active-glownonefilter on the active tab. Obsidian/Tokyo add drop-shadow() for icon glow.
--rdd-tab-accent-bar-width3pxWidth of the sidebar edge accent bar. Set to 0px to suppress it.

Toolbar strip active state ​

VariableDefaultDescription
--rdd-toolbar-btn-radio-active-bgthe accent at 14%Background tint of the active radio/group toolbar button.
--rdd-toolbar-btn-active-shadownonebox-shadow on active toolbar buttons. Obsidian/Tokyo override with an inset glow.
--rdd-toolbar-btn-active-glownonefilter on active toolbar buttons. Obsidian/Tokyo add drop-shadow() for icon glow.
--rdd-toolbar-accent-bar-width3pxWidth of the toolbar edge accent bar. Set to 0px for chip-shaped skins.
--rdd-toolbar-separator-colorrgba(255,255,255,0.09)Separator line color between toolbar item groups.

Adding structural CSS ​

CSS variables control colors and shadows. If your skin needs structural changes (different tab shape, rounded windows, glassmorphic blur), add class selectors scoped to your skin:

css
[data-rdd-skin="my-skin"] .rdd-floating-window {
  border-radius: 10px;
  backdrop-filter: blur(16px);
}

[data-rdd-skin="my-skin"] .rdd-workspace-tab {
  border-radius: 4px;
  margin: 3px 2px;
}

Study the built-in skins in the library source (src/index.css) for examples — the macos skin's structural overrides are particularly comprehensive.

The chrome's fixed layout values — tab-bar height, close-button size, title widths, the sidebar strip's width — are in the stylesheet, not inline styles (since 7.1.2), so a rule of your own overrides them without !important. Import the library's stylesheet before your own; a rule with a class and a descendant (.my-app .rdd-close-tab-x) wins whatever the order. Only values that change as you use the workspace (sizes, positions, split ratios) are set inline.

See also ​

Released under the MIT License.