# Settings 2

> Bordered three-column settings grid for profile, notifications, appearance, security, and a danger zone.

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-2).

- Page, with a live preview: https://shadcn.exchange/r/efferd/settings-2
- Screenshot: https://shadcn.exchange/shot/efferd/settings-2/light.png
- Registry item: https://efferd.com/r/settings-2.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-2.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`, `select`, `switch`, `toggle-group`, `@efferd/app-shell-2`

## Files

### `registry/blocks/settings/2/appearance-settings.tsx`

```tsx
import { FieldSeparator } from "@/components/ui/field";
import {
	Select,
	SelectContent,
	SelectGroup,
	SelectItem,
	SelectLabel,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
	ToggleGroup,
	ToggleGroupItem,
} from "@/components/ui/toggle-group";
import { SettingField } from "@/components/settings-field";
import { SettingsSection } from "@/components/settings-section";

const themeOptions = [
	{
		value: "light",
		label: "Light",
		icon: (
			<IconPlaceholder
				aria-hidden
				className="text-muted-foreground"
				hugeicons="Sun01Icon"
				lucide="SunIcon"
				phosphor="SunIcon"
				remixicon="RiSunLine"
				tabler="IconSun"
			/>
		),
	},
	{
		value: "dark",
		label: "Dark",
		icon: (
			<IconPlaceholder
				aria-hidden
				className="text-muted-foreground"
				hugeicons="Moon02Icon"
				lucide="MoonStarIcon"
				phosphor="MoonStarsIcon"
				remixicon="RiMoonLine"
				tabler="IconMoonStars"
			/>
		),
	},
	{
		value: "system",
		label: "System",
		icon: (
			<IconPlaceholder
				aria-hidden
				className="text-muted-foreground"
				hugeicons="ComputerSettingsIcon"
				lucide="MonitorCogIcon"
				phosphor="DesktopIcon"
				remixicon="RiComputerLine"
				tabler="IconDeviceDesktopCog"
			/>
		),
	},
] as const;

const languages = [
	{ value: "en", label: "English" },
	{ value: "de", label: "Deutsch" },
	{ value: "fr", label: "Français" },
	{ value: "ja", label: "日本語" },
] as const;

export function AppearanceSettings() {
	return (
		<SettingsSection
			description="Customize how Efferd looks for you."
			title="Appearance"
		>
			<SettingField
				description="Match your system or pick one."
				htmlFor="theme"
				label="Theme"
			>
				<ToggleGroup
					aria-label="Theme"
					defaultValue="system"
					id="theme"
					type="single"
					variant="outline"
				>
					{themeOptions.map((option) => (
						<ToggleGroupItem key={option.value} value={option.value}>
							<span className="flex items-center gap-1.5">
								{option.icon}
								{option.label}
							</span>
						</ToggleGroupItem>
					))}
				</ToggleGroup>
			</SettingField>
			<FieldSeparator />
			<SettingField
				description="Used across the interface."
				htmlFor="language"
				label="Language"
			>
				<Select defaultValue="en">
					<SelectTrigger id="language">
						<SelectValue />
					</SelectTrigger>
					<SelectContent>
						<SelectGroup>
							<SelectLabel>Languages</SelectLabel>
							{languages.map((language) => (
								<SelectItem key={language.value} value={language.value}>
									{language.label}
								</SelectItem>
							))}
						</SelectGroup>
					</SelectContent>
				</Select>
			</SettingField>
			<FieldSeparator />
			<SettingField
				description="Tighter spacing in tables and lists."
				htmlFor="compact-mode"
				label="Compact mode"
				orientation="horizontal"
			>
				<Switch id="compact-mode" />
			</SettingField>
		</SettingsSection>
	);
}
```

### `registry/blocks/settings/2/danger-settings.tsx`

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

export function DangerSettings() {
	return (
		<SettingsSection
			description="Irreversible and destructive actions."
			title="Danger zone"
		>
			<SettingField
				description="Download a copy of your data."
				htmlFor="export-data"
				label="Export data"
				orientation="horizontal"
			>
				<Button id="export-data" size="sm" type="button" variant="outline">
					Export
				</Button>
			</SettingField>
			<FieldSeparator />
			<SettingField
				description="Remove your account and all its data."
				htmlFor="delete-account"
				label="Delete account"
				orientation="horizontal"
			>
				<Button
					id="delete-account"
					size="sm"
					type="button"
					variant="destructive"
				>
					Delete
				</Button>
			</SettingField>
		</SettingsSection>
	);
}
```

### `registry/blocks/settings/2/notification-settings.tsx`

```tsx
import { Fragment } from "react";
import { FieldSeparator } from "@/components/ui/field";
import { Switch } from "@/components/ui/switch";
import { SettingField } from "@/components/settings-field";
import { SettingsSection } from "@/components/settings-section";

