Blocks
Complete macOS windows, built only from CupertinoUI components. Sign In, Preferences, Save, Reminders and Feedback are free: install one with the CLI and change it to fit your app. Finder, System Settings, Mail, Notes, Safari, Calendar and About This Mac come with CupertinoUI Pro. The Finder and System Settings windows were rebuilt from a real macOS 27 desktop; see them together in the desktop demo.
Sign InFree
A sign-in window with email and password, a remember-me checkbox and a password-reset confirmation.
"use client"
import * as React from "react"
import { LockIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { IconTile } from "@/components/ui/icon-tile"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Window,
WindowContent,
WindowControls,
WindowMain,
WindowTitle,
WindowTitlebar,
} from "@/components/ui/window"
// A sign-in window: email, password, and a short reset confirmation.
export function Login01({
className,
...props
}: React.ComponentProps<typeof Window>) {
const emailId = React.useId()
const passwordId = React.useId()
const [email, setEmail] = React.useState("maya@example.com")
const [password, setPassword] = React.useState("")
const [mode, setMode] = React.useState<"form" | "reset" | "in">("form")
return (
<Window className={cn("h-[448px] w-[340px]", className)} {...props}>
<WindowMain>
<WindowTitlebar>
<WindowControls />
<WindowTitle>Sign In</WindowTitle>
</WindowTitlebar>
<WindowContent className="flex flex-col px-8 pt-8 pb-6">
{mode === "in" ? (
<div className="flex flex-1 flex-col items-center justify-center text-center">
<Avatar className="size-16 text-[22px]">
<AvatarFallback>MC</AvatarFallback>
</Avatar>
<h2 className="mt-4 text-mac-title-1 font-bold">Signed in</h2>
<p className="mt-1 text-mac-subheadline text-mac-label-secondary">{email}</p>
<Button variant="secondary" className="mt-6" onClick={() => setMode("form")}>
Sign Out
</Button>
</div>
) : mode === "reset" ? (
<div className="flex flex-1 flex-col items-center justify-center text-center">
<IconTile color="blue" className="size-16 rounded-[16px] [&_svg]:size-8">
<LockIcon strokeWidth={1.75} />
</IconTile>
<h2 className="mt-4 text-mac-title-1 font-bold">Check your inbox</h2>
<p className="mt-1 max-w-[240px] text-mac-body text-mac-label-secondary">
A reset link would be sent to {email}.
</p>
<Button variant="secondary" className="mt-6" onClick={() => setMode("form")}>
Back to Sign In
</Button>
</div>
) : (
<form
className="flex flex-1 flex-col"
onSubmit={(event) => {
event.preventDefault()
setMode("in")
}}
>
<div className="flex flex-col items-center text-center">
<IconTile color="blue" className="size-16 rounded-[16px] [&_svg]:size-8">
<LockIcon strokeWidth={1.75} />
</IconTile>
<h2 className="mt-4 text-mac-title-1 font-bold">Welcome back</h2>
<p className="mt-1 text-mac-subheadline text-mac-label-secondary">
Sign in to continue.
</p>
</div>
<div className="mt-6 flex flex-col gap-3">
<div className="flex flex-col gap-1">
<Label htmlFor={emailId}>Email</Label>
<Input
id={emailId}
type="email"
autoComplete="username"
value={email}
onChange={(event) => setEmail(event.target.value)}
required
/>
</div>
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between">
<Label htmlFor={passwordId}>Password</Label>
<Button
type="button"
variant="link"
className="text-mac-subheadline"
onClick={() => setMode("reset")}
>
Forgot?
</Button>
</div>
<Input
id={passwordId}
type="password"
autoComplete="current-password"
value={password}
onChange={(event) => setPassword(event.target.value)}
placeholder="Required"
required
/>
</div>
<Label className="gap-[6px]">
<Checkbox defaultChecked />
Remember me
</Label>
</div>
<Button type="submit" size="lg" className="mt-auto w-full">
Sign In
</Button>
</form>
)}
</WindowContent>
</WindowMain>
</Window>
)
}
PreferencesFree
An app's Settings window: a tabs switches between General, Appearance and Notifications, each a grouped form of switches, pop-up buttons and a slider.
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import {
GroupedForm,
GroupedFormGroup,
GroupedFormRow,
GroupedFormRowControl,
GroupedFormRowDescription,
GroupedFormRowLabel,
GroupedFormRowTitle,
GroupedFormSection,
} from "@/components/ui/grouped-form"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Slider } from "@/components/ui/slider"
import { Switch } from "@/components/ui/switch"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import {
Window,
WindowContent,
WindowControls,
WindowMain,
WindowTitle,
WindowTitlebar,
} from "@/components/ui/window"
// A pop-up button in a settings row.
function Choice({ label, items, defaultValue }: { label: string; items: Record<string, string>; defaultValue: string }) {
return (
<Select defaultValue={defaultValue} items={items}>
<SelectTrigger variant="borderless" aria-label={label}>
<SelectValue />
</SelectTrigger>
<SelectContent>
{Object.entries(items).map(([value, text]) => (
<SelectItem key={value} value={value}>
{text}
</SelectItem>
))}
</SelectContent>
</Select>
)
}
// A settings row with a title, an optional description and a control.
function Row({ title, description, children }: { title: string; description?: string; children: React.ReactNode }) {
return (
<GroupedFormRow className={cn(description && "items-start")}>
{description ? (
<GroupedFormRowLabel>
<GroupedFormRowTitle>{title}</GroupedFormRowTitle>
<GroupedFormRowDescription>{description}</GroupedFormRowDescription>
</GroupedFormRowLabel>
) : (
<GroupedFormRowTitle>{title}</GroupedFormRowTitle>
)}
<GroupedFormRowControl>{children}</GroupedFormRowControl>
</GroupedFormRow>
)
}
// An app's Settings window: a tabs picks General, Appearance
// or Notifications, each a grouped form of switches, pop-up buttons and a
// slider. Replace the rows with your own settings.
export function Preferences01({ className, ...props }: React.ComponentProps<typeof Window>) {
return (
<Window className={cn("h-[460px] w-[560px]", className)} {...props}>
<WindowMain>
<WindowTitlebar>
<WindowControls />
<WindowTitle>Settings</WindowTitle>
</WindowTitlebar>
<WindowContent className="flex flex-col">
<Tabs defaultValue="general" className="flex min-h-0 flex-1 flex-col items-center gap-4 pt-3">
<TabsList>
<TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="appearance">Appearance</TabsTrigger>
<TabsTrigger value="notifications">Notifications</TabsTrigger>
</TabsList>
<TabsContent value="general" className="w-full px-5 pb-5">
<GroupedForm>
<GroupedFormSection>
<GroupedFormGroup>
<Row title="Open at login">
<Switch size="mini" aria-label="Open at login" />
</Row>
<Row title="Check for updates automatically">
<Switch size="mini" defaultChecked aria-label="Check for updates automatically" />
</Row>
</GroupedFormGroup>
</GroupedFormSection>
<GroupedFormSection>
<GroupedFormGroup>
<Row title="New documents open in">
<Choice label="New documents open in" defaultValue="documents" items={{ documents: "Documents", desktop: "Desktop", icloud: "iCloud Drive" }} />
</Row>
<Row title="Language">
<Choice label="Language" defaultValue="system" items={{ system: "System Language", en: "English", de: "Deutsch", fr: "Français" }} />
</Row>
</GroupedFormGroup>
</GroupedFormSection>
</GroupedForm>
</TabsContent>
<TabsContent value="appearance" className="w-full px-5 pb-5">
<GroupedForm>
<GroupedFormSection>
<GroupedFormGroup>
<Row title="Appearance">
<Choice label="Appearance" defaultValue="auto" items={{ auto: "Auto", light: "Light", dark: "Dark" }} />
</Row>
<Row title="Text size">
<div className="w-40">
<Slider defaultValue={[40]} aria-label="Text size" />
</div>
</Row>
<Row title="Show the sidebar" description="Hide it to make more room for what you’re working on.">
<Switch size="mini" defaultChecked aria-label="Show the sidebar" />
</Row>
</GroupedFormGroup>
</GroupedFormSection>
</GroupedForm>
</TabsContent>
<TabsContent value="notifications" className="w-full px-5 pb-5">
<GroupedForm>
<GroupedFormSection>
<GroupedFormGroup>
<Row title="Allow notifications">
<Switch size="mini" defaultChecked aria-label="Allow notifications" />
</Row>
<Row title="Sound">
<Choice label="Sound" defaultValue="glass" items={{ none: "None", glass: "Glass", ping: "Ping", pop: "Pop" }} />
</Row>
<Row title="Badge the app icon">
<Switch size="mini" defaultChecked aria-label="Badge the app icon" />
</Row>
<Row title="Show previews">
<Choice label="Show previews" defaultValue="always" items={{ always: "Always", never: "Never" }} />
</Row>
</GroupedFormGroup>
</GroupedFormSection>
</GroupedForm>
</TabsContent>
</Tabs>
</WindowContent>
</WindowMain>
</Window>
)
}
SaveFree
A save dialog with a file name, a folder pop-up, optional tags, and Cancel and Save.
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import {
Window,
WindowContent,
WindowControls,
WindowMain,
WindowTitle,
WindowTitlebar,
} from "@/components/ui/window"
const folders = {
documents: "Documents",
desktop: "Desktop",
downloads: "Downloads",
icloud: "iCloud Drive",
} as const
type Folder = keyof typeof folders
// A save dialog: file name, destination, tags, then Cancel and Save.
export function SavePanel01({
className,
...props
}: React.ComponentProps<typeof Window>) {
const nameId = React.useId()
const tagsId = React.useId()
const [name, setName] = React.useState("Untitled.tsx")
const [folder, setFolder] = React.useState<Folder>("documents")
const [tags, setTags] = React.useState("")
const [saved, setSaved] = React.useState(false)
return (
<Window className={cn("h-[220px] w-[440px]", className)} {...props}>
<WindowMain>
<WindowTitlebar>
<WindowControls />
<WindowTitle>Save</WindowTitle>
</WindowTitlebar>
<WindowContent className="flex flex-col px-5 py-4">
{saved ? (
<div className="flex flex-1 flex-col">
<div className="flex flex-1 flex-col items-center justify-center text-center">
<p className="text-mac-body font-semibold">Saved</p>
<p className="mt-1 text-mac-subheadline text-mac-label-secondary">
{name} in {folders[folder]}
{tags ? `, tagged ${tags}` : ""}
</p>
</div>
<div className="flex justify-end">
<Button onClick={() => setSaved(false)}>Done</Button>
</div>
</div>
) : (
<form
className="flex flex-1 flex-col"
onSubmit={(event) => {
event.preventDefault()
setSaved(true)
}}
>
<div className="grid grid-cols-[72px_1fr] items-center gap-x-3 gap-y-3">
<Label htmlFor={nameId} className="justify-end text-mac-label-secondary">
Save As:
</Label>
<Input
id={nameId}
value={name}
onChange={(event) => setName(event.target.value)}
required
/>
<span className="justify-self-end text-mac-body text-mac-label-secondary">Where:</span>
<Select
value={folder}
onValueChange={(value) => value && setFolder(value as Folder)}
items={folders}
>
<SelectTrigger className="w-full" aria-label="Where">
<SelectValue />
</SelectTrigger>
<SelectContent>
{(Object.keys(folders) as Folder[]).map((value) => (
<SelectItem key={value} value={value}>
{folders[value]}
</SelectItem>
))}
</SelectContent>
</Select>
<Label htmlFor={tagsId} className="justify-end text-mac-label-secondary">
Tags:
</Label>
<Input
id={tagsId}
value={tags}
onChange={(event) => setTags(event.target.value)}
placeholder="Optional"
/>
</div>
<div className="mt-auto flex justify-end gap-2">
<Button
type="button"
variant="secondary"
onClick={() => {
setName("Untitled.tsx")
setTags("")
setFolder("documents")
}}
>
Cancel
</Button>
<Button type="submit">Save</Button>
</div>
</form>
)}
</WindowContent>
</WindowMain>
</Window>
)
}
Reminder ListFree
A short reminder list you can search, check off and add to.
"use client"
import * as React from "react"
import { PlusIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import { SearchField } from "@/components/ui/search-field"
import {
Window,
WindowContent,
WindowControls,
WindowMain,
WindowTitle,
WindowTitlebar,
} from "@/components/ui/window"
type Reminder = { id: number; title: string; done: boolean }
const initialReminders: Reminder[] = [
{ id: 1, title: "Ship the free blocks", done: false },
{ id: 2, title: "Reply to Maya", done: false },
{ id: 3, title: "Renew the domain", done: true },
{ id: 4, title: "Book a dentist appointment", done: false },
{ id: 5, title: "Export the October invoices", done: false },
]
// A short reminder list: search, check off, and add a new one.
export function Reminders01({
className,
...props
}: React.ComponentProps<typeof Window>) {
const [reminders, setReminders] = React.useState(initialReminders)
const [query, setQuery] = React.useState("")
const [draft, setDraft] = React.useState("")
const nextId = React.useRef(initialReminders.length + 1)
const needle = query.trim().toLowerCase()
const visible = reminders.filter((item) => item.title.toLowerCase().includes(needle))
function toggle(id: number, done: boolean) {
setReminders((current) =>
current.map((item) => (item.id === id ? { ...item, done } : item))
)
}
return (
<Window className={cn("h-[480px] w-[340px]", className)} {...props}>
<WindowMain>
<WindowTitlebar>
<WindowControls />
<WindowTitle>Reminders</WindowTitle>
</WindowTitlebar>
<WindowContent className="flex flex-col overflow-hidden">
<div className="px-3 pt-3 pb-2">
<SearchField
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search"
aria-label="Search reminders"
className="w-full"
/>
</div>
<div className="min-h-0 flex-1 overflow-auto px-1.5 pb-1">
{visible.length === 0 ? (
<p className="px-3 py-8 text-center text-mac-body text-mac-label-secondary">
{reminders.length === 0 ? "No reminders yet" : "No matching reminders"}
</p>
) : (
<ul>
{visible.map((item) => (
<li key={item.id}>
<label className="flex cursor-default items-center gap-2.5 rounded-[6px] px-2.5 py-[7px] hover:bg-mac-fill-quaternary">
<Checkbox
checked={item.done}
onCheckedChange={(checked) => toggle(item.id, checked === true)}
aria-label={item.title}
/>
<span
className={cn(
"min-w-0 truncate text-mac-body",
item.done && "text-mac-label-tertiary line-through"
)}
>
{item.title}
</span>
</label>
</li>
))}
</ul>
)}
</div>
<form
className="flex items-center gap-2 border-t border-mac-separator px-3 py-2.5"
onSubmit={(event) => {
event.preventDefault()
const title = draft.trim()
if (!title) return
const id = nextId.current++
setReminders((current) => [...current, { id, title, done: false }])
setDraft("")
setQuery("")
}}
>
<Input
value={draft}
onChange={(event) => setDraft(event.target.value)}
placeholder="New Reminder"
aria-label="New reminder"
/>
<Button
type="submit"
size="icon"
variant="secondary"
disabled={!draft.trim()}
aria-label="Add reminder"
>
<PlusIcon />
</Button>
</form>
</WindowContent>
</WindowMain>
</Window>
)
}
FeedbackFree
A feedback window with a topic, a subject, a message and a short thank-you after sending.
"use client"
import * as React from "react"
import { CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { IconTile } from "@/components/ui/icon-tile"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import {
Window,
WindowContent,
WindowControls,
WindowMain,
WindowTitle,
WindowTitlebar,
} from "@/components/ui/window"
const topics = {
bug: "Bug",
idea: "Idea",
question: "Question",
other: "Something else",
} as const
type Topic = keyof typeof topics
// A feedback window: topic, subject, message, then a short thank-you.
export function Feedback01({
className,
...props
}: React.ComponentProps<typeof Window>) {
const subjectId = React.useId()
const messageId = React.useId()
const [topic, setTopic] = React.useState<Topic>("idea")
const [subject, setSubject] = React.useState("")
const [message, setMessage] = React.useState("")
const [sent, setSent] = React.useState(false)
return (
<Window className={cn("h-[440px] w-[400px]", className)} {...props}>
<WindowMain>
<WindowTitlebar>
<WindowControls />
<WindowTitle>Feedback</WindowTitle>
</WindowTitlebar>
<WindowContent className="flex flex-col px-5 py-4">
{sent ? (
<div className="flex flex-1 flex-col items-center justify-center text-center">
<IconTile color="green" className="size-16 rounded-[16px] [&_svg]:size-8">
<CheckIcon strokeWidth={2} />
</IconTile>
<h2 className="mt-4 text-mac-title-1 font-bold">Thanks for the note</h2>
<p className="mt-1 max-w-[260px] text-mac-body text-mac-label-secondary">
{`${subject ? `${topics[topic]}: ${subject}` : topics[topic]}. This is a sample, so nothing was sent.`}
</p>
<Button
variant="secondary"
className="mt-6"
onClick={() => {
setSent(false)
setSubject("")
setMessage("")
}}
>
Send Another
</Button>
</div>
) : (
<form
className="flex flex-1 flex-col gap-3"
onSubmit={(event) => {
event.preventDefault()
setSent(true)
}}
>
<div className="flex items-center justify-between gap-3">
<Label>Topic</Label>
<Select
value={topic}
onValueChange={(value) => value && setTopic(value as Topic)}
items={topics}
>
<SelectTrigger aria-label="Topic">
<SelectValue />
</SelectTrigger>
<SelectContent>
{(Object.keys(topics) as Topic[]).map((value) => (
<SelectItem key={value} value={value}>
{topics[value]}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex flex-col gap-1">
<Label htmlFor={subjectId}>Subject</Label>
<Input
id={subjectId}
value={subject}
onChange={(event) => setSubject(event.target.value)}
placeholder="A short summary"
/>
</div>
<div className="flex min-h-0 flex-1 flex-col gap-1">
<Label htmlFor={messageId}>Message</Label>
<Textarea
id={messageId}
value={message}
onChange={(event) => setMessage(event.target.value)}
placeholder="What happened, or what would help?"
className="min-h-24 flex-1 resize-none"
required
/>
</div>
<Label className="gap-[6px]">
<Checkbox />
Include a screenshot
</Label>
<div className="flex justify-end">
<Button type="submit" disabled={!message.trim()}>
Send
</Button>
</div>
</form>
)}
</WindowContent>
</WindowMain>
</Window>
)
}
The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/finderSystem SettingsPro
The Desktop & Dock pane: a sidebar of colored icon tiles and a grouped form with sliders, pop-ups and switches.
The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/system-settingsThe source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/mail-01The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/notes-01The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/safari-01The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/calendar-01About This MacPro
A compact utility window with the device artwork, specs and a More Info button.
The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/about-this-mac-01The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/music-01The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/find-my-01Activity MonitorPro
A process list for CPU, memory, energy, disk and network. Search, sort by any column and watch the CPU load update live.
The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/activity-monitor-01The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/reminders-02The source of this block comes with CupertinoUI Pro. With a license key, the CLI installs it into your project.
See Pricingnpx shadcn@latest add @cupertinoui-pro/xcode-01