# iOS Mail

> Mailboxes, inbox and message view with read state and archive.

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

## Install

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

Adds the page at `app/ios-mail/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-mail/page.tsx"
"use client"

import * as React from "react"
import { Archive, ChevronRight, Inbox, Mail, Send, SquarePen, Star, Trash2 } from "lucide-react"

import { cn } from "@/lib/utils"
import { IOSBackButton, IOSDevice, IOSGroup, IOSNavButton, IOSPage, IOSRow } from "@/components/ios-kit"

type Message = { id: number; from: string; subject: string; preview: string; body: string; time: string; unread: boolean; starred: boolean }

const seed: Message[] = [
  { id: 1, from: "Maya Chen", subject: "Design review moved to 3pm", preview: "Heads up — the review is now at 3pm in the north room.", body: "Heads up — the review is now at 3pm in the north room. Bring the updated onboarding flow and the token audit. I'll send the agenda after lunch.", time: "9:12 AM", unread: true, starred: true },
  { id: 2, from: "Atelier Billing", subject: "Your receipt from November", preview: "Thanks for being a Pro member. Your receipt is attached.", body: "Thanks for being a Pro member. Your receipt for November is attached. No action is needed — your card will be charged on the 12th.", time: "8:03 AM", unread: true, starred: false },
  { id: 3, from: "Jordan Lee", subject: "Weekend hike?", preview: "Thinking Mt. Tam on Saturday if the weather holds up.", body: "Thinking Mt. Tam on Saturday if the weather holds up. Leaving around 8, back by lunch. Let me know and I'll grab the permits.", time: "Yesterday", unread: false, starred: false },
  { id: 4, from: "Priya Patel", subject: "Re: Launch checklist", preview: "I've ticked off everything except the changelog.", body: "I've ticked off everything except the changelog. Can you take a look at the last two items before we cut the release?", time: "Yesterday", unread: false, starred: true },
  { id: 5, from: "Swift Weekly", subject: "Issue 412: Concurrency, simplified", preview: "This week: structured concurrency patterns that actually scale.", body: "This week: structured concurrency patterns that actually scale, a tour of the new previews workflow and five libraries worth bookmarking.", time: "Tuesday", unread: false, starred: false },
]

const mailboxes = [
  { label: "Inbox", icon: Inbox },
  { label: "VIP", icon: Star },
  { label: "Sent", icon: Send },
  { label: "Archive", icon: Archive },
  { label: "Trash", icon: Trash2 },
]

export default function IOSMail() {
  const [messages, setMessages] = React.useState(seed)
  const [view, setView] = React.useState<"boxes" | "inbox">("inbox")
  const [openId, setOpenId] = React.useState<number | null>(null)
  const open = messages.find((m) => m.id === openId)
  const unread = messages.filter((m) => m.unread).length

  if (open) {
    return (
      <IOSDevice>
        <IOSPage
          title=""
          large={false}
          left={<IOSBackButton label="Inbox" onClick={() => setOpenId(null)} />}
          right={
            <span className="flex gap-4">
              <IOSNavButton aria-label="Archive" onClick={() => { setMessages((all) => all.filter((m) => m.id !== open.id)); setOpenId(null) }}><Archive className="size-[22px]" /></IOSNavButton>
              <IOSNavButton aria-label="Reply"><Mail className="size-[22px]" /></IOSNavButton>
            </span>
          }
        >
          <article className="px-5 pt-2 pb-10">
            <h1 className="text-[22px] leading-7 font-bold">{open.subject}</h1>
            <div className="mt-4 flex items-center gap-3">
              <span className="flex size-10 items-center justify-center rounded-full bg-(color:--ios-fill) text-[15px] font-semibold text-(color:--ios-label2)">{open.from.charAt(0)}</span>
              <div className="min-w-0 flex-1">
                <p className="truncate text-[16px] font-semibold">{open.from}</p>
                <p className="text-[13px] text-(color:--ios-label2)">To: You</p>
              </div>
              <span className="text-[13px] text-(color:--ios-label2)">{open.time}</span>
            </div>
            <p className="mt-5 text-[17px] leading-[24px]">{open.body}</p>
          </article>
        </IOSPage>
      </IOSDevice>
    )
  }

  if (view === "boxes") {
    return (
      <IOSDevice>
        <IOSPage title="Mailboxes" right={<IOSNavButton>Edit</IOSNavButton>}>
          <IOSGroup>
            {mailboxes.map(({ label, icon: Icon }) => (
              <IOSRow
                key={label}
                icon={<Icon />}
                iconClassName="bg-transparent text-(color:--ios-blue)"
                title={label}
                detail={label === "Inbox" ? unread : undefined}
                chevron
                onClick={() => label === "Inbox" && setView("inbox")}
              />
            ))}
          </IOSGroup>
        </IOSPage>
      </IOSDevice>
    )
  }

  return (
    <IOSDevice>
      <IOSPage
        title="Inbox"
        left={<IOSBackButton label="Mailboxes" onClick={() => setView("boxes")} />}
        right={<IOSNavButton>Edit</IOSNavButton>}
        tabbed
      >
        <ul className="bg-(color:--ios-surface)">
          {messages.map((m) => (
            <li key={m.id} className="relative after:absolute after:right-0 after:bottom-0 after:left-8 after:h-px after:bg-(color:--ios-sep)">
              <button
                type="button"
                onClick={() => {
                  setMessages((all) => all.map((x) => (x.id === m.id ? { ...x, unread: false } : x)))
                  setOpenId(m.id)
                }}
                className="flex w-full items-start gap-2 py-2.5 pr-4 pl-3 text-left active:bg-(color:--ios-fill)"
              >
                <span className={cn("mt-2 size-2.5 shrink-0 rounded-full", m.unread ? "bg-(color:--ios-blue)" : "bg-transparent")} />
                <span className="min-w-0 flex-1">
                  <span className="flex items-baseline justify-between gap-2">
                    <span className="truncate text-[17px] font-semibold">{m.from}</span>
                    <span className="flex shrink-0 items-center gap-0.5 text-[15px] text-(color:--ios-label2)">
                      {m.time}
                      <ChevronRight className="size-3.5 text-(color:--ios-label3)" />
                    </span>
                  </span>
                  <span className="block truncate text-[15px]">{m.subject}</span>
                  <span className="line-clamp-2 text-[15px] leading-5 text-(color:--ios-label2)">{m.preview}</span>
                </span>
              </button>
            </li>
          ))}
        </ul>
      </IOSPage>
      <div className="absolute inset-x-0 bottom-0 z-20 flex h-[84px] items-start justify-between border-t border-(color:--ios-sep) bg-(color:--ios-bg)/85 px-5 pt-3 backdrop-blur-xl">
        <span className="text-[11px] text-(color:--ios-label2)">
          <span className="block text-center text-[12px]">Updated Just Now</span>
          <span className="block text-center">{unread} Unread</span>
        </span>
        <button type="button" aria-label="Compose" className="absolute top-2.5 right-5 text-(color:--ios-blue) active:opacity-50">
          <SquarePen className="size-6" />
        </button>
      </div>
    </IOSDevice>
  )
}
```
