# iOS Notes

> Notes list, editor and pinning.

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

## Install

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

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

import * as React from "react"
import { Camera, CheckCircle2, Folder, PenLine, Pin, SquarePen, Table2, Type } from "lucide-react"

import { IOSBackButton, IOSDevice, IOSGroup, IOSNavButton, IOSPage, IOSSearchField } from "@/components/ios-kit"

type Note = { id: number; body: string; date: string; pinned?: boolean }

const seed: Note[] = [
  { id: 1, pinned: true, date: "Today", body: "Groceries\nOat milk, sourdough, lemons, basil, olive oil, dark chocolate" },
  { id: 2, pinned: true, date: "Yesterday", body: "Trip to Kyoto\nBook ryokan for the 14th. Fushimi Inari at sunrise. Nishiki Market for lunch." },
  { id: 3, date: "Monday", body: "Meeting notes\nShip onboarding revamp before the holidays. Jordan owns analytics." },
  { id: 4, date: "11/02/26", body: "Book ideas\nA field guide to ordinary things. Essays on small software." },
  { id: 5, date: "10/28/26", body: "Gift list\nMia: ceramic mug. Dad: field notebook. Sam: vinyl record." },
]

const title = (body: string) => body.split("\n")[0] || "New Note"
const preview = (body: string) => body.split("\n").slice(1).join(" ") || "No additional text"

function Row({ note, onOpen }: { note: Note; onOpen: () => void }) {
  return (
    <button
      type="button"
      onClick={onOpen}
      className="relative flex w-full flex-col px-4 py-2.5 text-left transition-colors after:absolute after:right-0 after:bottom-0 after:left-4 after:h-px after:bg-(color:--ios-sep) last:after:hidden active:bg-(color:--ios-fill)"
    >
      <span className="truncate text-[17px] leading-[22px] font-semibold">{title(note.body)}</span>
      <span className="flex gap-2 text-[15px] leading-5 text-(color:--ios-label2)">
        <span className="shrink-0">{note.date}</span>
        <span className="truncate">{preview(note.body)}</span>
      </span>
    </button>
  )
}

export default function IOSNotes() {
  const [notes, setNotes] = React.useState(seed)
  const [openId, setOpenId] = React.useState<number | null>(null)
  const [query, setQuery] = React.useState("")

  const open = notes.find((note) => note.id === openId)
  const edit = (body: string) => setNotes((list) => list.map((note) => (note.id === openId ? { ...note, body } : note)))
  const create = () => {
    const id = Math.max(...notes.map((note) => note.id)) + 1
    setNotes((list) => [{ id, body: "", date: "Today" }, ...list])
    setOpenId(id)
  }

  if (open) {
    return (
      <IOSDevice>
        <div className="flex min-h-0 flex-1 flex-col pt-[54px]">
          <div className="flex h-11 shrink-0 items-center justify-between px-3">
            <IOSBackButton label="Notes" onClick={() => setOpenId(null)} />
            <IOSNavButton className="font-semibold" onClick={() => setOpenId(null)}>Done</IOSNavButton>
          </div>
          <textarea
            autoFocus
            value={open.body}
            onChange={(event) => edit(event.target.value)}
            aria-label="Note"
            placeholder="Title"
            className="min-h-0 flex-1 resize-none bg-transparent px-5 py-2 text-[17px] leading-[24px] caret-(color:--ios-yellow) outline-none placeholder:text-(color:--ios-label3)"
          />
          <div className="flex shrink-0 items-center justify-around border-t border-(color:--ios-sep) bg-(color:--ios-surface) pt-2 pb-[38px] text-(color:--ios-yellow)">
            {[
              { label: "Format", icon: Type },
              { label: "Checklist", icon: CheckCircle2 },
              { label: "Table", icon: Table2 },
              { label: "Camera", icon: Camera },
              { label: "Markup", icon: PenLine },
            ].map(({ label, icon: Icon }) => (
              <button key={label} type="button" aria-label={label} className="p-2 active:opacity-50">
                <Icon className="size-6" />
              </button>
            ))}
          </div>
        </div>
      </IOSDevice>
    )
  }

  const q = query.trim().toLowerCase()
  const visible = notes.filter((note) => note.body.toLowerCase().includes(q))
  const pinned = visible.filter((note) => note.pinned)
  const others = visible.filter((note) => !note.pinned)

  return (
    <IOSDevice>
      <IOSPage title="Notes" left={<IOSNavButton aria-label="Folders" className="text-(color:--ios-yellow)"><Folder className="size-[22px]" /></IOSNavButton>} right={<IOSNavButton className="text-(color:--ios-yellow)">Edit</IOSNavButton>} tabbed>
        <div className="mb-4 px-4">
          <IOSSearchField value={query} onChange={(event) => setQuery(event.target.value)} />
        </div>
        {pinned.length > 0 && (
          <IOSGroup header={<span className="flex items-center gap-1"><Pin className="size-3" /> Pinned</span>}>
            {pinned.map((note) => <Row key={note.id} note={note} onOpen={() => setOpenId(note.id)} />)}
          </IOSGroup>
        )}
        {others.length > 0 && (
          <IOSGroup header="Earlier">
            {others.map((note) => <Row key={note.id} note={note} onOpen={() => setOpenId(note.id)} />)}
          </IOSGroup>
        )}
        {visible.length === 0 && <p className="pt-10 text-center text-(color:--ios-label2)">No Results</p>}
      </IOSPage>
      <div className="absolute inset-x-0 bottom-0 z-30 flex h-[83px] items-start justify-between border-t border-(color:--ios-sep) bg-(color:--ios-bg)/80 px-6 pt-2 backdrop-blur-xl">
        <span className="pt-2 text-[11px] text-(color:--ios-label2) max-sm:text-[12px]" aria-live="polite">{notes.length} Notes</span>
        <button type="button" aria-label="New note" onClick={create} className="p-1 text-(color:--ios-yellow) active:opacity-50">
          <SquarePen className="size-6" />
        </button>
      </div>
    </IOSDevice>
  )
}
```
