Iphone

PreviousNext

IPhone mockup with screen, header, and custom children content.

Docs
scrollxuicomponent

Preview

Loading preview…
components/ui/iphone.tsx
import type { HTMLAttributes } from "react"
import { Wifi, Battery, Signal } from "lucide-react"

export interface IphoneProps extends HTMLAttributes<HTMLDivElement> {
  imgUrl?: string
  clip?: string
  showHeader?: boolean
  headerClassName?: string
}

export function Iphone({ imgUrl, clip, showHeader, className, style, children, ...props }: IphoneProps) {
  const calcX = (21.25 / 433) * 100
  const calcY = (19.25 / 882) * 100
  const calcW = (389.5 / 433) * 100
  const calcH = (843.5 / 882) * 100
  const rX = (55.75 / 389.5) * 100
  const rY = (55.75 / 843.5) * 100

  const notchHeight = ((67 - 19.25) / 843.5) * 100

  const currentTime = new Date().toLocaleTimeString('en-US', {
    hour: '2-digit',
    minute: '2-digit',
    hour12: false
  })

  return (
    <div
      className={`relative inline-block w-full align-middle leading-none ${className}`}
      style={{ aspectRatio: `433/882`, ...style }}
      {...props}
    >
      {clip && (
        <div
          className="pointer-events-none absolute z-0 overflow-hidden bg-white dark:bg-black"
          style={{ left: `${calcX}%`, top: `${calcY}%`, width: `${calcW}%`, height: `${calcH}%`, borderRadius: `${rX}% / ${rY}%` }}
        >
          <video className="block size-full object-cover" src={clip} autoPlay loop muted playsInline preload="metadata" />
        </div>
      )}

      {!clip && imgUrl && (
        <div
          className="pointer-events-none absolute z-0 overflow-hidden bg-white dark:bg-black"
          style={{ left: `${calcX}%`, top: `${calcY}%`, width: `${calcW}%`, height: `${calcH}%`, borderRadius: `${rX}% / ${rY}%` }}
        >
          <img src={imgUrl || "/placeholder.svg"} alt="" className="block size-full object-cover object-top" />
        </div>
      )}

      {!clip && !imgUrl && (
        <div
          className="pointer-events-none absolute z-0 overflow-hidden bg-white dark:bg-black"
          style={{ left: `${calcX}%`, top: `${calcY}%`, width: `${calcW}%`, height: `${calcH}%`, borderRadius: `${rX}% / ${rY}%` }}
        />
      )}

      {children && (
        <div
          className="absolute z-10 overflow-hidden"
          style={{ left: `${calcX}%`, top: `${calcY}%`, width: `${calcW}%`, height: `${calcH}%`, borderRadius: `${rX}% / ${rY}%` }}
        >
          <div className="size-full" style={{ paddingTop: `${notchHeight}%` }}>
            {children}
          </div>
        </div>
      )}

      {showHeader && (
        <div
          className="absolute z-[15] flex items-center justify-between pointer-events-none text-[10px] sm:text-[11px] font-semibold px-[14px] sm:px-[22px]"
          style={{ left: `${calcX}%`, top: `${calcY}%`, width: `${calcW}%`, height: `${notchHeight}%` }}
        >
          <div className="text-black dark:text-white shrink-0">{currentTime}</div>
          <div className="flex items-center justify-end gap-[3px] sm:gap-[5px] min-w-[60px] sm:min-w-[70px]">
            <Signal className="text-black dark:text-white w-[11px] h-[11px] sm:w-[15px] sm:h-[15px]" strokeWidth={2.5} />
            <Wifi className="text-black dark:text-white w-[12px] h-[12px] sm:w-[16px] sm:h-[16px]" strokeWidth={2.5} />
            <Battery className="text-black fill-black dark:fill-white dark:text-white w-[18px] h-[18px] sm:w-[22px] sm:h-[22px]" strokeWidth={2.5} />
          </div>
        </div>
      )}

      <svg viewBox="0 0 433 882" fill="none" xmlns="http://www.w3.org/2000/svg" className="absolute inset-0 size-full pointer-events-none" style={{ transform: "translateZ(0)", zIndex: 20 }}>
        <g mask="url(#hole)">
          <path d="M2 73C2 32.6832 34.6832 0 75 0H357C397.317 0 430 32.6832 430 73V809C430 849.317 397.317 882 357 882H75C34.6832 882 2 849.317 2 809V73Z" fill="url(#metalGrad1)" />
          <path d="M0 171C0 170.448 0.447715 170 1 170H3V204H1C0.447715 204 0 203.552 0 203V171Z" fill="url(#metalGrad2)" />
          <path d="M1 234C1 233.448 1.44772 233 2 233H3.5V300H2C1.44772 300 1 299.552 1 299V234Z" fill="url(#metalGrad2)" />
          <path d="M1 319C1 318.448 1.44772 318 2 318H3.5V385H2C1.44772 385 1 384.552 1 384V319Z" fill="url(#metalGrad2)" />
          <path d="M430 279H432C432.552 279 433 279.448 433 280V384C433 384.552 432.552 385 430 385H430V279Z" fill="url(#metalGrad2)" />
          <path d="M6 74C6 35.3401 37.3401 4 76 4H356C394.66 4 426 35.3401 426 74V808C426 846.66 394.66 878 356 878H76C37.3401 878 6 846.66 6 808V74Z" fill="#1a1a1a" />
        </g>

        <path opacity="0.5" d="M174 5H258V5.5C258 6.60457 257.105 7.5 256 7.5H176C174.895 7.5 174 6.60457 174 5.5V5Z" fill="#888888" />

        <path d="M21.25 75C21.25 44.2101 46.2101 19.25 77 19.25H355C385.79 19.25 410.75 44.2101 410.75 75V807C410.75 837.79 385.79 862.75 355 862.75H77C46.2101 862.75 21.25 837.79 21.25 807V75Z" fill="url(#metalGrad3)" stroke="url(#metalGrad3)" strokeWidth="0.5" mask="url(#hole)" />

        <path d="M154 48.5C154 38.2827 162.283 30 172.5 30H259.5C269.717 30 278 38.2827 278 48.5C278 58.7173 269.717 67 259.5 67H172.5C162.283 67 154 58.7173 154 48.5Z" fill="#1a1a1a" />
        <path d="M249 48.5C249 42.701 253.701 38 259.5 38C265.299 38 270 42.701 270 48.5C270 54.299 265.299 59 259.5 59C253.701 59 249 54.299 249 48.5Z" fill="#1a1a1a" />
        <path d="M254 48.5C254 45.4624 256.462 43 259.5 43C262.538 43 265 45.4624 265 48.5C265 51.5376 262.538 54 259.5 54C256.462 54 254 51.5376 254 48.5Z" fill="url(#lensGrad)" />

        <defs>
          <linearGradient id="metalGrad1" x1="216" y1="0" x2="216" y2="882" gradientUnits="userSpaceOnUse">
            <stop stopColor="#e8e8e8" />
            <stop offset="0.5" stopColor="#c4c4c4" />
            <stop offset="1" stopColor="#a8a8a8" />
          </linearGradient>
          <linearGradient id="metalGrad2" x1="2" y1="170" x2="2" y2="385" gradientUnits="userSpaceOnUse">
            <stop stopColor="#d4d4d4" />
            <stop offset="1" stopColor="#9a9a9a" />
          </linearGradient>
          <linearGradient id="metalGrad3" x1="216" y1="19" x2="216" y2="863" gradientUnits="userSpaceOnUse">
            <stop stopColor="#d8d8d8" />
            <stop offset="0.5" stopColor="#b0b0b0" />
            <stop offset="1" stopColor="#949494" />
          </linearGradient>
          <radialGradient id="lensGrad" cx="259.5" cy="48.5" r="5.5" gradientUnits="userSpaceOnUse">
            <stop stopColor="#4d6b9a" />
            <stop offset="0.4" stopColor="#2a4573" />
            <stop offset="0.7" stopColor="#1a2d4d" />
            <stop offset="1" stopColor="#0a1428" />
          </radialGradient>
          <mask id="hole" maskUnits="userSpaceOnUse">
            <rect x="0" y="0" width="433" height="882" fill="white" />
            <rect x="21.25" y="19.25" width="389.5" height="843.5" rx="55.75" ry="55.75" fill="black" />
          </mask>
        </defs>
      </svg>
    </div>
  )
}

Installation

npx shadcn@latest add @scrollxui/iphone

Usage

import { Iphone } from "@/components/iphone"
<Iphone />