Skip to content

Component Registry

The component registry pattern allows you to define window components once and open them by ID. This makes window state fully serializable—perfect for persistence in localStorage or URL state.

Setting Up with defineWindows

The recommended approach is to use defineWindows for unified configuration:

import { defineWindows } from 'glazier/server';
// Define all windows in one place
const windows = defineWindows({
settings: {
title: 'Settings',
defaultPosition: { x: 100, y: 100 },
defaultSize: { width: 350, height: 400 },
path: '/settings',
icon: {
label: 'Settings',
iconKey: 'settings',
position: { x: 20, y: 20 },
},
},
terminal: {
title: 'Terminal',
defaultPosition: { x: 150, y: 150 },
defaultSize: { width: 600, height: 400 },
path: '/terminal',
defaultProps: { initialPath: '/home/user' },
},
notes: {
title: 'Notes',
defaultPosition: { x: 200, y: 200 },
defaultSize: { width: 400, height: 500 },
path: '/notes',
},
});
export type WindowId = typeof windows.ids[number];

Creating a Type-Safe Registry

Use createRegistry to ensure all window IDs have corresponding components:

import { createRegistry } from 'glazier';
// Define your window components
function SettingsPanel({ windowId }: { windowId: string }) {
return (
<WindowFrame windowId={windowId}>
<TitleBar><Title /><WindowControls /></TitleBar>
<Content><h3>Settings</h3>{/* Settings UI */}</Content>
<ResizeHandles windowId={windowId} />
</WindowFrame>
);
}
function TerminalApp({ windowId, initialPath }: { windowId: string; initialPath?: string }) {
return (
<WindowFrame windowId={windowId}>
<TitleBar><Title /><WindowControls /></TitleBar>
<Content>
<code>$ cd {initialPath ?? '/home/user'}</code>
</Content>
<ResizeHandles windowId={windowId} />
</WindowFrame>
);
}
function NotesApp({ windowId }: { windowId: string }) {
return (
<WindowFrame windowId={windowId}>
<TitleBar><Title /><WindowControls /></TitleBar>
<Content>
<textarea placeholder="Start typing..." />
</Content>
<ResizeHandles windowId={windowId} />
</WindowFrame>
);
}
// Create type-safe registry - TypeScript ensures all IDs are covered
const registry = createRegistry(windows.ids, {
settings: SettingsPanel,
terminal: TerminalApp,
notes: NotesApp,
});

Using the Registry

Pass the registry to WindowManagerProvider:

import { WindowManagerProvider, Desktop, Window } from 'glazier';
function App() {
const containerRef = useRef<HTMLDivElement>(null);
return (
<WindowManagerProvider
boundsRef={containerRef}
registry={registry}
defaultWindows={[windows.getWindowState('settings')]}
defaultIcons={windows.getIconConfigs()}
>
<div ref={containerRef} className="h-screen relative">
<Desktop>
{({ windowId, component: Component, componentProps }) => (
<Window id={windowId} className="rounded-lg shadow-xl">
<Component windowId={windowId} {...componentProps} />
</Window>
)}
</Desktop>
</div>
</WindowManagerProvider>
);
}

Opening Windows by Component ID

Use openWindow with the window state from defineWindows:

const { openWindow } = useWindowManager();
function launchApp(componentId: WindowId) {
// Get default state from defineWindows
const state = windows.getWindowState(componentId);
openWindow(state);
}
// Or with custom overrides
function launchTerminal(initialPath: string) {
openWindow({
...windows.getWindowState('terminal'),
id: `terminal-${Date.now()}`, // Unique ID for multiple instances
componentProps: { initialPath },
});
}

URL Routing

Sync window focus with browser URL using createBrowserAdapter:

import { createBrowserAdapter } from 'glazier';
const routingAdapter = createBrowserAdapter();
const pathMap = windows.getPathMap();
<WindowManagerProvider
registry={registry}
onFocusChange={(windowId) => {
if (windowId) {
const path = pathMap[windowId as WindowId];
if (path) routingAdapter.navigate(path);
}
}}
>

Server-Side Rendering / Static Generation

For SSR/SSG frameworks like Next.js or Astro, import defineWindows from glazier/server:

lib/windowConfigs.ts
import { defineWindows } from 'glazier/server'; // Server-safe import
export const windows = defineWindows({
home: { title: 'Home', path: '/', ... },
about: { title: 'About', path: '/about', ... },
});
// Use in server components
export const validSlugs = windows.getValidSlugs();
// pages/[slug].tsx
export function getStaticPaths() {
return validSlugs.map(slug => ({ params: { slug } }));
}

Serializable State

Because windows reference components by ID, the state is JSON-serializable:

// Window state is fully serializable
const windowState = {
id: 'terminal-123',
title: 'Terminal',
componentId: 'terminal',
componentProps: { initialPath: '/home/user' },
position: { x: 100, y: 100 },
size: { width: 600, height: 400 },
zIndex: 1,
displayState: 'normal',
};
// Save to localStorage
localStorage.setItem('windows', JSON.stringify(state.windows));
// Restore on load
const saved = JSON.parse(localStorage.getItem('windows') || '[]');
<WindowManagerProvider defaultWindows={saved} registry={registry}>

Complete Example

import { useRef, useState } from 'react';
import {
WindowManagerProvider,
Desktop,
Window,
Taskbar,
SnapPreviewOverlay,
WindowFrame,
TitleBar,
Title,
WindowControls,
Content,
ResizeHandles,
createRegistry,
createBrowserAdapter,
} from 'glazier';
import { defineWindows } from 'glazier/server';
// Configuration
const windows = defineWindows({
settings: {
title: 'Settings',
defaultPosition: { x: 50, y: 80 },
defaultSize: { width: 300, height: 250 },
path: '/settings',
icon: { label: 'Settings', position: { x: 20, y: 20 } },
},
terminal: {
title: 'Terminal',
defaultPosition: { x: 100, y: 120 },
defaultSize: { width: 600, height: 400 },
path: '/terminal',
},
});
// Registry
const registry = createRegistry(windows.ids, {
settings: SettingsPanel,
terminal: TerminalApp,
});
// Routing
const routingAdapter = createBrowserAdapter();
const pathMap = windows.getPathMap();
function App() {
const containerRef = useRef<HTMLDivElement>(null);
const [snapZone, setSnapZone] = useState<'left' | 'right' | null>(null);
return (
<WindowManagerProvider
boundsRef={containerRef}
registry={registry}
defaultWindows={[windows.getWindowState('settings')]}
defaultIcons={windows.getIconConfigs()}
onFocusChange={(windowId) => {
if (windowId) {
const path = pathMap[windowId as typeof windows.ids[number]];
if (path) routingAdapter.navigate(path);
}
}}
>
<div ref={containerRef} className="h-screen relative">
<Desktop>
{({ windowId, component: Component, componentProps }) => (
<Window id={windowId} className="rounded-lg shadow-xl bg-slate-800">
<Component
windowId={windowId}
onSnapZoneChange={setSnapZone}
{...componentProps}
/>
</Window>
)}
</Desktop>
<SnapPreviewOverlay zone={snapZone} />
<MyTaskbar />
</div>
</WindowManagerProvider>
);
}