ryOS ryOS / Docs
GitHub Launch

Theme System

ryOS supports 4 themes emulating classic operating systems with comprehensive CSS variable systems, centralized metadata helpers, and platform-specific UI behaviors.

Available Themes

Theme IDNamePlatformEraKey Elements
macosxAquaMac OS X~2001Glossy buttons, traffic lights, pinstripe, dock
system7System 7Classic Mac~1991Black & white, dotted titlebar pattern
xpLunaWindows XP~2001Blue chrome, rounded corners, taskbar
win98ClassicWindows 98~19983D beveled face, blue gradient titlebar

Theme Architecture

flowchart TD
    A[User Selection] --> B[Theme Store]
    B --> C["data-os-theme attribute"]
    C --> D[CSS Variables Resolve]
    D --> E[Window Frame]
    D --> F[Menu Bar]
    D --> G[Dock/Taskbar]
    D --> H[UI Components]
    
    B --> I{Windows Theme?}
    I -->|Yes| J[Load Legacy CSS]
    I -->|No| K[Remove Legacy CSS]

Theme Metadata

Each theme provides metadata for layout decisions:

interface ThemeMetadata {
  /** Whether this is a Windows-style theme (XP, 98) */
  isWindows: boolean;
  /** Whether this is a macOS-style theme (macOS X, System 7) */
  isMac: boolean;
  /** Whether the theme has a dock (bottom app launcher) */
  hasDock: boolean;
  /** Whether the theme has a taskbar (Windows-style bottom bar) */
  hasTaskbar: boolean;
  /** Whether the theme has a top menu bar */
  hasMenuBar: boolean;
  /** Title bar controls position: "left" (macOS) or "right" (Windows) */
  titleBarControlsPosition: "left" | "right";
  /** Default menu bar height in pixels */
  menuBarHeight: number;
  /** Taskbar height in pixels (0 if no taskbar) */
  taskbarHeight: number;
  /** Base dock height before scaling (0 if no dock) */
  baseDockHeight: number;
}

Platform Comparison

PropertySystem 7macOS XWindows XPWindows 98
isWindowsfalsefalsetruetrue
isMactruetruefalsefalse
hasDockfalsetruefalsefalse
hasTaskbarfalsefalsetruetrue
hasMenuBartruetruefalsefalse
titleBarControlsPosition"left""left""right""right"
menuBarHeight30px25px0px0px
taskbarHeight0px0px30px30px
baseDockHeight0px56px0px0px

Theme Definition Interface

interface OsTheme {
  id: OsThemeId;
  name: string;
  metadata: ThemeMetadata;
  
  fonts: {
    ui: string;
    mono?: string;
  };
  
  colors: {
    windowBg: string;
    menubarBg: string;
    menubarBorder: string;
    windowBorder: string;
    windowBorderInactive?: string;
    
    titleBar: {
      activeBg: string;
      inactiveBg: string;
      text: string;
      inactiveText: string;
      border?: string;
      borderInactive?: string;
      borderBottom?: string;
      pattern?: string;
    };
    
    button: {
      face: string;
      highlight: string;
      shadow: string;
      activeFace?: string;
    };
    
    trafficLights?: {
      close: string;
      closeHover?: string;
      minimize: string;
      minimizeHover?: string;
      maximize: string;
      maximizeHover?: string;
    };
    
    selection: {
      bg: string;
      text: string;
    };
    
    text: {
      primary: string;
      secondary: string;
      disabled: string;
    };
  };
  
  metrics: {
    borderWidth: string;
    radius: string;
    titleBarHeight: string;
    titleBarRadius?: string;
    windowShadow: string;
  };
  
  assets?: {
    closeButton?: string;
    maximizeButton?: string;
    minimizeButton?: string;
  };
  
  wallpaperDefaults?: {
    photo?: string;
    tile?: string;
  };
}

CSS Variable System

Themes are applied via the data-os-theme attribute on the document root:

