Skip to content

Desktop Icons

Glazier supports desktop icons that can be dragged, selected, and used to launch windows.

Setup with defineWindows

The recommended approach is to define icons alongside windows using defineWindows:

import { defineWindows } from 'glazier/server';
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',
icon: {
label: 'Terminal',
iconKey: 'terminal',
position: { x: 20, y: 120 },
},
defaultProps: { initialPath: '/home/user' },
},
});
// Use in provider
<WindowManagerProvider
registry={registry}
defaultIcons={windows.getIconConfigs()}
>

Using useIconLauncher Hook

The useIconLauncher hook simplifies the “open or focus” pattern:

import { DesktopIconGrid, useIconLauncher } from 'glazier';
function DesktopIcon({ iconId, iconState, dragProps, wasDragged, onSelect }) {
const { launchProps, isWindowOpen } = useIconLauncher({ iconId });
return (
<div
{...dragProps}
{...launchProps}
onClick={(e) => {
e.stopPropagation();
if (!wasDragged) onSelect();
}}
className={isWindowOpen ? 'icon-active' : ''}
>
<IconImage icon={iconState.icon} active={isWindowOpen} />
<span>{iconState.label}</span>
</div>
);
}
// Usage in DesktopIconGrid
<DesktopIconGrid grid={{ cellWidth: 80, cellHeight: 90, gap: 10 }}>
{(props) => (
<DesktopIcon
iconId={props.iconState.id}
{...props}
/>
)}
</DesktopIconGrid>

The hook provides:

  • launchProps - Contains onDoubleClick handler that opens/focuses window
  • isWindowOpen - Boolean for styling active icons
  • existingWindow - The window state if already open
  • launch() - Imperative function to trigger open/focus

Using DesktopIconGrid

The DesktopIconGrid component renders all icons with grid awareness:

import { DesktopIconGrid } from 'glazier';
function Desktop() {
const gridConfig = {
cellWidth: 80,
cellHeight: 90,
gap: 8,
};
return (
<DesktopIconGrid grid={gridConfig} snapToGrid={true}>
{({
iconState,
pixelPosition,
isSelected,
isDragging,
wasDragged,
dragProps,
openOrFocus,
}) => (
<div
{...dragProps}
onClick={(e) => {
e.stopPropagation();
if (!wasDragged) openOrFocus();
}}
style={{
position: 'absolute',
left: pixelPosition.x,
top: pixelPosition.y,
cursor: isDragging ? 'grabbing' : 'pointer',
}}
>
<IconDisplay
label={iconState.label}
icon={iconState.icon}
isSelected={isSelected}
isDragging={isDragging}
/>
</div>
)}
</DesktopIconGrid>
);
}

Icon State

Each icon has the following state:

interface IconState {
id: string;
label: string;
componentId: string; // References WindowRegistry
componentProps?: Record<string, unknown>;
position: Position;
icon?: string; // Consumer-defined icon identifier
}

Grid Configuration

interface GridConfig {
cellWidth: number; // Width of each grid cell
cellHeight: number; // Height of each grid cell
gap?: number; // Gap between cells (default: 0)
}

Free Positioning (No Grid)

For icons that can be placed anywhere without grid snapping, use DesktopIcon directly:

import { DesktopIcon, useWindowManager } from 'glazier';
function FreePositionIcons() {
const { icons } = useWindowManager();
return (
<>
{icons.map((icon) => (
<DesktopIcon key={icon.id} id={icon.id}>
{({ iconState, dragProps, onLaunch, wasDragged }) => (
<div
{...dragProps}
onClick={() => !wasDragged && onLaunch()}
style={{
position: 'absolute',
left: iconState.position.x,
top: iconState.position.y,
}}
>
{iconState.label}
</div>
)}
</DesktopIcon>
))}
</>
);
}

Render Props

DesktopIconGrid Render Props

PropTypeDescription
iconStateIconStateThe icon’s current state
pixelPositionPositionCurrent pixel position
gridPosition{ row, column }Position in grid
isSelectedbooleanWhether icon is selected
isDraggingbooleanWhether icon is being dragged
wasDraggedbooleanWhether icon was moved during drag
dragPropsobjectProps to spread on the icon element
onSelect(multiSelect?) => voidSelect the icon
onLaunch() => voidLaunch associated window
isWindowOpenbooleanWhether associated window is open
openOrFocus() => voidOpens window or focuses if open

Visual Feedback for Active Icons

Use isWindowOpen from useIconLauncher or render props to style active icons:

function DesktopIcon({ iconId, iconState, ...props }) {
const { launchProps, isWindowOpen } = useIconLauncher({ iconId });
return (
<div {...props.dragProps} {...launchProps}>
<div className={`icon-image ${isWindowOpen ? 'bg-slate-600' : 'bg-slate-700'}`}>
<svg className={isWindowOpen ? 'text-blue-300' : 'text-blue-400'}>
{/* icon */}
</svg>
</div>
<span>{iconState.label}</span>
</div>
);
}

Complete Example

import {
WindowManagerProvider,
DesktopIconGrid,
useIconLauncher,
createRegistry,
} from 'glazier';
import { defineWindows } from 'glazier/server';
const windows = defineWindows({
home: {
title: 'Home',
defaultPosition: { x: 100, y: 100 },
defaultSize: { width: 400, height: 300 },
path: '/',
icon: { label: 'Home', iconKey: 'home', position: { x: 20, y: 20 } },
},
settings: {
title: 'Settings',
defaultPosition: { x: 150, y: 150 },
defaultSize: { width: 350, height: 400 },
path: '/settings',
icon: { label: 'Settings', iconKey: 'settings', position: { x: 20, y: 120 } },
},
});
const registry = createRegistry(windows.ids, {
home: HomeWindow,
settings: SettingsWindow,
});
function App() {
return (
<WindowManagerProvider
registry={registry}
defaultWindows={[windows.getWindowState('home')]}
defaultIcons={windows.getIconConfigs()}
>
<DesktopIconGrid grid={{ cellWidth: 80, cellHeight: 90, gap: 10 }}>
{(props) => <DesktopIcon {...props} />}
</DesktopIconGrid>
{/* ... rest of app */}
</WindowManagerProvider>
);
}
function DesktopIcon({ iconState, isSelected, isDragging, wasDragged, dragProps, onSelect }) {
const { launchProps, isWindowOpen } = useIconLauncher({ iconId: iconState.id });
return (
<div
{...dragProps}
{...launchProps}
onClick={(e) => {
e.stopPropagation();
if (!wasDragged) onSelect();
}}
style={{
position: 'absolute',
left: iconState.position.x,
top: iconState.position.y,
cursor: isDragging ? 'grabbing' : 'pointer',
opacity: isDragging ? 0.7 : 1,
}}
className={`flex flex-col items-center p-2 rounded ${
isSelected ? 'bg-blue-500/30 ring-1 ring-blue-400' : 'hover:bg-white/10'
}`}
>
<div className={`p-2 rounded ${isWindowOpen ? 'bg-slate-600' : 'bg-slate-700'}`}>
<IconSvg icon={iconState.icon} active={isWindowOpen} />
</div>
<span className="text-xs text-white truncate">{iconState.label}</span>
</div>
);
}