Nasaq

ProfileForm

The profile settings form. Photo, display name, username with a live availability check, email with a verified badge, resend and a password-protected change, phone, bio, language and time zone, and a sticky Save / Discard bar that shows only when something changed.

PreviewOpen ↗

Code

<ProfileForm  values={{ name: "Sara Alharbi", username: "sara", email: "[email protected]", phone: "", bio: "", locale: "en", timezone: "Asia/Riyadh" }}  emailVerified  checkUsername={async (u) => (await api.usernameFree(u)) }  onSubmit={async (values) => {    const res = await api.saveProfile(values);    if (!res.ok) return { fieldErrors: { username: "That username is taken." } };  }}  avatar={{ src: user.avatarUrl, onChange: uploadAvatar, onRemove: removeAvatar }}  onChangeEmail={async ({ email, password }) => api.changeEmail(email, password)}  onResendVerification={() => api.resendVerification()}/>

Account · beta

Live examples and controls: ProfileForm in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/profile-form.json

A complete "Profile" page body. It tracks what changed, checks the username while the user types, and shows a sticky save bar only when there is something to save. Nasaq talks to no server: every action is an async callback you provide.

When to use

  • The Profile section of an account settings page.

When not to use

  • Sign-up: use RegisterForm.
  • A form with different fields: build it from Field.

Import

import { ProfileForm } from "@fadymondy/nasaq/web";

Quick start

<ProfileForm
  values={{ name: "Sara Alharbi", username: "sara", email: "[email protected]", phone: "", bio: "", locale: "en", timezone: "Asia/Riyadh" }}
  emailVerified
  checkUsername={async (u) => (await api.usernameFree(u)) }
  onSubmit={async (values) => {
    const res = await api.saveProfile(values);
    if (!res.ok) return { fieldErrors: { username: "That username is taken." } };
  }}
  avatar={{ src: user.avatarUrl, onChange: uploadAvatar, onRemove: removeAvatar }}
  onChangeEmail={async ({ email, password }) => api.changeEmail(email, password)}
  onResendVerification={() => api.resendVerification()}
/>

Anatomy

ProfileForm                    data-slot="profile-form"
├─ AvatarUpload                when avatar.onChange is set
├─ Name, Username (@, LTR), Email (badge, resend, change), Phone, Bio (counter)
├─ Language (Select), Time zone (Combobox)
├─ ChangeEmail dialog          new email + current password
└─ save bar                    data-slot="profile-form-bar"  (sticky, only when dirty)

API

PropTypeDefaultDescription
valuesProfileValuesrequiredSaved values { name, username, email, phone, bio, locale, timezone }. New content resets the form.
onSubmit(values) => Promise<void | { error?, fieldErrors? }>requiredResolve on success. Return errors to show them; throwing shows a generic error.
avatar{ src, onChange, onRemove, accept, maxSize, outputSize, outputType }nonePhoto section; hidden without onChange.
checkUsername(username) => Promise<boolean | { available, message? }>noneDebounced, stale answers are dropped. Save is blocked while checking or taken.
usernameDebouncenumber400Milliseconds.
emailVerifiedbooleannoneShows the badge.
onResendVerification() => Promise<void>noneResend button, with a 30 second cooldown.
onChangeEmail({ email, password }) => Promise<void | ProfileSubmitResult>noneShows Change email and its dialog.
languages, timezonesProfileOption[]provider locales, all IANA zones{ value, label }.
bioMaxLengthnumber160
defaultCountrystring"SA"For the phone field.
disabledbooleanfalse
labelsPartial<ProfileLabels>en / arOverride any string.

Examples

  • Server-side field error: return { fieldErrors: { username: "..." } } from onSubmit.
  • Fixed language list: languages={[{ value: "en", label: "English" }, { value: "ar", label: "العربية" }]}.

Accessibility

  • Every input has a visible label, help text through aria-describedby, and errors through Field.
  • Availability results, resend results and save results are announced in live regions.
  • The save bar is reachable by keyboard; Discard restores the saved values and returns focus.
  • Autocomplete: name, username, email, tel.

RTL & i18n

  • English and Arabic strings follow the Nasaq locale; override with labels.
  • Username, email and phone stay left-to-right inside Arabic pages, and are isolated when placed inside sentences.
  • Counters use Western digits.

Styling & tokens

Uses Field, Input, Textarea, Badge, Alert and Dialog tokens; the bar uses bg-card, border-border and shadow-floating. Extend with className.

Do / Don't

  • Do re-validate everything on the server, including username availability.
  • Do return fieldErrors for problems tied to a field.
  • Do not save on blur; the sticky bar is the one way to commit.
  • Do not skip the password for an email change.

Lab

https://docs.nasaqui.com/?path=/docs/components-account-profile-form--docs

On this page