# Settings 4

> Stacked account fields for name, email, avatar, a copyable user ID, and deletion.

A shadcn/ui block from the [@efferd](https://efferd.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/efferd/settings-4).

- Page, with a live preview: https://shadcn.exchange/r/efferd/settings-4
- Screenshot: https://shadcn.exchange/shot/efferd/settings-4/light.png
- Registry item: https://efferd.com/r/settings-4.json
- Categories: settings
- Listed: 2026-10-29, source last changed 2026-10-29

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/efferd/settings-4.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `avatar`, `button`, `card`, `field`, `input-group`, `@efferd/use-copy-to-clipboard`, `@efferd/app-shell-4`

## Files

### `registry/blocks/settings/4/avatar-field.tsx`

```tsx
"use client";

import { useEffect, useState } from "react";
import {
	Avatar,
	AvatarFallback,
	AvatarImage,
} from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { SettingField } from "@/components/settings-field";
import { SettingsSection } from "@/components/settings-section";

const defaultAvatarSrc = "https://github.com/shabanhr.png";

export function AvatarField() {
	const [src, setSrc] = useState(defaultAvatarSrc);

	useEffect(() => {
		if (!src.startsWith("blob:")) {
			return;
		}
		return () => URL.revokeObjectURL(src);
	}, [src]);
	return (
		<SettingsSection
			action={<Button type="button">Save Changes</Button>}
			hint="Square image recommended. Accepted file types: .png, .jpg, .webp, .avif. Max file size: 2MB."
		>
			<SettingField
				description="This is your avatar image on your Efferd account. Click your avatar to upload a new image."
				htmlFor="avatar"
				label="Your Avatar"
			>
				<div className="flex items-center justify-center gap-2">
					<label className="cursor-pointer" htmlFor="avatar">
						<span className="sr-only">Upload a new avatar</span>
						<Avatar className="size-16">
							<AvatarImage alt="Shaban Haider" src={src} />
							<AvatarFallback>SH</AvatarFallback>
						</Avatar>
						<input
							accept=".png,.jpg,.jpeg,.webp,.avif"
							aria-describedby="avatar-hint"
							className="sr-only"
							id="avatar"
							onChange={(event) => {
								const file = event.target.files?.[0];
								if (!file) {
									return;
								}
								setSrc(URL.createObjectURL(file));
							}}
							type="file"
						/>
					</label>
				</div>
			</SettingField>
		</SettingsSection>
	);
}
```

### `registry/blocks/settings/4/delete-account-field.tsx`

```tsx
import { Button } from "@/components/ui/button";
import { SettingField } from "@/components/settings-field";
import { SettingsSection } from "@/components/settings-section";

export function DeleteAccountField() {
	return (
		<SettingsSection
			action={
				<Button id="delete-account" type="button" variant="destructive">
					Delete Account
				</Button>
			}
		>
			<SettingField
				description="Deletes your account and all data. This cannot be undone."
				htmlFor="delete-account"
				label="Delete Account"
			/>
		</SettingsSection>
	);
}
```

### `registry/blocks/settings/4/email-field.tsx`

```tsx
import { Button } from "@/components/ui/button";
import {
	InputGroup,
	InputGroupAddon,
	InputGroupInput,
} from "@/components/ui/input-group";
import { SettingField } from "@/components/settings-field";
import { SettingsSection } from "@/components/settings-section";

export function EmailField() {
	return (
		<SettingsSection
			action={<Button type="button">Save Changes</Button>}
			hint="We'll send a confirmation link before this address updates."
		>
			<SettingField
				description="Used to log in and receive notifications. Confirmation required for changes."
				htmlFor="email"
				label="Your Email"
			>
				<InputGroup>
					<InputGroupAddon>
						<IconPlaceholder
							hugeicons="AtIcon"
							lucide="AtSignIcon"
							phosphor="AtIcon"
							remixicon="RiAtLine"
							tabler="IconAt"
						/>
					</InputGroupAddon>
					<InputGroupInput
						aria-describedby="email-hint"
						autoComplete="email"
						defaultValue="shaban@efferd.com"
						id="email"
						type="email"
					/>
				</InputGroup>
			</SettingField>
		</SettingsSection>
	);
}
```

### `registry/blocks/settings/4/name-field.tsx`

```tsx
import { Button } from "@/components/ui/button";
import {
	InputGroup,
	InputGroupAddon,
	InputGroupInput,
} from "@/components/ui/input-group";
import { SettingField } from "@/components/settings-field";
import { SettingsSection } from "@/components/settings-section";

export function NameField() {
	return (
		<SettingsSection
			action={<Button type="button">Save Changes</Button>}
			hint="Max 32 characters."
		>
			<SettingField
				description="This is your display name on Efferd."
				htmlFor="display-name"
				label="Your Name"
			>
				<InputGroup>
					<InputGroupAddon>
						<IconPlaceholder
							hugeicons="UserIcon"
							lucide="UserIcon"
							phosphor="UserIcon"
							remixicon="RiUserLine"
							tabler="IconUser"
						/>
					</InputGroupAddon>
					<InputGroupInput
						aria-describedby="display-name-hint"
						autoComplete="name"
						defaultValue="Shaban Haider"
						id="display-name"
						maxLength={32}
					/>
				</InputGroup>
			</SettingField>
		</SettingsSection>
	);
}
```

### `registry/blocks/settings/4/settings-field.tsx`

```tsx
import {
	Field,
	FieldContent,
	FieldDescription,
	FieldLabel,
} from "@/components/ui/field";

export function SettingField({
	label,
	htmlFor,
	description,
	children,
	...props
}: React.ComponentProps<typeof Field> & {
	label: string;
	htmlFor: string;
	description: string;
}) {
	return (
		<Field orientation="responsive" {...props}>
			<FieldContent>
				<FieldLabel className="mb-1" htmlFor={htmlFor}>
					{label}
				</FieldLabel>
				<FieldDescription>{description}</FieldDescription>
			</FieldContent>
			{children ? <FieldContent>{children}</FieldContent> : null}
		</Field>
	);
}
```

### `registry/blocks/settings/4/settings-section.tsx`

```tsx
import { cn } from "@/lib/utils";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { FieldGroup } from "@/components/ui/field";

export function SettingsSection({
	hint,
	action,
	children,
	className,
	...props
}: React.ComponentProps<typeof Card> & {
	hint?: string;
	action?: React.ReactNode;
}) {
	return (
		<Card
			className={cn("pt-[calc(var(--card-spacing)*1.5)]", className)}
			{...props}
		>
			<CardContent>
				<FieldGroup>{children}</FieldGroup>
			</CardContent>
			{hint || action ? (
				<CardFooter
					className={cn(
						"gap-4",
						action && hint && "justify-between",
						action && !hint && "justify-end"
					)}
				>
					{hint ? (
						<p className="text-pretty text-muted-foreground text-xs">{hint}</p>
					) : null}
					{action}
				</CardFooter>
			) : null}
		</Card>
	);
}
```

### `registry/blocks/settings/4/settings.tsx`

```tsx
import { AvatarField } from "@/components/avatar-field";
import { DeleteAccountField } from "@/components/delete-account-field";
import { EmailField } from "@/components/email-field";
import { NameField } from "@/components/name-field";
import { UserIdField } from "@/components/user-id-field";

export function Settings() {
	return (
		<div className="flex flex-col gap-6">
			<NameField />
			<EmailField />
			<AvatarField />
			<UserIdField />
			<DeleteAccountField />
		</div>
	);
}
```

### `registry/blocks/settings/4/user-id-field.tsx`

```tsx
"use client";

import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
import {
	InputGroup,
	InputGroupAddon,
	InputGroupButton,
	InputGroupInput,
} from "@/components/ui/input-group";
import { SettingField } from "@/components/settings-field";
import { SettingsSection } from "@/components/settings-section";

const userId = "user_1UNKM8D5KJZV19B0KHC4VDQ9X";

export function UserIdField() {
	const { copied, copy } = useCopyToClipboard();

	return (
		<SettingsSection hint="This may be used to identify your account in the API.">
			<SettingField
				description="This is your unique account identifier on Efferd."
				htmlFor="user-id"
				label="Your User ID"
			>
				<InputGroup>
					<InputGroupInput
						aria-describedby="user-id-hint"
						className="font-mono tabular-nums"
						id="user-id"
						readOnly
						value={userId}
					/>
					<InputGroupAddon align="inline-end">
						<InputGroupButton
							aria-label={copied ? "Copied" : "Copy user ID"}
							onClick={() => copy(userId)}
							size="icon-xs"
						>
							{copied ? (
								<IconPlaceholder
									hugeicons="Tick01Icon"
									lucide="CheckIcon"
									phosphor="CheckIcon"
									remixicon="RiCheckLine"
									tabler="IconCheck"
								/>
							) : (
								<IconPlaceholder
									hugeicons="Copy01Icon"
									lucide="CopyIcon"
									phosphor="CopyIcon"
									remixicon="RiFileCopyLine"
									tabler="IconCopy"
								/>
							)}
						</InputGroupButton>
					</InputGroupAddon>
				</InputGroup>
			</SettingField>
		</SettingsSection>
	);
}
```