const notifications = [
	{
		id: "product-updates",
		label: "Product updates",
		description: "New features and release notes.",
		defaultChecked: true,
	},
	{
		id: "weekly-digest",
		label: "Weekly digest",
		description: "A Monday summary of activity.",
		defaultChecked: true,
	},
	{
		id: "mentions",
		label: "Mentions",
		description: "When someone @mentions you.",
		defaultChecked: true,
	},
	{
		id: "billing-alerts",
		label: "Billing alerts",
		description: "Failed payments and renewals.",
		defaultChecked: false,
	},
] as const;

export function NotificationSettings() {
	return (
		<SettingsSection
			description="Choose what we email you."
			title="Notifications"
		>
			{notifications.map((item, index) => (
				<Fragment key={item.id}>
					{index > 0 && <FieldSeparator />}
					<SettingField
						description={item.description}
						htmlFor={item.id}
						label={item.label}
						orientation="horizontal"
					>
						<Switch defaultChecked={item.defaultChecked} id={item.id} />
					</SettingField>
				</Fragment>
			))}
		</SettingsSection>
	);
}
```

### `registry/blocks/settings/2/profile-settings.tsx`

```tsx
"use client";
import { useEffect, useState } from "react";
import {
	Avatar,
	AvatarFallback,
	AvatarImage,
} from "@/components/ui/avatar";
import { FieldSeparator } from "@/components/ui/field";
import {
	InputGroup,
	InputGroupAddon,
	InputGroupInput,
	InputGroupText,
} from "@/components/ui/input-group";
import { SettingField } from "@/components/settings-field";
import { SettingsSection } from "@/components/settings-section";

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

export function ProfileSettings() {
	return (
		<SettingsSection
			className="lg:col-span-2"
			description="How you appear to teammates."
			title="Profile"
		>
			<SettingField
				description="Click to upload a square image, at least 256px."
				htmlFor="avatar"
				label="Avatar"
				orientation="horizontal"
			>
				<div className="flex shrink-0">
					<SettingsAvatar />
				</div>
			</SettingField>
			<FieldSeparator />
			<SettingField
				description="Used in comments, invoices, and the sidebar."
				htmlFor="display-name"
				label="Display name"
			>
				<InputGroup>
					<InputGroupAddon>
						<IconPlaceholder
							aria-hidden
							className="size-4"
							hugeicons="UserIcon"
							lucide="UserIcon"
							phosphor="UserIcon"
							remixicon="RiUserLine"
							tabler="IconUser"
						/>
					</InputGroupAddon>
					<InputGroupInput
						autoComplete="name"
						defaultValue="Shaban Haider"
						id="display-name"
					/>
				</InputGroup>
			</SettingField>
			<FieldSeparator />
			<SettingField
				description="Sign-in and billing receipts go here."
				htmlFor="email"
				label="Email"
			>
				<InputGroup>
					<InputGroupAddon>
						<IconPlaceholder
							aria-hidden
							className="size-4"
							hugeicons="MailAtSign01Icon"
							lucide="AtSignIcon"
							phosphor="AtIcon"
							remixicon="RiAtLine"
							tabler="IconAt"
						/>
					</InputGroupAddon>
					<InputGroupInput
						autoComplete="email"
						defaultValue="shaban@efferd.com"
						id="email"
						type="email"
					/>
				</InputGroup>
			</SettingField>
			<FieldSeparator />
			<SettingField
				description="Your public profile link."
				htmlFor="slug"
				label="Slug"
			>
				<InputGroup>
					<InputGroupAddon>
						<InputGroupText>efferd.com/</InputGroupText>
					</InputGroupAddon>
					<InputGroupInput
						autoComplete="slug"
						defaultValue="shaban"
						id="slug"
					/>
				</InputGroup>
			</SettingField>
		</SettingsSection>
	);
}

