OpenSourceUIOpenSourceUIOpensource UI
GitHub—
  1. Home
  2. Components
  3. Mockups
  4. Apple iPod
Back to MockupsComponents / Mockups

Apple iPod

Free Mockups React component — IpodMockupCard. MIT licensed, copy-paste ready for Next.js and Tailwind CSS.

Classic iPod frame with a screen slot and click wheel — silver, black, white, pink, blue, green, or red finishes. Drop any UI into the screen area.

Preview

Menu◀◀▶▶▶❚

Setup

How to use

Free for personal and commercial use. No UI attribution required. Include the MIT copyright notice when copying component source into your project. Read the MIT license.

  1. 1

    Run in your terminal:

    $npm install clsx tailwind-merge lucide-react
  2. 2

    Copy lib/cn.ts below. Skip if you already have cn().

  3. 3

    Copy the code below and create components/mockups/apple-ipod-mockup-card.tsx in your project.

  4. 4

    Import and render:

    Example

    import { IpodMockupCard } from "@/components/mockups/apple-ipod-mockup-card";

    <IpodMockupCard variant="silver">{screenContent}</IpodMockupCard>

lib/cn.ts

1
2
3
4
5
6
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

components/mockups/apple-ipod-mockup-card.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import {
forwardRef,
type ComponentPropsWithoutRef,
type ReactNode,
} from "react";
 
import { cn } from "@/lib/cn";
 
const ipodFrameVariants = {
silver: {
body: "border-neutral-200/80 bg-[#e8e8ec]",
wheel: "border-neutral-200/80 bg-[#efeff2]",
wheelCenter: "border-neutral-200/70 bg-[#f8f8fa]",
label: "text-neutral-500",
},
black: {
body: "border-neutral-700/50 bg-[#2a2a2e]",
wheel: "border-neutral-600/50 bg-[#3a3a3e]",
wheelCenter: "border-neutral-600/40 bg-[#4a4a4f]",
label: "text-neutral-400",
},
white: {
body: "border-neutral-200/80 bg-[#f2f2f6]",
wheel: "border-neutral-200/80 bg-[#fafafa]",
wheelCenter: "border-neutral-200/70 bg-white",
label: "text-neutral-500",
},
pink: {
body: "border-rose-200/60 bg-[#eec0cc]",
wheel: "border-rose-200/60 bg-[#f4d0da]",
wheelCenter: "border-rose-200/50 bg-[#fae8ee]",
label: "text-rose-700/70",
},
blue: {
body: "border-sky-200/60 bg-[#a8c8e8]",
wheel: "border-sky-200/60 bg-[#c0d8f0]",
wheelCenter: "border-sky-200/50 bg-[#d8e8f8]",
label: "text-sky-800/70",
},
green: {
body: "border-emerald-200/60 bg-[#a8d4a8]",
wheel: "border-emerald-200/60 bg-[#c0e0c0]",
wheelCenter: "border-emerald-200/50 bg-[#d8f0d8]",
label: "text-emerald-800/70",
},
red: {
body: "border-red-200/50 bg-[#e07070]",
wheel: "border-red-200/50 bg-[#e88888]",
wheelCenter: "border-red-200/40 bg-[#f0a0a0]",
label: "text-red-900/70",
},
} as const;
 
export type IpodFrameVariant = keyof typeof ipodFrameVariants;
 
const FRAME_RADIUS = "1.35rem";
const FRAME_PADDING = "0.625rem";
const SCREEN_RADIUS = `calc(${FRAME_RADIUS} - ${FRAME_PADDING})`;
 
type IpodMockupCardProps = Readonly<
ComponentPropsWithoutRef<"div"> & {
variant?: IpodFrameVariant;
children?: ReactNode;
}
>;
 
function ClickWheel({
frame,
}: {
frame: (typeof ipodFrameVariants)[IpodFrameVariant];
}) {
return (
<div
className={cn(
"relative flex h-22 w-22 items-center justify-center rounded-full border",
frame.wheel,
)}
aria-hidden="true"
>
<span
className={cn(
"absolute top-2 left-1/2 -translate-x-1/2 text-[6px] font-semibold tracking-wide uppercase",
frame.label,
)}
>
Menu
</span>
<span
className={cn(
"absolute top-1/2 left-2 -translate-y-1/2 text-[7px] font-bold",
frame.label,
)}
>
{"\u25C0\u25C0"}
</span>
<span
className={cn(
"absolute top-1/2 right-2 -translate-y-1/2 text-[7px] font-bold",
frame.label,
)}
>
{"\u25B6\u25B6"}
</span>
<span
className={cn(
"absolute bottom-2 left-1/2 -translate-x-1/2 text-[7px] font-bold",
frame.label,
)}
>
{"\u25B6\u275A"}
</span>
 
<div
className={cn(
"flex h-8 w-8 items-center justify-center rounded-full border",
frame.wheelCenter,
)}
/>
</div>
);
}
 
export const IpodMockupCard = forwardRef<HTMLDivElement, IpodMockupCardProps>(
({ className, children, variant = "silver", ...props }, ref) => {
const frame = ipodFrameVariants[variant];
 
return (
<div
ref={ref}
data-slot="ipod-mockup-card"
data-variant={variant}
className={cn(
"flex w-55 flex-col border p-2.5 pb-3.5 shadow-[0_8px_24px_rgba(0,0,0,0.07)]",
frame.body,
className,
)}
style={{ borderRadius: FRAME_RADIUS }}
{...props}
>
<div
className="relative aspect-[1.32] w-full overflow-hidden bg-white shadow-[inset_0_0_0_1px_rgba(0,0,0,0.06)]"
style={{ borderRadius: SCREEN_RADIUS }}
>
{children}
</div>
 
<div className="mt-3 flex justify-center">
<ClickWheel frame={frame} />
</div>
</div>
);
},
);
 
IpodMockupCard.displayName = "IpodMockupCard";
PreviousNext

On this

page

Jump to a section on this page.

  • lib/cn.ts
  • components/mockups/apple-ipod-mockup-card.tsx
Platinum slotAvailable

Feature your product here.

Sticky card next to every component — highest-intent placement.

Claim this card