# iOS Phone Keypad

> Dial pad, call button and recents.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/templates/ios-phone-keypad
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/ios-phone-keypad.json
```

Adds the page at `app/ios-phone-keypad/page.tsx` together with the components below. Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Components

- [iOS Kit](https://chromotion.dev/r/ios-kit.md): `@/components/ios-kit`

## Dependencies

```bash
npm install lucide-react
```

## Source

```tsx title="app/ios-phone-keypad/page.tsx"
"use client"

import * as React from "react"
import { Clock, Delete, Grid3x3, Phone, Star, User, Voicemail } from "lucide-react"

import { IOSDevice, IOSTabBar, type IOSTabItem } from "@/components/ios-kit"

const keys: [string, string][] = [
  ["1", ""], ["2", "ABC"], ["3", "DEF"],
  ["4", "GHI"], ["5", "JKL"], ["6", "MNO"],
  ["7", "PQRS"], ["8", "TUV"], ["9", "WXYZ"],
  ["*", ""], ["0", "+"], ["#", ""],
]

const tabs: IOSTabItem[] = [
  { id: "favorites", label: "Favorites", icon: Star },
  { id: "recents", label: "Recents", icon: Clock },
  { id: "contacts", label: "Contacts", icon: User },
  { id: "keypad", label: "Keypad", icon: Grid3x3 },
  { id: "voicemail", label: "Voicemail", icon: Voicemail, badge: 2 },
]

function format(digits: string) {
  if (/^[*#]/.test(digits) || digits.length <= 3) return digits
  if (digits.length <= 7) return `${digits.slice(0, 3)}-${digits.slice(3)}`
  const area = digits.slice(0, 3)
  const rest = digits.slice(3, 10)
  return `(${area}) ${rest.slice(0, 3)}-${rest.slice(3)}${digits.length > 10 ? digits.slice(10) : ""}`
}

export default function IOSPhoneKeypad() {
  const [digits, setDigits] = React.useState("")
  const [tab, setTab] = React.useState("keypad")
  const [calling, setCalling] = React.useState(false)

  return (
    <IOSDevice>
      <div className="flex min-h-0 flex-1 flex-col items-center pt-[54px] pb-[83px]">
        <div className="flex h-[76px] w-full items-end justify-center px-6 pb-2">
          <p
            aria-live="polite"
            className="truncate text-[38px] leading-[44px] font-light tracking-tight"
            data-empty={digits === ""}
          >
            {format(digits) || " "}
          </p>
        </div>
        <p className="h-6 text-[15px] text-(color:--ios-blue)">{digits ? "Add to Contacts" : " "}</p>

        <div className="mt-4 grid grid-cols-3 gap-x-[22px] gap-y-4">
          {keys.map(([key, letters]) => (
            <button
              key={key}
              type="button"
              onClick={() => setDigits((value) => (value.length < 14 ? value + key : value))}
              className="flex size-[78px] flex-col items-center justify-center rounded-full bg-black/[0.07] transition-colors duration-300 active:bg-black/25 active:duration-0 dark:bg-white/15 dark:active:bg-white/35"
            >
              <span className="text-[34px] leading-[34px] font-light">{key}</span>
              <span className="h-3 text-[10px] font-semibold tracking-[2px] text-(color:--ios-label)">{letters}</span>
            </button>
          ))}
        </div>

        <div className="mt-4 grid grid-cols-3 gap-x-[22px]">
          <span />
          <button
            type="button"
            aria-label={calling ? "End call" : "Call"}
            onClick={() => digits && setCalling((value) => !value)}
            className={`flex size-[78px] items-center justify-center rounded-full text-white transition-colors active:opacity-70 ${
              calling ? "bg-(color:--ios-red)" : "bg-(color:--ios-green)"
            } ${digits ? "" : "opacity-60"}`}
          >
            <Phone className={`size-9 ${calling ? "rotate-[135deg]" : ""} transition-transform`} fill="currentColor" />
          </button>
          <div className="flex items-center justify-center">
            {digits && (
              <button
                type="button"
                aria-label="Delete"
                onClick={() => {
                  setDigits((value) => value.slice(0, -1))
                  setCalling(false)
                }}
                className="flex size-[78px] items-center justify-center text-(color:--ios-label2) active:opacity-50"
              >
                <Delete className="size-8" strokeWidth={1.6} />
              </button>
            )}
          </div>
        </div>
      </div>
      <IOSTabBar items={tabs} value={tab} onValueChange={setTab} />
    </IOSDevice>
  )
}
```
