Design and style UI components for ryOS following the 4 OS themes (System 7, macOS Aqua, Windows XP, Windows 98)...
docs/3.3.1-theme-architecture.mddocs/3.3-theme-system.mdsrc/styles/themes/tokens.csssrc/styles/themes.csstailwind.config.jssrc/themes/, src/stores/useThemeStore.ts, src/hooks/useThemeFlags.tsVisual values belong in CSS --os-* tokens. TypeScript theme files hold metadata, platform behavior, dark-mode support flags, and wallpaper defaults; do not duplicate palettes in TS or component code.
| Theme | ID | Platform | Key traits |
|---|---|---|---|
| macOS Aqua | macosx |
mac / aqua |
Glossy controls, traffic lights, dock, pinstripe or Aqua Glass, light/dark, accents |
| System 7 | system7 |
mac / system7 |
Black/white, square corners, Chicago-style type, dotted titlebars, accents |
| Windows XP | xp |
windows |
Luna blue chrome, rounded window borders, taskbar, legacy XP CSS |
| Windows 98 | win98 |
windows |
Gray bevels, square corners, classic taskbar, legacy 98 CSS |
Default theme is macosx; default Aqua material is glass.
useThemeStore applies attributes to <html>. Prefer targeting these through tokens and Tailwind variants.
| Attribute / class | Meaning |
|---|---|
data-os-theme |
Exact theme id: system7, macosx, xp, win98 |
data-os-platform |
Shared platform bucket: mac or windows |
data-os-mac-chrome |
Mac chrome variant: aqua or system7; absent for Windows |
data-os-color-scheme="dark" |
Present only when the active theme supports dark mode and dark is enabled |
data-os-aqua-material="glass" |
Present only for macOS Aqua Glass |
data-os-accent |
Present for non-default Mac chrome accents |
data-os-system-font |
Present for debug font overrides |
.dark |
Mirrors Aqua dark mode for Tailwind dark:* compatibility |
src/styles/themes.css imports theme CSS in this order:
tokens.css - defaults, per-theme token blocks, [data-selected="true"], z-index scale.platform.css - rules shared by mac or windows platform buckets.containment.css - reduced motion and third-party/app isolation.aqua.css - Aqua structural chrome, .aqua-button, brushed metal, typography.windows.css - Windows structural landing rules.dark-aqua.css - Aqua dark tokens and structural overrides.aqua-glass.css - Aqua Glass overrides, imported after dark Aqua.control-panels-mac.css / control-panels-themed.css - Control Panels skins.Windows themes also load /css/xp-custom.css or /css/98-custom.css dynamically. When a rule applies to both XP and Win98, use data-os-platform="windows" instead of duplicated exact-theme selectors.
Use token-backed Tailwind utilities first:
className="bg-os-window-bg border-os-window rounded-os shadow-os-window"
className="font-os-ui text-os-text-primary"
className="bg-os-panel-bg border-[length:var(--os-metrics-border-width)]"
className="bg-os-input-bg border-os-input-border focus:border-os-input-focusBorder"
className="text-os-link bg-os-selection-bg text-os-selection-text"
className="h-os-titlebar h-os-menubar z-menubar"
Core token groups:
--os-font-ui, --os-font-mono--os-color-window-bg, --os-color-panel-bg, --os-color-input-bg--os-color-window-border, --os-color-separator, --os-color-input-border--os-color-text-primary, --os-color-text-secondary, --os-color-text-disabled, --os-color-link--os-color-menubar-*, --os-color-titlebar-*, --os-color-button-*--os-color-selection-*, --os-color-selection-glow, --os-color-selection-ring-gap--os-metrics-*, --os-window-shadow--os-color-traffic-light-*, --os-pinstripe-*, --os-texture-*, --os-typography-*--z-base, --z-dialog, --z-menubar, --z-dropdown, --z-spotlightShadcn HSL variables (--background, --primary, etc.) still exist for generic UI primitives. For OS chrome and app surfaces, prefer --os-* tokens and bg-os-* / text-os-* utilities.
Use root-attribute variants for small static visual differences:
className={cn(
"bg-os-window-bg text-os-text-primary",
"os-windows:border-os os-mac-aqua:rounded-os",
"os-mac-system7:rounded-none os-theme-win98:shadow-none",
"os-dark:bg-os-window-bg os-mac-aqua-dark:text-os-text-primary"
)}
Available variants:
os-mac:, os-windows:os-mac-aqua:, os-mac-system7:os-theme-system7:, os-theme-macosx:, os-theme-xp:, os-theme-win98:os-dark:, os-mac-aqua-dark:, os-theme-<id>-dark:Prefer CSS variants and tokens over React theme branches when the DOM and behavior do not change.
Use useThemeFlags() for component decisions:
const {
currentTheme,
osPlatform,
macChrome,
metadata,
isWindowsTheme,
isMacTheme,
isMacOSTheme,
isSystem7Theme,
isWinXp,
isWin98,
isClassicTheme,
isAquaMenuChrome,
isMacAquaChrome,
supportsDarkMode,
isDarkMode,
darkModePreference,
supportsAccent,
accent,
aquaMaterial,
isAquaGlass,
} = useThemeFlags();
Use React branches only when structure, behavior, assets, layout math, or app logic differs. For non-React code, use useThemeStore.getState() or helpers from @/themes such as getOsPlatform, getOsMacChrome, isWindowsTheme, isMacTheme, isThemeWinXp, and isThemeWin98.
Prefer shared primitives before adding new four-way class branches:
import {
osCardClassName,
osDrawerSurfaceClassName,
osToolbarSurfaceClassName,
osAppSidebarSurfaceClassName,
osSeparatorBorderClassName,
osSubtleIconButtonClassName,
windowsBevelClassName,
} from "@/components/shared/osThemePrimitives";
Pass isAquaGlass when a primitive supports it. Use windowsBevelClassName("raised" | "sunken") instead of hand-writing Win98 bevel borders.
When choosing app icons, toolbar glyphs, file-type art, dialog icons, devices, folders, or other OS-flavored imagery, search the repo icon libraries before drawing new assets or using external sources.
Active theme icons live under public/icons/<theme>/... and are resolved through public/icons/manifest.json by helpers such as pickIconPath, resolveIconLegacyAware, and useIconPath. Historical source libraries live outside active theme resolution:
public/resources/macos-icon-catalogs/{panther,tiger}/catalog.mdpublic/resources/windows-icon-catalogs/{win98,xp}/catalog.mdUse this order:
public/icons/default, then matching theme variants in public/icons/macosx, public/icons/win98, or public/icons/xp.applications, system-preferences, control-panels, dialog-ui-assets, folders, devices, and file-types.public/icons/<theme>/... only when it should participate in active UI rendering; keep public/resources/... as the source catalog, not as runtime app metadata.bun run generate:icons after changing active public/icons files.default assets first, then theme-specific variants where they materially improve the UI.Useful searches:
rg -i "calculator|paint|printer|warning|folder" public/resources/*-icon-catalogs
rg -i "\"themes\"|\"macosx\"|\"win98\"|\"xp\"" public/icons/manifest.json
import { Button } from "@/components/ui/button";
<Button variant="default">Standard</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="retro">Retro</Button>
<Button variant="aqua">Aqua</Button>
Button maps variants to .aqua-button on macOS Aqua and legacy .button on Windows.
<button className="aqua-button">Default</button>
<button className="aqua-button primary">Primary</button>
<button className="aqua-button secondary">Secondary</button>
<button className="aqua-button orange">Orange</button>
<div
className={cn(
"rounded-os bg-os-window-bg text-os-text-primary shadow-os-window",
"border-[length:var(--os-metrics-border-width)] border-os-window",
"os-theme-win98:shadow-none"
)}
>
Use the built-in selection utility when possible:
<div data-selected={isSelected ? "true" : undefined}>Song</div>
macosx; it is driven by data-os-aqua-material="glass" and src/styles/themes/aqua-glass.css.bg-white/80 backdrop-blur-* unless the surface is intentionally outside the OS material system.macosx currently supports dark mode. Use --os-* tokens, os-dark:, or os-mac-aqua-dark:; branch on isDarkMode only for behavioral or structural differences.macosx, system7) support accents. The default accent is wallpaper; default means "System" and clears inline overrides so stylesheet tokens win.--os-color-selection-*, --os-color-link, focus-ring tokens, or Tailwind bg-os-selection-bg / text-os-link.WindowFrame supports per-window materials:
| Material | Use case |
|---|---|
default |
Standard opaque windows |
transparent |
Semi-transparent app windows such as media surfaces |
notitlebar |
Immersive windows with floating or hover chrome |
brushedmetal |
Classic Mac brushed-metal apps |
Global Aqua Glass is separate from WindowFrame material. Regular Aqua windows receive glass classes when the global material is glass; brushed-metal windows keep brushed-metal semantics and are adjusted by CSS.
font-os-ui and font-os-mono; avoid theme-specific font utility names.WindowFrame content has .window-body, which consumes --os-typography-window.WindowFrame, use OS_SHELL_TEXT_SCALE_CLASS from @/lib/themeChrome.OS_NATIVE_CHROME_SKIP_CLASS on an ancestor.prose-textedit typography variant for TextEdit-like rich content so Aqua dark mode stays readable.--os-* tokens.currentTheme === "xp" || currentTheme === "win98" branches for shared Windows styling; use platform attributes, variants, or helpers.src/components/shared/osThemePrimitives.ts and existing app patterns.cn() for conditional class merging.--os-* tokens, token-backed Tailwind utilities, and os-*: variants.useThemeFlags() or @/themes helpers only when structure or behavior changes.macosx surfaces.