function SettingsAvatar() {
	const [src, setSrc] = useState(defaultSrc);

	useEffect(() => {
		if (!src.startsWith("blob:")) {
			return;
		}
		return () => URL.revokeObjectURL(src);
	}, [src]);

	return (
		<label className="group/avatar relative inline-flex cursor-pointer rounded-full outline-none focus-within:ring-3 focus-within:ring-ring/50">
			<input
				accept="image/*"
				aria-label="Change photo"
				className="sr-only"
				id="avatar"
				onChange={(event) => {
					const file = event.target.files?.[0];
					if (!file) {
						return;
					}
					setSrc(URL.createObjectURL(file));
				}}
				type="file"
			/>
			<Avatar className="size-12 hover:opacity-50">
				<AvatarImage alt="" src={src} />
				<AvatarFallback>SH</AvatarFallback>
			</Avatar>
		</label>
	);
}
```

### `registry/blocks/settings/2/security-settings.tsx`

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

export function SecuritySettings() {
	return (
		<SettingsSection description="Keep your account safe." title="Security">
			<SettingField
				description="Require a code when signing in."
				htmlFor="two-factor"
				label="Two-factor auth"
				orientation="horizontal"
			>
				<Switch defaultChecked id="two-factor" />
			</SettingField>
			<FieldSeparator />
			<SettingField
				description="Last changed 3 months ago."
				htmlFor="change-password"
				label="Password"
				orientation="horizontal"
			>
				<Button id="change-password" size="sm" type="button" variant="outline">
					Change
				</Button>
			</SettingField>
			<FieldSeparator />
			<SettingField
				description="Signed in on 3 devices."
				htmlFor="sign-out-sessions"
				label="Sessions"
				orientation="horizontal"
			>
				<Button
					id="sign-out-sessions"
					size="sm"
					type="button"
					variant="outline"
				>
					Sign out all
				</Button>
			</SettingField>
		</SettingsSection>
	);
}
```

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

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

export function SettingField({
	label,
	description,
	htmlFor,
	children,
	className,
	...props
}: React.ComponentProps<typeof Field> & {
	label: string;
	description: string;
	htmlFor: string;
	children: React.ReactNode;
	className?: string;
}) {
	return (
		<Field
			className={cn(
				"@md/field-group:has-[>[data-slot=field-content]]:items-center",
				className
			)}
			orientation="responsive"
			{...props}
		>
			<FieldContent>
				<FieldLabel className="font-normal" htmlFor={htmlFor}>
					{label}
				</FieldLabel>
				<FieldDescription className="text-xs">{description}</FieldDescription>
			</FieldContent>
			{children}
		</Field>
	);
}
```

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

```tsx
import {
	CardContent,
	CardDescription,
	CardHeader,
	CardTitle,
} from "@/components/ui/card";
import { FieldGroup } from "@/components/ui/field";
import { DashboardCard } from "@/components/dashboard-card";

export function SettingsSection({
	title,
	description,
	children,
	...props
}: Omit<React.ComponentProps<typeof DashboardCard>, "title"> & {
	title: string;
	description: string;
}) {
	return (
		<DashboardCard {...props}>
			<CardHeader className="gap-0 border-b">
				<CardTitle>{title}</CardTitle>
				<CardDescription>{description}</CardDescription>
			</CardHeader>
			<CardContent className="flex-1 pt-(--card-spacing)">
				<FieldGroup className="gap-5">{children}</FieldGroup>
			</CardContent>
		</DashboardCard>
	);
}
```

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

```tsx
import { Button } from "@/components/ui/button";
import { AppearanceSettings } from "@/components/appearance-settings";
import { DangerSettings } from "@/components/danger-settings";
import { NotificationSettings } from "@/components/notification-settings";
import { ProfileSettings } from "@/components/profile-settings";
import { SecuritySettings } from "@/components/security-settings";

export function Settings() {
	return (
		<div className="overflow-hidden border">
			<div className="grid grid-cols-1 gap-px bg-border lg:grid-cols-3">
				<ProfileSettings />
				<NotificationSettings />
				<AppearanceSettings />
				<SecuritySettings />
				<DangerSettings />
				<div className="flex flex-col gap-3 bg-background px-6 py-4 sm:flex-row sm:items-center sm:justify-between lg:col-span-3">
					<p className="text-muted-foreground text-sm">
						Changes apply to your account only.
					</p>
					<div className="flex gap-2">
						<Button type="button" variant="outline">
							Cancel
						</Button>
						<Button type="button">Save changes</Button>
					</div>
				</div>
			</div>
		</div>
	);
}
```

### `registry/blocks/dashboard/2/dashboard-card.tsx`

```tsx
import { cn } from "@/lib/utils";
import type * as React from "react";
import { Card } from "@/components/ui/card";

export function DashboardCard({
	className,
	...props
}: React.ComponentProps<typeof Card>) {
	return (
		<Card
			className={cn("rounded-none bg-background shadow-none ring-0", className)}
			{...props}
		/>
	);
}
```
