ryOS UI Design & Styling
Canonical References
- Implementation spec:
docs/3.3.1-theme-architecture.md
- User-facing overview:
docs/3.3-theme-system.md
- Token source of truth:
src/styles/themes/tokens.css
- Theme CSS import order:
src/styles/themes.css
- Tailwind mappings and variants:
tailwind.config.js
- Theme metadata/state:
src/themes/, src/stores/useThemeStore.ts, src/hooks/useThemeFlags.ts
Visual 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.
Supported Themes
| 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 |
Default theme is macosx; default Aqua material is glass.
Root Attributes
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: or ; absent for Windows |
CSS Layers
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.
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.
Tokens and Utilities
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:
- Fonts:
--os-font-ui, --os-font-mono
- Surfaces:
--os-color-window-bg, --os-color-panel-bg, --os-color-input-bg
- Borders/separators:
--os-color-window-border, --os-color-separator, --os-color-input-border
- Text:
--os-color-text-primary, --os-color-text-secondary, --os-color-text-disabled,
Shadcn 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.
Tailwind Variants
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.
Theme State and Flags
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.
Shared Surface Primitives
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.
Icon and Asset Sourcing
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:
- Mac OS X Panther/Tiger catalogs:
public/resources/macos-icon-catalogs/{panther,tiger}/catalog.md
- Windows 98/XP catalogs:
public/resources/windows-icon-catalogs/{win98,xp}/catalog.md
Use this order:
- Prefer an existing logical icon in
public/icons/default, then matching theme variants in public/icons/macosx, public/icons/win98, or public/icons/xp.
- If no active icon fits, search the resource catalogs by concept and source era-appropriate art from categories such as
applications, system-preferences, control-panels, dialog-ui-assets, folders, devices, and file-types.
Useful searches:
rg -i "calculator|paint|printer|warning|folder" public/resources/*-icon-catalogs
rg -i "\"themes\"|\"macosx\"|\"win98\"|\"xp\"" public/icons/manifest.json
Component Patterns
Button
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.
Aqua CSS Buttons
<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>
Tokenized Panel
<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"
)}
>
Selected Rows
Use the built-in selection utility when possible:
<div data-selected={isSelected ? "true" : undefined}>Song</div>
Aqua Glass, Dark Mode, and Accents
- Aqua Glass is the default material for
macosx; it is driven by data-os-aqua-material="glass" and src/styles/themes/aqua-glass.css.
- Do not build ad hoc glass with generic
bg-white/80 backdrop-blur-* unless the surface is intentionally outside the OS material system.
- Only
macosx currently supports dark mode. Use --os-* tokens, os-dark:, or os-mac-aqua-dark:; branch on isDarkMode only for behavioral or structural differences.
- Mac chromes (
macosx, ) support accents. The default accent is ; means "System" and clears inline overrides so stylesheet tokens win.
Window Materials
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.
Typography
- Use
font-os-ui and font-os-mono; avoid theme-specific font utility names.
WindowFrame content has .window-body, which consumes --os-typography-window.
- For shell or portaled copy outside a
WindowFrame, use OS_SHELL_TEXT_SCALE_CLASS from @/lib/themeChrome.
- For custom native chrome that must avoid Aqua global typography selectors, put
OS_NATIVE_CHROME_SKIP_CLASS on an ancestor.
- Use the
prose-textedit typography variant for TextEdit-like rich content so Aqua dark mode stays readable.
Anti-Patterns
- Do not copy hex values from old examples; add or consume
--os-* tokens.
- Do not make
currentTheme === "xp" || currentTheme === "win98" branches for shared Windows styling; use platform attributes, variants, or helpers.
- Do not use React branches for static colors, borders, radii, shadows, or text colors.
- Do not create one-off glassmorphism when Aqua Glass tokens and CSS apply.
- Do not add new theme-specific components before checking
src/components/shared/osThemePrimitives.ts and existing app patterns.
Checklist
- Search for an existing primitive or app pattern before styling from scratch.
- Use
cn() for conditional class merging.
- Prefer
--os-* tokens, token-backed Tailwind utilities, and os-*: variants.
- Use
useThemeFlags() or @/themes helpers only when structure or behavior changes.
- Consider Aqua Glass, Aqua dark mode, accents, inactive-window selection, and Windows legacy CSS.
- Test styled components across all four themes; include Aqua Glass and Aqua dark mode when touching
macosx surfaces.