Recreating the Bliss of Windows XP
A love letter to the operating system that defined a generation — built with modern web technologies.
Why Recreate Windows XP?
For many of us, Windows XP was the first computer experience. The iconic Bliss wallpaper, the satisfying startup chime, and the unmistakable Luna theme are etched into collective memory. Windows XP Astro is an attempt to preserve that nostalgia — not as a static screenshot, but as a living, interactive experience in the browser.
“The best interface is the one you already know.”
The goal isn’t pixel-perfect emulation of every feature. Instead, it’s about capturing the feeling — the boot sequence anticipation, the satisfying click of the Start menu, and the simple joy of sweeping mines on a lazy afternoon.
Note: This is a personal project built for fun and learning.
It’s also a surprisingly effective way to explore modern frontend architecture while building something that feels familiar.
Features Overview
Windows XP Astro recreates the core desktop experience through several interactive layers:
Boot Sequence & Login
The application follows a realistic page flow that mirrors the original OS:
- Index — Initial landing; determines if this is a fresh boot or a return visit
- Startup — The classic XP boot animation with the scrolling progress bar
- Login — User selection screen with the friendly welcome panel
- Desktop — The full interactive environment
Each transition is managed through persisted state, so refreshing the page doesn’t dump you back to the start screen1.
Desktop Environment
The desktop provides the familiar XP experience:
- Draggable, resizable application windows
- A fully functional taskbar with:
- Start button and expandable Start menu
- Running application indicators
- System tray with clock
- Desktop icons that launch applications on double-click
- Window management (minimize, maximize, close)
Applications
Currently supported applications include:
| Application | Description | Window Options |
|---|---|---|
| Minesweeper | The classic puzzle game | Minimize, Close |
| Recycle Bin | Desktop staple | Minimize, Maximize, Close |
Minesweeper is fully playable and includes sub-applications for custom difficulty settings and a scoreboard — spawned dynamically from the main game window.
Tech Stack
The project is built on a modern frontend foundation:
- Astro — Static site framework with islands architecture
- React 19 — Interactive client-side components
- TypeScript — End-to-end type safety
- SCSS — Structured styling with ITCSS methodology
- PostHog — Privacy-friendly analytics
- Vercel — Deployment and hosting
Key Dependencies
{
"@astrojs/react": "^4.4.2",
"astro": "^5.16.5",
"react": "^19.2.3",
"react-draggable": "^4.5.0",
"sass-embedded": "^1.96.0"
} Architecture Highlights
The Astro / React Component Split
This is the most important architectural decision in the project. Astro components handle server-rendered, static UI while React components power client-side interactivity.
The Astro config enforces this boundary explicitly:
export default defineConfig({
integrations: [
react({
include: ["**/clientComponents/**"],
}),
],
}); Only files under src/clientComponents/ are hydrated as React islands. Placing a React component anywhere else means it won’t hydrate — it’ll render as static HTML and silently break.
Both directories follow Atomic Design principles:
atoms/— Buttons, icons, labelsmolecules/— Composed UI groups (menu items, window title bars)organisms/— Full features (application windows, the taskbar)
State Management via localStorage
There’s no Redux or Zustand here. State is managed through a type-safe localStorage wrapper:
export const SetInitialComputerState = () => {
const initialState: ComputerState = {
currentPage: "index",
firstLogin: true,
};
UpdateLocalStorageData("ComputerState", initialState);
};
export const UpdateCurrentComputerPage = (page: PageType) => {
const computerState = GetLocalStorageData("ComputerState");
if (computerState) {
computerState.currentPage = page;
UpdateLocalStorageData("ComputerState", computerState);
}
}; A StorageDataMap interface maps each storage key to its TypeScript type, so GetLocalStorageData<T>() always returns correctly typed data. No any types, no runtime surprises.
The Event System
Components communicate through custom browser events rather than prop drilling or a global store:
interface ApplicationEvent {
app: ApplicationWindows;
opened: boolean;
}
interface CreateApplicationEvent {
app: ApplicationWindows;
}
interface CustomMinesweeperEvent {
settings: DifficultySettings;
} Three core events coordinate the system:
applicationOpened— Fired when an app is opened, closed, or focusedcreateApplication— Spawns a new application instance (used for sub-apps)customMinesweeperEvent— Propagates Minesweeper-specific settings changes
The Application Registry
Every application is defined in a central registry with a consistent shape:
type Application = {
type: ApplicationWindows;
name: string;
icon: Icons;
windowOptions: WindowOptions[]; // "Close" | "Minimize" | "Maximize"
menuBarElements?: MenuBarElement[];
}; Desktop applications are a curated subset of all supported applications — sub-apps like minesweeper-custom-difficulty exist in the registry but aren’t placed on the desktop.
SCSS with ITCSS
Styles follow the Inverted Triangle CSS methodology, layered from generic to specific:
| Layer | Purpose |
|---|---|
01_reset | Global resets, box-sizing, Tahoma font |
02_settings | CSS custom properties — colors, gradients, UI tokens |
03_tools | SCSS mixins for responsive breakpoints |
04_shared | Shared button and event styles |
05_client-components | React component styling |
The classic XP title bar gradient, for instance, is defined as a CSS custom property:
--title-bar-bg: linear-gradient(
180deg,
rgba(9, 151, 255, 1) 0%,
rgba(0, 83, 238, 1) 8%,
rgba(0, 80, 238, 1) 40%,
rgba(0, 102, 255, 1) 88%,
rgba(0, 91, 255, 1) 95%,
rgba(0, 61, 215, 1) 100%
); Page Script Pattern
Astro’s view transitions require a specific two-script pattern to ensure code runs at the right time:
<!-- Inline script: re-runs on every page navigation -->
<script is:inline data-astro-rerun>
document.addEventListener(
"astro:page-load",
() => {
desktopInit();
},
{ once: true },
);
</script>
<!-- Module script: bundled once, guards against re-registration -->
<script>
import { HandlePageLoad } from "../lib/Logic/PageLogic";
if (!window.desktopInit) {
window.desktopInit = function () {
const continueLoad = HandlePageLoad("desktop");
if (!continueLoad) return;
};
}
</script> The inline script re-executes on every navigation, while the module script runs once and attaches the init function to window. The if (!window.desktopInit) guard prevents double-registration.
Adding a New Application
Want to add an app? Here’s the checklist:
Step-by-step guide
- Define the application in the
SupportedApplicationsrecord - Add its type to the
ApplicationWindowsunion inApplicationTypes.ts - Create the component under
src/clientComponents/organisms/ - Optionally add it to the
DesktopApplicationsarray for a desktop icon - Handle the
applicationOpenedevent in your component - Style it under
src/style/05_client-components/
Roadmap
Potential features and improvements for future development:
- Boot sequence animation
- Login screen
- Desktop with draggable windows
- Taskbar with Start menu
- Minesweeper (fully playable)
- Solitaire
- Notepad application
- Paint application
- Internet Explorer
(with actual browsing)with an easter egg page - Sound effects (startup chime, error ding, click feedback)
- Right-click context menus on the desktop
- Display properties / theme customization
- My Computer file browser
- Shutdown and restart animations
Known Issues
Everything works perfectly and there are no bugs. Here are some known quirks:
- Boot loop bug: If your last visited page was
startup, the state machine gets stuck in a loop — the startup page redirects back to itself2 - Window z-index stacking can occasionally get confused when rapidly switching between apps
Development
Getting started is straightforward. Clone the repo, install dependencies, and run the dev server:
# Requirements: Node 24.x, npm 11.6.1+
npm install
npm run dev Other useful commands:
npm run build # Build for production
npm run typecheck # Type checking (tsc --noEmit)
npm run lint:fix # Lint with auto-fix
npm run format # Format with Prettier Code style follows strict conventions: 4-space indentation, double quotes, semicolons, and a 100-character print width. The @/* path alias maps to ./src/*.
Credits & Links
Built with these excellent tools and resources:
- Astro — The web framework for content-driven websites
- React — A library for building user interfaces
- react-draggable — Draggable component for React
- Josh Comeau’s CSS Reset — A modern CSS reset
- ITCSS Architecture — Scalable and maintainable CSS methodology
This article was written as a markdown rendering test and project summary for Windows XP Astro.