Skip to content
All Taste components

Mobile screens

Profile & settings

An account screen with the person's details and grouped settings rows, some with switches.

profile card · inset grouped rows · 3 switches · value + chevron rows · log out · tab bar

Profile

PM
Priya Menon

+91 98450 12321

priya.menon@mailbox.in

Preferences

Push notifications
Reminders by SMS
Dark appearance
LanguageEnglish

Account

Saved addresses3
Payment methods2
Privacy & security
// Mobile-first page; the same markup maps to React Native via NativeWind (SectionList/Switch).
"use client";
import { useState } from "react";
import { Bell, CalendarDays, ChevronRight, CreditCard, Globe, Heart, Home, LogOut, MapPin, MessageCircle, Moon, Pencil, ShieldCheck, User } from "lucide-react";
import { cn } from "@/lib/utils";

const me = { name: "Priya Menon", phone: "+91 98450 12321", email: "priya.menon@mailbox.in", initials: "PM" };
const toggles = [
  { id: "push", label: "Push notifications", icon: Bell, tone: "bg-primary text-primary-fg", on: true },
  { id: "sms", label: "Reminders by SMS", icon: MessageCircle, tone: "bg-success text-bg", on: true },
  { id: "dark", label: "Dark appearance", icon: Moon, tone: "bg-info text-bg", on: false },
];
const links = {
  Preferences: [{ label: "Language", icon: Globe, tone: "bg-warning text-bg", value: "English", href: "/settings/language" }],
  Account: [
    { label: "Saved addresses", icon: MapPin, tone: "bg-primary text-primary-fg", value: "3", href: "/settings/addresses" },
    { label: "Payment methods", icon: CreditCard, tone: "bg-success text-bg", value: "2", href: "/settings/payments" },
    { label: "Privacy & security", icon: ShieldCheck, tone: "bg-info text-bg", value: "", href: "/settings/privacy" },
  ],
};
const tabs = [
  { label: "Home", icon: Home },
  { label: "Bookings", icon: CalendarDays },
  { label: "Saved", icon: Heart },
  { label: "Profile", icon: User, current: true },
];

function Row({ icon: Icon, tone, label, divided, children }:
  { icon: typeof Bell; tone: string; label: string; divided?: boolean; children: React.ReactNode }) {
  return (
    <div className="flex items-center gap-3 pl-3">
      <span className={cn("grid size-[30px] shrink-0 place-items-center rounded-sm", tone)}><Icon className="size-4" aria-hidden /></span>
      <div className={cn("flex min-h-12 flex-1 items-center gap-2 pr-3 text-[14.5px]", divided && "border-t border-border")}>
        <span className="min-w-0 flex-1">{label}</span>
        {children}
      </div>
    </div>
  );
}

export default function ProfileScreen() {
  const [on, setOn] = useState<Record<string, boolean>>(Object.fromEntries(toggles.map((t) => [t.id, t.on])));

  return (
    <div className="mx-auto flex min-h-dvh max-w-md flex-col bg-bg font-sans text-fg">
      <main className="flex-1 px-4 pb-6 pt-2">
        <h1 className="text-2xl font-semibold tracking-tight">Profile</h1>

        <div className="mt-3 flex items-center gap-3 rounded-lg bg-surface p-3">
          <span className="grid size-[52px] place-items-center rounded-full bg-primary-soft text-[17px] font-medium text-primary-soft-fg">{me.initials}</span>
          <div className="min-w-0 flex-1">
            <p className="text-[15px] font-semibold">{me.name}</p>
            <p className="text-xs tabular-nums text-fg-muted">{me.phone}</p>
            <p className="truncate text-xs text-fg-muted">{me.email}</p>
          </div>
          <a href="/profile/edit" aria-label="Edit profile" className="-mr-2 grid size-11 place-items-center rounded-full hover:bg-surface-2"><Pencil className="size-5" /></a>
        </div>

        <h2 className="mx-1 mb-1.5 mt-5 text-xs font-medium text-fg-muted">Preferences</h2>
        <div className="overflow-hidden rounded-lg bg-surface">
          {toggles.map((t, i) => (
            <Row key={t.id} icon={t.icon} tone={t.tone} label={t.label} divided={i > 0}>
              <button
                type="button"
                role="switch"
                aria-checked={on[t.id]}
                aria-label={t.label}
                onClick={() => setOn((s) => ({ ...s, [t.id]: !s[t.id] }))}
                className="relative h-[22px] w-[38px] shrink-0 rounded-full bg-border-strong transition-colors aria-checked:bg-primary"
              >
                <span className={cn("absolute top-[3px] size-4 rounded-full bg-bg shadow-sm transition-[left]", on[t.id] ? "left-[19px]" : "left-[3px]")} />
              </button>
            </Row>
          ))}
          {links.Preferences.map((l) => (
            <Row key={l.label} icon={l.icon} tone={l.tone} label={l.label} divided>
              <a href={l.href} aria-label={l.label + ": " + l.value} className="flex min-h-11 items-center gap-0.5 text-[13.5px] text-fg-subtle">{l.value}<ChevronRight className="size-4" /></a>
            </Row>
          ))}
        </div>

        <h2 className="mx-1 mb-1.5 mt-5 text-xs font-medium text-fg-muted">Account</h2>
        <div className="overflow-hidden rounded-lg bg-surface">
          {links.Account.map((l, i) => (
            <Row key={l.label} icon={l.icon} tone={l.tone} label={l.label} divided={i > 0}>
              <a href={l.href} aria-label={l.label} className="flex min-h-11 items-center gap-0.5 text-[13.5px] tabular-nums text-fg-subtle">{l.value}<ChevronRight className="size-4" /></a>
            </Row>
          ))}
        </div>

        <button type="button" className="mt-4 flex h-12 w-full items-center justify-center gap-2 rounded-lg bg-surface text-[14.5px] font-medium text-danger">
          <LogOut className="size-[18px]" aria-hidden /> Log out
        </button>
      </main>

      <nav aria-label="Main" className="sticky bottom-0 grid grid-cols-4 border-t border-border bg-bg px-1.5 pb-[max(env(safe-area-inset-bottom),8px)] pt-1">
        {tabs.map(({ label, icon: Icon, current }) => (
          <a key={label} href="#" aria-current={current ? "page" : undefined}
            className={cn("flex min-h-11 flex-col items-center justify-center gap-0.5 text-[10.5px] font-medium", current ? "text-primary" : "text-fg-subtle")}>
            <Icon className="size-[22px]" aria-hidden />{label}
          </a>
        ))}
      </nav>
    </div>
  );
}

The code uses the Taste Tailwind preset. Make yours on the Taste page with Export my system.