:root[data-os-theme="macosx"] {
  /* Typography */
  --os-font-ui: "LucidaGrande", "Lucida Grande", ...;
  --os-font-mono: Monaco, Menlo, monospace;
  
  /* Window */
  --os-color-window-bg: #ececec;
  --os-color-window-border: rgba(0, 0, 0, 0.4);
  --os-window-shadow: 0 3px 10px rgba(0,0,0,0.3);
  
  /* Menu Bar */
  --os-color-menubar-bg: linear-gradient(to bottom, #f8f8f8, #d4d4d4);
  --os-color-menubar-border: rgba(0, 0, 0, 0.44);
  --os-color-menubar-text: #000000;
  
  /* Title Bar */
  --os-color-titlebar-active-bg: linear-gradient(to bottom, #e8e8e8, #d0d0d0);
  --os-color-titlebar-inactive-bg: #f6f6f6;
  --os-color-titlebar-text: #000000;
  
  /* Selection */
  --os-color-selection-bg: rgba(39, 101, 202, 0.88);
  --os-color-selection-text: #ffffff;
  
  /* Metrics */
  --os-metrics-border-width: 1px;
  --os-metrics-radius: 0.45rem;
  --os-metrics-titlebar-height: 22px;
  --os-metrics-menubar-height: 25px;
  
  /* Textures */
  --os-pinstripe-window: repeating-linear-gradient(...);
  --os-pinstripe-titlebar: linear-gradient(...);
}

Variable Categories

CategoryVariablesDescription
Typography--os-font-ui, --os-font-monoUI and monospace fonts
Window--os-color-window-bg, --os-window-shadowWindow appearance
Menu Bar--os-color-menubar-bg, --os-color-menubar-textMenu bar styling
Title Bar--os-color-titlebar-Window title bar
Selection--os-color-selection-bg, --os-color-selection-textSelected items
Text--os-color-text-primary, --os-color-text-secondaryText colors
Metrics--os-metrics-radius, --os-metrics-border-widthDimensions
Textures--os-pinstripe-, --os-texture-*Background patterns

Theme State Management

// src/stores/useThemeStore.ts
export const useThemeStore = create<ThemeState>((set) => ({
  current: "macosx",
  
  setTheme: (theme) => {
    set({ current: theme });
    localStorage.setItem("ryos:theme", theme);
    localStorage.removeItem("os_theme"); // legacy cleanup
    document.documentElement.dataset.osTheme = theme;
    ensureLegacyCss(theme);
  },
  
  hydrate: () => {
    const saved =
      (localStorage.getItem("ryos:theme") as OsThemeId | null) ??
      (localStorage.getItem("os_theme") as OsThemeId | null); // legacy fallback
    const theme = saved || "macosx";
    set({ current: theme });
    document.documentElement.dataset.osTheme = theme;
    ensureLegacyCss(theme);
  },
}));

Legacy CSS Loading

Windows themes dynamically load additional CSS (xp-custom.css / 98-custom.css):

async function ensureLegacyCss(theme: OsThemeId) {
  // Only XP and Win98 use legacy CSS
  if (theme !== "xp" && theme !== "win98") {
    if (legacyCssLink) {
      legacyCssLink.remove();
      legacyCssLink = null;
    }
    return;
  }

  const href = theme === "xp" 
    ? "/css/xp-custom.css" 
    : "/css/98-custom.css";

  const link = document.createElement("link");
  link.rel = "stylesheet";
  link.href = href;
  
  if (legacyCssLink) {
    legacyCssLink.replaceWith(link);
  } else {
    document.head.appendChild(link);
  }
  legacyCssLink = link;
}

Theme Switching Flow

sequenceDiagram
    participant User
    participant ControlPanel
    participant ThemeStore
    participant DOM
    participant CSS
    
    User->>ControlPanel: Select theme
    ControlPanel->>ThemeStore: setTheme("xp")
    ThemeStore->>ThemeStore: Update state
    ThemeStore->>localStorage: Save preference
    ThemeStore->>DOM: Set data-os-theme="xp"
    ThemeStore->>DOM: Load xp-custom.css
    DOM->>CSS: CSS selectors match
    CSS->>CSS: Variables resolve
    Note over DOM,CSS: Components re-render with new styles

Platform-Specific UI

macOS Themes (System 7 & macOS X)

graph TD
    subgraph Mac["Mac Theme Layout"]
        MB[Menu Bar - Top]
        WIN[Window with Traffic Lights]
        DOCK[Dock - Bottom
macOS X only] end MB --> |Apple Menu| AM[Apple Logo Menu] MB --> |App Menu| APP[Application Menu] WIN --> |Controls - Left| TL[Traffic Light Buttons]
Characteristics:
  • Top menu bar with Apple logo
  • Traffic light buttons (close/minimize/zoom) on LEFT
  • Dock (macOS X only)
  • Aqua button styling with gradients
  • Pinstripe textures

Windows Themes (XP & 98)

graph TD
    subgraph Win["Windows Theme Layout"]
        WIN[Window with Title Bar Controls]
        TB[Taskbar - Bottom]
    end
    
    WIN --> |Controls - Right| CTRL[Min/Max/Close]
    TB --> |Start| SM[Start Menu]
    TB --> |Tasks| TASKS[Running Apps]
    TB --> |Tray| TRAY[System Tray]
Characteristics:
  • Bottom taskbar with Start button
  • Title bar buttons on RIGHT
  • Legacy CSS from xp.css/98.css
  • 3D border effects

Theme-Aware Component Pattern

Components use the theme store to adapt their rendering:

import { useThemeStore } from "@/stores/useThemeStore";

function MyComponent() {
  const currentTheme = useThemeStore((state) => state.current);
  const isXpTheme = currentTheme === "xp" || currentTheme === "win98";
  const isMacTheme = currentTheme === "macosx";

  if (isMacTheme) {
    return <AquaButton>Click me</AquaButton>;
  }
  
  if (isXpTheme) {
    return <button className="button">Click me</button>;
  }
  
  return <ClassicButton>Click me</ClassicButton>;
}

Theme Helper Functions

// Check theme platform
export function isWindowsTheme(theme: OsThemeId): boolean {
  return theme === "xp" || theme === "win98";
}

export function isMacTheme(theme: OsThemeId): boolean {
  return theme === "macosx" || theme === "system7";
}

// Get theme metadata
export function getThemeMetadata(theme: OsThemeId): ThemeMetadata {
  return themes[theme].metadata;
}

These helpers are now the preferred way to branch on platform behavior, instead of scattered inline theme === "xp" || theme === "win98" checks.

Theme-Aware Icon System

Icons are resolved based on the current theme:

// src/utils/icons.ts
export function pickIconPath(
  name: string,
  { theme, fallbackTheme = "default" }: GetIconPathOptions = {}
): string {
  // No theme: use fallback
  if (!theme) {
    return `/icons/${fallbackTheme}/${name}`;
  }
  
  // Check if theme has this icon
  if (manifest.themes[theme]?.includes(name)) {
    return `/icons/${theme}/${name}`;
  }
  
  // Fallback to default
  return `/icons/${fallbackTheme}/${name}`;
}
Directory Structure:
public/icons/
├── default/          # Fallback icons
├── macosx/           # macOS X specific
├── system7/          # System 7 specific
├── xp/               # Windows XP specific
└── win98/            # Windows 98 specific

Aqua Styling (macOS X)

Aqua Buttons

.aqua-button {
  height: 28px;
  padding: 0 16px;
  border-radius: 14px;
  background: linear-gradient(
    to bottom,
    #fff 0%,
    #f0f0f0 50%,
    #e0e0e0 50%,
    #f5f5f5 100%
  );
  box-shadow: 
    inset 0 1px 0 rgba(255,255,255,0.8),
    0 1px 2px rgba(0,0,0,0.2);
}

.aqua-button.primary {
  background: linear-gradient(
    to bottom,
    #6cb3fa 0%,
    #1f7cf5 50%,
    #1066e5 50%,
    #58b0fa 100%
  );
  color: white;
}

/* Top shine effect */
.aqua-button:before {
  content: "";
  position: absolute;
  top: 2px;
  left: 10%;
  right: 10%;
  height: 40%;
  background: linear-gradient(
    to bottom,
    rgba(255,255,255,0.8),
    rgba(255,255,255,0)
  );
  border-radius: 50%;
}

Traffic Light Buttons

.traffic-light {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 1px solid rgba(0,0,0,0.2);
}

.traffic-light.close {
  background: linear-gradient(#ff6059, #e33e32);
}

.traffic-light.minimize {
  background: linear-gradient(#ffbd2e, #e5a000);
}

.traffic-light.maximize {
  background: linear-gradient(#29c941, #19a82e);
}

Pinstripe Textures

--os-pinstripe-window: repeating-linear-gradient(
  0deg,
  transparent 0px,
  transparent 1.5px,
  rgba(255, 255, 255, 0.85) 1.5px,
  rgba(255, 255, 255, 0.85) 4px
),
linear-gradient(to bottom, #ececec 0%, #ececec 100%);

Inactive Window Handling

Each theme handles inactive windows differently:

/* System 7 - Gray selection */
:root[data-os-theme="system7"] .window:not(.is-foreground) {
  --os-color-selection-bg: #cfcfcf;
  --os-color-selection-text: #000000;
}

/* macOS X - Lighter border */
:root[data-os-theme="macosx"] .window:not(.is-foreground) {
  --os-color-window-border: rgba(0, 0, 0, 0.08);
  --os-color-titlebar-border: rgba(0, 0, 0, 0.2);
}

/* Windows - Standard inactive styling */
:root[data-os-theme="xp"] .window:not(.is-foreground) .title-bar {
  background: linear-gradient(#8ca8ce, #a0b8da);
}

Wallpaper Defaults

Each theme has default wallpaper suggestions:

ThemePhoto DefaultTile Default
macOS XAqua Blue-
System 7-Desktop Pattern
XPBliss-
Win98-Teal

Related Documentation