- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Desktop Window
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Frame
- Heading
- Hover Card
- Input
- Input Group
- Input OTP
- Item
- Kbd
- Label
- Live Waveform
- Marker
- Menubar
- Message
- Message Scroller
- Meter
- Meter Ring
- Native Select
- Navigation Menu
- NumberCount
- Pagination
- Popover
- Progress
- Progress Ring
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Timeline
- Toast
- Toggle
- Toggle Group
- Tooltip
Version 0.5.0
Wrap a screenshot, a code sample, or a live component in window chrome. The frame is decoration — it does not drag or resize.
import {
DesktopWindow,
DesktopWindowContent,DesktopWindow makes any content look like it is running inside a desktop application window. It is chrome only: it does not drag, resize, minimise, maximise, or track focus. Use it for screenshots, marketing heroes, product mockups, and docs illustrations.
Installation#
bunx --bun shadcn@latest add @ondo-ui/desktop-window
Usage#
import {
DesktopWindow,
DesktopWindowContent,
DesktopWindowControls,
DesktopWindowTitle,
DesktopWindowTitlebar,
} from "@/components/ui/desktop-window"<DesktopWindow os="macos">
<DesktopWindowTitlebar>
<DesktopWindowControls />
<DesktopWindowTitle>Finder</DesktopWindowTitle>
</DesktopWindowTitlebar>
<DesktopWindowContent>Your content</DesktopWindowContent>
</DesktopWindow>Switching platforms#
os accepts "macos" (the default), "windows", and "ubuntu". It flips CSS, not the DOM — write DesktopWindowControls before DesktopWindowTitle on every platform and the layout rearranges itself.
The demo below maps one array over one piece of markup, so the only thing that varies between the three windows is the os value:
os="macos"
os="windows"
os="ubuntu"
import {
DesktopWindow,
DesktopWindowContent,What each value gives you:
| Controls | Title | Titlebar | |
|---|---|---|---|
macos | three 12px circles, left | centered, 13px semibold | 28px, hairline below |
windows | three 46×32 buttons, flush top-right | left, 12px | 32px, no separator |
ubuntu | three 24px gray circles, right | centered, 14px bold | 48px, hairline below |
The platforms also differ in a way that is easy to mistake for a bug: macOS reveals the ✕ − ⤢ glyphs only while the titlebar is hovered, whereas Windows 11 and Ubuntu always show — □ ✕. That is how each system behaves. Ubuntu's close button is a gray circle like its neighbours — the red close button is a Windows trait, not a universal one.
"ubuntu" renders the Yaru/GNOME headerbar that Ubuntu ships by default. Other Linux desktops — KDE Plasma, Xfce, elementary — draw their windows differently and are not covered by this value.
Color and theming#
The chrome is deliberately not themed. Titlebar backgrounds, traffic light colors, corner radii, and the Windows close-button red are fixed to the real platform values, so the frame reads as that operating system in any project you install it into.
Only DesktopWindowContent follows your theme — it uses bg-background. If you want the body to match a surface other than the page background, override it:
<DesktopWindowContent className="bg-muted p-6">Your content</DesktopWindowContent>Interactive controls#
Controls are decorative by default: each renders as an aria-hidden span, so a mockup does not add three no-op stops to the tab order. Pass a handler and that control — and only that control — becomes a real <button> with an aria-label and a focus ring.
Press a control, or tab into the window.
"use client"
import * as React from "react"Toolbars, icons, and sidebars#
There is no DesktopWindowToolbar or DesktopWindowSidebar. Anything beyond the core anatomy goes in as children. On macOS the title is absolutely positioned across the full titlebar, so a trailing action needs z-10 to stay above it.
Toolbars, app icons, and sidebars are not separate parts. Put them in the titlebar as children.
import { IconFolder, IconLayoutSidebar } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"Sizing#
DesktopWindow has no intrinsic width. It fills its container, so set the size from outside with className:
<DesktopWindow className="h-96 w-full max-w-3xl">Your content</DesktopWindow>API Reference#
DesktopWindow#
| Prop | Type | Default | Description |
|---|---|---|---|
os | "macos" | "windows" | "ubuntu" | "macos" | Which platform's chrome to render. Published to every child through context. |
Also accepts every div prop.
DesktopWindowControls#
| Prop | Type | Default | Description |
|---|---|---|---|
onClose | () => void | — | Promotes the close control to a labeled button. |
onMinimize | () => void | — | Promotes the minimise control to a labeled button. |
onMaximize | () => void | — | Promotes the zoom (macOS) or maximise (Windows, Ubuntu) control. |
Also accepts every div prop.
DesktopWindowTitlebar, DesktopWindowTitle, DesktopWindowContent#
Accept every div prop. DesktopWindowTitle is presentational text, not a heading — the frame is decoration and should not add a heading level to your document outline.